From 607ca10aba7b4e8c879d9a9094af9b9c6dedcae5 Mon Sep 17 00:00:00 2001
From: guosheng <gs@bronet.cn>
Date: Wed, 20 Nov 2019 11:11:18 +0800
Subject: [PATCH] 上传wechat

---
 .idea/workspace.xml                                                                                        |  664 ++++++++--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 composer.lock                                                                                              | 2277 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/.gitignore                                                  |    5 +++++
 vendor/easywechat-composer/easywechat-composer/.php_cs                                                     |   29 +++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/.travis.yml                                                 |   12 ++++++++++++
 vendor/easywechat-composer/easywechat-composer/LICENSE                                                     |   21 +++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/README.md                                                   |   55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/composer.json                                               |   35 +++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/phpunit.xml                                                 |   20 ++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Commands/ExtensionsCommand.php                          |   63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Commands/Provider.php                                   |   31 +++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Contracts/Encrypter.php                                 |   35 +++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationOptions.php                        |   80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationTo.php                             |   83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Delegation/Hydrate.php                                  |   83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/EasyWeChat.php                                          |   79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Encryption/DefaultEncrypter.php                         |   89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Exceptions/DecryptException.php                         |   21 +++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Exceptions/DelegationException.php                      |   42 ++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Exceptions/EncryptException.php                         |   21 +++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Extension.php                                           |  143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Http/DelegationResponse.php                             |   25 +++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Http/Response.php                                       |  104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Laravel/Http/Controllers/DelegatesController.php        |   49 +++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Laravel/ServiceProvider.php                             |  113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Laravel/config.php                                      |   29 +++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Laravel/routes.php                                      |   16 ++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/ManifestManager.php                                     |  120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Plugin.php                                              |   92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Traits/MakesHttpRequests.php                            |  110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/src/Traits/WithAggregator.php                               |   60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/easywechat-composer/easywechat-composer/tests/ManifestManagerTest.php                               |   37 +++++++++++++++++++++++++++++++++++++
 vendor/guzzlehttp/guzzle/.php_cs                                                                           |   21 +++++++++++++++++++++
 vendor/guzzlehttp/guzzle/Dockerfile                                                                        |   18 ++++++++++++++++++
 vendor/guzzlehttp/guzzle/phpstan.neon.dist                                                                 |    9 +++++++++
 vendor/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php                                        |    7 +++++++
 vendor/monolog/monolog/UPGRADE.md                                                                          |   72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php                                                   |   49 +++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php                                    |   89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php                                         |   66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php                                             |  124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php                                        |   59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php                                 |   37 +++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php                                     |   61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Handler/Handler.php                                                     |   53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php                                             |   88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php                                                 |   40 ++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php                                             |  146 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php                                              |  193 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php                                 |   38 ++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php                                     |   71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php                                             |  100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php                                                  |   64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php                                          |   99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php                                   |   24 ++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php                                         |   32 ++++++++++++++++++++++++++++++++
 vendor/monolog/monolog/src/Monolog/Test/TestCase.php                                                       |   66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/socialite/.github/FUNDING.yml                                                              |    9 +++++++++
 vendor/overtrue/socialite/.gitignore                                                                       |    9 +++++++++
 vendor/overtrue/socialite/src/Providers/DouYinProvider.php                                                 |  168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/socialite/src/Providers/OutlookProvider.php                                                |   88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/socialite/src/Providers/TaobaoProvider.php                                                 |  242 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/socialite/tests/UserTest.php                                                               |   24 ++++++++++++++++++++++++
 vendor/overtrue/wechat/CHANGELOG.md                                                                        | 1401 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/CONTRIBUTING.md                                                                     |   67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/BasicService/Application.php                                                    |   39 +++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/BasicService/ContentSecurity/Client.php                                         |  123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/BasicService/ContentSecurity/ServiceProvider.php                                |   31 +++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/BasicService/Jssdk/Client.php                                                   |  207 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/BasicService/Jssdk/ServiceProvider.php                                          |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/BasicService/Media/Client.php                                                   |  212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/BasicService/Media/ServiceProvider.php                                          |   44 ++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/BasicService/QrCode/Client.php                                                  |  120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/BasicService/QrCode/ServiceProvider.php                                         |   31 +++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/BasicService/Url/Client.php                                                     |   47 +++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/BasicService/Url/ServiceProvider.php                                            |   31 +++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Factory.php                                                                     |   54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/AccessToken.php                                                          |  282 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/BaseClient.php                                                           |  271 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Clauses/Clause.php                                                       |   64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Config.php                                                               |   23 +++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Contracts/AccessTokenInterface.php                                       |   40 ++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Contracts/Arrayable.php                                                  |   29 +++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Contracts/EventHandlerInterface.php                                      |   25 +++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Contracts/MediaInterface.php                                             |   25 +++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Contracts/MessageInterface.php                                           |   35 +++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Decorators/FinallyResult.php                                             |   35 +++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Decorators/TerminateResult.php                                           |   35 +++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Encryptor.php                                                            |  219 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Events/AccessTokenRefreshed.php                                          |   35 +++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Events/ApplicationInitialized.php                                        |   35 +++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Events/HttpResponseCreated.php                                           |   35 +++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Events/ServerGuardResponseCreated.php                                    |   35 +++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Exceptions/BadRequestException.php                                       |   21 +++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Exceptions/DecryptException.php                                          |   16 ++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Exceptions/Exception.php                                                 |   23 +++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Exceptions/HttpException.php                                             |   52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidArgumentException.php                                  |   21 +++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidConfigException.php                                    |   21 +++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Exceptions/RuntimeException.php                                          |   21 +++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Exceptions/UnboundServiceException.php                                   |   21 +++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Helpers.php                                                              |   55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Http/Response.php                                                        |  121 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Http/StreamResponse.php                                                  |   86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Log/LogManager.php                                                       |  583 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/Article.php                                                     |   58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/Card.php                                                        |   52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/DeviceEvent.php                                                 |   40 ++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/DeviceText.php                                                  |   50 ++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/File.php                                                        |   25 +++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/Image.php                                                       |   27 +++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/Link.php                                                        |   36 ++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/Location.php                                                    |   38 ++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/Media.php                                                       |   70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/Message.php                                                     |  208 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/MiniProgramPage.php                                             |   31 +++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/Music.php                                                       |   73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/News.php                                                        |   73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/NewsItem.php                                                    |   57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/Raw.php                                                         |   56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/ShortVideo.php                                                  |   30 ++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/TaskCard.php                                                    |   44 ++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/Text.php                                                        |   54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/TextCard.php                                                    |   40 ++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/Transfer.php                                                    |   56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/Video.php                                                       |   65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Messages/Voice.php                                                       |   37 +++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Providers/ConfigServiceProvider.php                                      |   39 +++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Providers/EventDispatcherServiceProvider.php                             |   47 +++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Providers/ExtensionServiceProvider.php                                   |   39 +++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Providers/HttpClientServiceProvider.php                                  |   39 +++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php                                         |   79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Providers/RequestServiceProvider.php                                     |   39 +++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/ServerGuard.php                                                          |  375 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/ServiceContainer.php                                                     |  167 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Support/AES.php                                                          |   85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Support/Arr.php                                                          |  466 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Support/ArrayAccessible.php                                              |   66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Support/Collection.php                                                   |  421 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Support/File.php                                                         |  135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Support/Helpers.php                                                      |  114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Support/Str.php                                                          |  193 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Support/XML.php                                                          |  167 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Traits/HasAttributes.php                                                 |  251 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Traits/HasHttpRequests.php                                               |  231 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Traits/InteractsWithCache.php                                            |  109 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Traits/Observable.php                                                    |  273 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Kernel/Traits/ResponseCastable.php                                              |   96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/Application.php                                                   |  176 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/Base/Client.php                                                   |  126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/Base/ServiceProvider.php                                          |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/Certficates/Client.php                                            |  103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/Certficates/ServiceProvider.php                                   |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/Kernel/BaseClient.php                                             |  261 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/EncryptException.php                            |   23 +++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidExtensionException.php                   |   23 +++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidSignException.php                        |   23 +++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/Material/Client.php                                               |   73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/Material/ServiceProvider.php                                      |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/Media/Client.php                                                  |   49 +++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/Media/ServiceProvider.php                                         |   44 ++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/Client.php                                         |  134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/ServiceProvider.php                                |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/Withdraw/Client.php                                               |   67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MicroMerchant/Withdraw/ServiceProvider.php                                      |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/Client.php                                          |   85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/ServiceProvider.php                                 |   28 ++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/AppCode/Client.php                                                  |   92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/AppCode/ServiceProvider.php                                         |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Application.php                                                     |   83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Auth/AccessToken.php                                                |   39 +++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Auth/Client.php                                                     |   43 +++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Auth/ServiceProvider.php                                            |   32 ++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Base/Client.php                                                     |   38 ++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Base/ServiceProvider.php                                            |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/CustomerService/ServiceProvider.php                                 |   34 ++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/DataCube/Client.php                                                 |  174 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/DataCube/ServiceProvider.php                                        |   28 ++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Encryptor.php                                                       |   50 ++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Express/Client.php                                                  |  133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Express/ServiceProvider.php                                         |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Mall/CartClient.php                                                 |   88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Mall/ForwardsMall.php                                               |   48 ++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Mall/MediaClient.php                                                |   37 +++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Mall/OrderClient.php                                                |   75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Mall/ProductClient.php                                              |   68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Mall/ServiceProvider.php                                            |   44 ++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/Client.php                                                |  123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/ServiceProvider.php                                       |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/OCR/ServiceProvider.php                                             |   34 ++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/OpenData/Client.php                                                 |   96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/OpenData/ServiceProvider.php                                        |   28 ++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Plugin/Client.php                                                   |   67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Plugin/DevClient.php                                                |   93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Plugin/ServiceProvider.php                                          |   42 ++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Server/ServiceProvider.php                                          |   42 ++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Soter/Client.php                                                    |   41 +++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/Soter/ServiceProvider.php                                           |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/Client.php                                         |  112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/ServiceProvider.php                                |   28 ++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/Client.php                                          |  114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/ServiceProvider.php                                 |   28 ++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/UniformMessage/Client.php                                           |  146 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/MiniProgram/UniformMessage/ServiceProvider.php                                  |   28 ++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Application.php                                                 |   88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Auth/AccessToken.php                                            |   39 +++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Auth/ServiceProvider.php                                        |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/AutoReply/Client.php                                            |   34 ++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/AutoReply/ServiceProvider.php                                   |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Base/Client.php                                                 |   84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Base/ServiceProvider.php                                        |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/Client.php                                         |  383 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/MessageBuilder.php                                 |  162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/ServiceProvider.php                                |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Card/BoardingPassClient.php                                     |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Card/Card.php                                                   |   48 ++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Card/Client.php                                                 |  405 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Card/CodeClient.php                                             |  193 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Card/CoinClient.php                                             |  119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Card/GeneralCardClient.php                                      |   71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Card/JssdkClient.php                                            |   85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Card/MeetingTicketClient.php                                    |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Card/MemberCardClient.php                                       |  123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Card/MovieTicketClient.php                                      |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Card/ServiceProvider.php                                        |   73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Card/SubMerchantClient.php                                      |  121 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Comment/Client.php                                              |  208 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Comment/ServiceProvider.php                                     |   44 ++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Client.php                                      |  230 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Messenger.php                                   |  165 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/CustomerService/ServiceProvider.php                             |   37 +++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/CustomerService/SessionClient.php                               |  104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/DataCube/Client.php                                             |  340 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/DataCube/ServiceProvider.php                                    |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Device/Client.php                                               |  251 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Device/ServiceProvider.php                                      |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Goods/Client.php                                                |  113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Goods/ServiceProvider.php                                       |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Material/Client.php                                             |  301 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Material/ServiceProvider.php                                    |   44 ++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Menu/Client.php                                                 |  103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Menu/ServiceProvider.php                                        |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/OAuth/ServiceProvider.php                                       |   66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/OCR/Client.php                                                  |   85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/OCR/ServiceProvider.php                                         |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/POI/Client.php                                                  |  145 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/POI/ServiceProvider.php                                         |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Semantic/Client.php                                             |   45 +++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Semantic/ServiceProvider.php                                    |   31 +++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Server/Guard.php                                                |   30 ++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Server/Handlers/EchoStrHandler.php                              |   51 +++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Server/ServiceProvider.php                                      |   46 ++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/Client.php                                          |   81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/DeviceClient.php                                    |  190 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/GroupClient.php                                     |  167 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/MaterialClient.php                                  |   44 ++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/PageClient.php                                      |  110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/RelationClient.php                                  |   87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ServiceProvider.php                                 |   57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ShakeAround.php                                     |   44 ++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/StatsClient.php                                     |  110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Store/Client.php                                                |  209 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/Store/ServiceProvider.php                                       |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/Client.php                                      |  234 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/ServiceProvider.php                             |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/User/ServiceProvider.php                                        |   35 +++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/User/TagClient.php                                              |  175 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/User/UserClient.php                                             |  172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/WiFi/CardClient.php                                             |   52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/WiFi/Client.php                                                 |   98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/WiFi/DeviceClient.php                                           |  127 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/WiFi/ServiceProvider.php                                        |   45 +++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OfficialAccount/WiFi/ShopClient.php                                             |  100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Application.php                                                    |  220 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Auth/AccessToken.php                                               |   49 +++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Auth/ServiceProvider.php                                           |   37 +++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Auth/VerifyTicket.php                                              |   91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/Account/Client.php                            |   96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/AggregateServiceProvider.php                  |   22 ++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Auth/AccessToken.php                                    |   79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/Client.php                          |   76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/ServiceProvider.php                 |   25 +++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Application.php                             |   53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Auth/Client.php                             |   64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/Client.php                             |  239 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/ServiceProvider.php                    |   25 +++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/Client.php                           |   54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/ServiceProvider.php                  |   25 +++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/Client.php                          |  248 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/ServiceProvider.php                 |   25 +++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/Client.php                           |   72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/ServiceProvider.php                  |   25 +++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Account/Client.php                      |   81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Application.php                         |   46 ++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/Client.php                  |   77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/ServiceProvider.php         |   25 +++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/OAuth/ComponentDelegate.php             |   54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Server/Guard.php                                        |   32 ++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Base/Client.php                                                    |  166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Base/ServiceProvider.php                                           |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/Client.php                                            |   86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/ServiceProvider.php                                   |   25 +++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Component/Client.php                                               |   60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Component/ServiceProvider.php                                      |   25 +++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Server/Guard.php                                                   |   64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Authorized.php                                     |   30 ++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Unauthorized.php                                   |   30 ++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/UpdateAuthorized.php                               |   30 ++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/VerifyTicketRefreshed.php                          |   48 ++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenPlatform/Server/ServiceProvider.php                                         |   34 ++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/Application.php                                                        |   85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/Auth/AccessToken.php                                                   |   52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/Auth/ServiceProvider.php                                               |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/Corp/Client.php                                                        |  217 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/Corp/ServiceProvider.php                                               |   38 ++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/MiniProgram/Client.php                                                 |   50 ++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/MiniProgram/ServiceProvider.php                                        |   31 +++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/Provider/Client.php                                                    |  206 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/Provider/ServiceProvider.php                                           |   36 ++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/Server/Guard.php                                                       |   68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/Server/Handlers/EchoStrHandler.php                                     |   62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/Server/ServiceProvider.php                                             |   56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/SuiteAuth/AccessToken.php                                              |   56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/SuiteAuth/ServiceProvider.php                                          |   37 +++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/SuiteAuth/SuiteTicket.php                                              |   85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/Work/Application.php                                                   |   41 +++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/OpenWork/Work/Auth/AccessToken.php                                              |   80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Application.php                                                         |  206 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Base/Client.php                                                         |   54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Base/ServiceProvider.php                                                |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Bill/Client.php                                                         |   48 ++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Bill/ServiceProvider.php                                                |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Coupon/Client.php                                                       |   77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Coupon/ServiceProvider.php                                              |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Jssdk/Client.php                                                        |  135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Jssdk/ServiceProvider.php                                               |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Kernel/BaseClient.php                                                   |  194 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/InvalidSignException.php                              |   18 ++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/SandboxException.php                                  |   18 ++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Merchant/Client.php                                                     |   94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Merchant/ServiceProvider.php                                            |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Notify/Handler.php                                                      |  201 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Notify/Paid.php                                                         |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Notify/Refunded.php                                                     |   48 ++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Notify/Scanned.php                                                      |   60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Order/Client.php                                                        |  126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Order/ServiceProvider.php                                               |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/ProfitSharing/Client.php                                                |  201 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/ProfitSharing/ServiceProvider.php                                       |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Redpack/Client.php                                                      |   88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Redpack/ServiceProvider.php                                             |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Refund/Client.php                                                       |  159 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Refund/ServiceProvider.php                                              |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Reverse/Client.php                                                      |   67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Reverse/ServiceProvider.php                                             |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Sandbox/Client.php                                                      |   60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Sandbox/ServiceProvider.php                                             |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Security/Client.php                                                     |   38 ++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Security/ServiceProvider.php                                            |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Transfer/Client.php                                                     |  122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Payment/Transfer/ServiceProvider.php                                            |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Agent/Client.php                                                           |   68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Agent/ServiceProvider.php                                                  |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Application.php                                                            |  100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Auth/AccessToken.php                                                       |   45 +++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Auth/ServiceProvider.php                                                   |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Base/Client.php                                                            |   34 ++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Base/ServiceProvider.php                                                   |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Chat/Client.php                                                            |   82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Chat/ServiceProvider.php                                                   |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Department/Client.php                                                      |   81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Department/ServiceProvider.php                                             |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/ExternalContact/Client.php                                                 |  118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/ExternalContact/ContactWayClient.php                                       |   98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/ExternalContact/MessageClient.php                                          |  168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/ExternalContact/ServiceProvider.php                                        |   45 +++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/ExternalContact/StatisticsClient.php                                       |   47 +++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/GroupRobot/Client.php                                                      |   49 +++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Image.php                                              |   41 +++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Markdown.php                                           |   40 ++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Message.php                                            |   23 +++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/News.php                                               |   55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/NewsItem.php                                           |   45 +++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Text.php                                               |   70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/GroupRobot/Messenger.php                                                   |  129 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/GroupRobot/ServiceProvider.php                                             |   37 +++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Invoice/Client.php                                                         |  100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Invoice/ServiceProvider.php                                                |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Jssdk/Client.php                                                           |   68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Jssdk/ServiceProvider.php                                                  |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Media/Client.php                                                           |  116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Media/ServiceProvider.php                                                  |   28 ++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Menu/Client.php                                                            |   61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Menu/ServiceProvider.php                                                   |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Message/Client.php                                                         |   48 ++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Message/Messenger.php                                                      |  205 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Message/ServiceProvider.php                                                |   43 +++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/MiniProgram/Application.php                                                |   44 ++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/MiniProgram/Auth/Client.php                                                |   39 +++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/OA/Client.php                                                              |  171 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/OA/ServiceProvider.php                                                     |   33 +++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/OAuth/AccessTokenDelegate.php                                              |   46 ++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/OAuth/ServiceProvider.php                                                  |   62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Server/Guard.php                                                           |   48 ++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Server/Handlers/EchoStrHandler.php                                         |   58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/Server/ServiceProvider.php                                                 |   46 ++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/User/Client.php                                                            |  251 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/User/ServiceProvider.php                                                   |   37 +++++++++++++++++++++++++++++++++++++
 vendor/overtrue/wechat/src/Work/User/TagClient.php                                                         |  178 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/pimple/pimple/.gitignore                                                                            |    3 +++
 vendor/pimple/pimple/ext/pimple/.gitignore                                                                 |   30 ++++++++++++++++++++++++++++++
 vendor/psr/cache/CHANGELOG.md                                                                              |   16 ++++++++++++++++
 vendor/psr/cache/LICENSE.txt                                                                               |   19 +++++++++++++++++++
 vendor/psr/cache/README.md                                                                                 |    9 +++++++++
 vendor/psr/cache/composer.json                                                                             |   25 +++++++++++++++++++++++++
 vendor/psr/cache/src/CacheException.php                                                                    |   10 ++++++++++
 vendor/psr/cache/src/CacheItemInterface.php                                                                |  105 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/psr/cache/src/CacheItemPoolInterface.php                                                            |  138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/psr/cache/src/InvalidArgumentException.php                                                          |   13 +++++++++++++
 vendor/psr/container/.gitignore                                                                            |    3 +++
 vendor/psr/log/.gitignore                                                                                  |    1 +
 vendor/symfony/cache-contracts/.gitignore                                                                  |    3 +++
 vendor/symfony/cache-contracts/CacheInterface.php                                                          |   57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache-contracts/CacheTrait.php                                                              |   76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache-contracts/CallbackInterface.php                                                       |   30 ++++++++++++++++++++++++++++++
 vendor/symfony/cache-contracts/ItemInterface.php                                                           |   65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache-contracts/LICENSE                                                                     |   19 +++++++++++++++++++
 vendor/symfony/cache-contracts/README.md                                                                   |    9 +++++++++
 vendor/symfony/cache-contracts/TagAwareCacheInterface.php                                                  |   38 ++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache-contracts/composer.json                                                               |   34 ++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/.gitignore                                                                            |    3 +++
 vendor/symfony/cache/Adapter/AbstractAdapter.php                                                           |  201 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php                                                   |  309 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/AdapterInterface.php                                                          |   37 +++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/ApcuAdapter.php                                                               |   27 +++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/ArrayAdapter.php                                                              |  163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/ChainAdapter.php                                                              |  310 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/DoctrineAdapter.php                                                           |   27 +++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/FilesystemAdapter.php                                                         |   29 +++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php                                                 |  152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/MemcachedAdapter.php                                                          |   37 +++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/NullAdapter.php                                                               |  140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/PdoAdapter.php                                                                |   54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/PhpArrayAdapter.php                                                           |  325 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/PhpFilesAdapter.php                                                           |   38 ++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/ProxyAdapter.php                                                              |  247 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/Psr16Adapter.php                                                              |   86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/RedisAdapter.php                                                              |   30 ++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php                                                      |  212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/SimpleCacheAdapter.php                                                        |   21 +++++++++++++++++++++
 vendor/symfony/cache/Adapter/TagAwareAdapter.php                                                           |  399 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php                                                  |   33 +++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/TraceableAdapter.php                                                          |  281 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php                                                  |   38 ++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/CHANGELOG.md                                                                          |   61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/CacheItem.php                                                                         |  206 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/DataCollector/CacheDataCollector.php                                                  |  190 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php                                            |   72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php                                          |   48 ++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/DependencyInjection/CachePoolPass.php                                                 |  178 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php                                           |   60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/DoctrineProvider.php                                                                  |  104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Exception/CacheException.php                                                          |   25 +++++++++++++++++++++++++
 vendor/symfony/cache/Exception/InvalidArgumentException.php                                                |   25 +++++++++++++++++++++++++
 vendor/symfony/cache/Exception/LogicException.php                                                          |   25 +++++++++++++++++++++++++
 vendor/symfony/cache/LICENSE                                                                               |   19 +++++++++++++++++++
 vendor/symfony/cache/LockRegistry.php                                                                      |  152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Marshaller/DefaultMarshaller.php                                                      |   99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Marshaller/MarshallerInterface.php                                                    |   40 ++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/PruneableInterface.php                                                                |   23 +++++++++++++++++++++++
 vendor/symfony/cache/Psr16Cache.php                                                                        |  263 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/README.md                                                                             |   18 ++++++++++++++++++
 vendor/symfony/cache/ResettableInterface.php                                                               |   21 +++++++++++++++++++++
 vendor/symfony/cache/Simple/AbstractCache.php                                                              |  191 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Simple/ApcuCache.php                                                                  |   29 +++++++++++++++++++++++++++++
 vendor/symfony/cache/Simple/ArrayCache.php                                                                 |  159 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Simple/ChainCache.php                                                                 |  257 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Simple/DoctrineCache.php                                                              |   34 ++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Simple/FilesystemCache.php                                                            |   36 ++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Simple/MemcachedCache.php                                                             |   34 ++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Simple/NullCache.php                                                                  |   90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Simple/PdoCache.php                                                                   |   59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Simple/PhpArrayCache.php                                                              |  248 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Simple/PhpFilesCache.php                                                              |   45 +++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Simple/Psr6Cache.php                                                                  |   23 +++++++++++++++++++++++
 vendor/symfony/cache/Simple/RedisCache.php                                                                 |   35 +++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Simple/TraceableCache.php                                                             |  243 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/AbstractRedisAdapterTest.php                                            |   46 ++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/AdapterTestCase.php                                                     |  263 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/ApcuAdapterTest.php                                                     |  124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/ArrayAdapterTest.php                                                    |   57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/ChainAdapterTest.php                                                    |  119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/DoctrineAdapterTest.php                                                 |   32 ++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/FilesystemAdapterTest.php                                               |   61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/FilesystemTagAwareAdapterTest.php                                       |   28 ++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/MaxIdLengthAdapterTest.php                                              |   87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/MemcachedAdapterTest.php                                                |  240 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/NamespacedProxyAdapterTest.php                                          |   31 +++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/NullAdapterTest.php                                                     |  141 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/PdoAdapterTest.php                                                      |   73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/PdoDbalAdapterTest.php                                                  |   45 +++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/PhpArrayAdapterTest.php                                                 |  160 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php                                     |   49 +++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/PhpFilesAdapterTest.php                                                 |   43 +++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/PredisAdapterTest.php                                                   |   47 +++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/PredisClusterAdapterTest.php                                            |   26 ++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/PredisRedisClusterAdapterTest.php                                       |   31 +++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/PredisTagAwareAdapterTest.php                                           |   34 ++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/PredisTagAwareClusterAdapterTest.php                                    |   34 ++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/PredisTagAwareRedisClusterAdapterTest.php                               |   34 ++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/ProxyAdapterTest.php                                                    |   74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/Psr16AdapterTest.php                                                    |   42 ++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/RedisAdapterTest.php                                                    |  108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/RedisArrayAdapterTest.php                                               |   24 ++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/RedisClusterAdapterTest.php                                             |   58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/RedisTagAwareAdapterTest.php                                            |   35 +++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/RedisTagAwareArrayAdapterTest.php                                       |   34 ++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/RedisTagAwareClusterAdapterTest.php                                     |   35 +++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/SimpleCacheAdapterTest.php                                              |   42 ++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/TagAwareAdapterTest.php                                                 |  223 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/TagAwareAndProxyAdapterIntegrationTest.php                              |   38 ++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/TraceableAdapterTest.php                                                |  191 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Adapter/TraceableTagAwareAdapterTest.php                                        |   37 +++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/CacheItemTest.php                                                               |   96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/DependencyInjection/CacheCollectorPassTest.php                                  |   49 +++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/DependencyInjection/CachePoolClearerPassTest.php                                |   73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/DependencyInjection/CachePoolPassTest.php                                       |  177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/DependencyInjection/CachePoolPrunerPassTest.php                                 |   70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/DoctrineProviderTest.php                                                        |   45 +++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Fixtures/ArrayCache.php                                                         |   52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Fixtures/ExternalAdapter.php                                                    |   76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/LockRegistryTest.php                                                            |   26 ++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Marshaller/DefaultMarshallerTest.php                                            |  112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Psr16CacheTest.php                                                              |  168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/AbstractRedisCacheTest.php                                               |   49 +++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/ApcuCacheTest.php                                                        |   38 ++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/ArrayCacheTest.php                                                       |   26 ++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/CacheTestCase.php                                                        |  141 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/ChainCacheTest.php                                                       |  114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/DoctrineCacheTest.php                                                    |   32 ++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/FilesystemCacheTest.php                                                  |   35 +++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/MemcachedCacheTest.php                                                   |  175 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/MemcachedCacheTextModeTest.php                                           |   28 ++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/NullCacheTest.php                                                        |   97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/PdoCacheTest.php                                                         |   48 ++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/PdoDbalCacheTest.php                                                     |   49 +++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/PhpArrayCacheTest.php                                                    |  130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/PhpArrayCacheWithFallbackTest.php                                        |   56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/PhpArrayCacheWrapper.php                                                 |   46 ++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/PhpFilesCacheTest.php                                                    |   39 +++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/Psr6CacheTest.php                                                        |   31 +++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/Psr6CacheWithAdapterTest.php                                             |   26 ++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/Psr6CacheWithoutAdapterTest.php                                          |   26 ++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/RedisArrayCacheTest.php                                                  |   27 +++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/RedisCacheTest.php                                                       |   85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/RedisClusterCacheTest.php                                                |   30 ++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Simple/TraceableCacheTest.php                                                   |  172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Traits/PdoPruneableTrait.php                                                    |   34 ++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Tests/Traits/TagAwareTestTrait.php                                                    |  158 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/AbstractAdapterTrait.php                                                       |  149 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/AbstractTrait.php                                                              |  284 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/ApcuTrait.php                                                                  |  121 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/ArrayTrait.php                                                                 |  165 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/ContractsTrait.php                                                             |   97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/DoctrineTrait.php                                                              |   98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/FilesystemCommonTrait.php                                                      |  144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/FilesystemTrait.php                                                            |  111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/MemcachedTrait.php                                                             |  329 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/PdoTrait.php                                                                   |  424 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/PhpArrayTrait.php                                                              |  152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/PhpFilesTrait.php                                                              |  282 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/ProxyTrait.php                                                                 |   43 +++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/RedisClusterProxy.php                                                          |   63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/RedisProxy.php                                                                 |   65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/Traits/RedisTrait.php                                                                 |  489 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/composer.json                                                                         |   58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/cache/phpunit.xml.dist                                                                      |   51 +++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher-contracts/.gitignore                                                       |    3 +++
 vendor/symfony/event-dispatcher-contracts/Event.php                                                        |   96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php                                     |   58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher-contracts/LICENSE                                                          |   19 +++++++++++++++++++
 vendor/symfony/event-dispatcher-contracts/README.md                                                        |    9 +++++++++
 vendor/symfony/event-dispatcher-contracts/composer.json                                                    |   34 ++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/.gitignore                                                                 |    3 +++
 vendor/symfony/event-dispatcher/CHANGELOG.md                                                               |   61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php                                         |  407 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php                                |   42 ++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/Debug/WrappedListener.php                                                  |  136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php                              |  152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/Event.php                                                                  |   38 ++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/EventDispatcher.php                                                        |  308 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/EventDispatcherInterface.php                                               |   82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/EventSubscriberInterface.php                                               |   46 ++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/GenericEvent.php                                                           |  175 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php                                               |  102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/LICENSE                                                                    |   19 +++++++++++++++++++
 vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php                                             |  147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/LegacyEventProxy.php                                                       |   62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/README.md                                                                  |   15 +++++++++++++++
 vendor/symfony/event-dispatcher/Tests/ChildEventDispatcherTest.php                                         |   26 ++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php                               |  319 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/Tests/Debug/WrappedListenerTest.php                                        |   60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php                    |  258 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php                                              |  511 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/Tests/EventTest.php                                                        |   55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/Tests/GenericEventTest.php                                                 |  134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php                                     |  100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/Tests/LegacyEventDispatcherProxyTest.php                                   |   63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/composer.json                                                              |   54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/event-dispatcher/phpunit.xml.dist                                                           |   31 +++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/.gitignore                                                                  |    3 +++
 vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php                                 |   21 +++++++++++++++++++++
 vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php                                   |   21 +++++++++++++++++++++
 vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php                                    |   21 +++++++++++++++++++++
 vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php                                     |   21 +++++++++++++++++++++
 vendor/symfony/http-foundation/File/Exception/NoFileException.php                                          |   21 +++++++++++++++++++++
 vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php                                    |   21 +++++++++++++++++++++
 vendor/symfony/http-foundation/File/Exception/PartialFileException.php                                     |   21 +++++++++++++++++++++
 vendor/symfony/http-foundation/HeaderUtils.php                                                             |  224 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Session/SessionUtils.php                                                    |   59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php                         |  124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php                             |  114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php                               |   54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php                                 |   85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php                                       |   77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php                                       |   53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php                                      |   55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php                                    |   56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php                                    |   56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php                                  |   63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/File/Fixtures/-test                                                   | Bin 0 -> 35 bytes
 vendor/symfony/http-foundation/Tests/HeaderUtilsTest.php                                                   |  132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php       |  145 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.expected               |   16 ++++++++++++++++
 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.php                    |   13 +++++++++++++
 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.expected |   23 +++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.php      |   15 +++++++++++++++
 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MigratingSessionHandlerTest.php               |  186 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php           |   22 ++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PredisSessionHandlerTest.php                  |   22 ++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisArraySessionHandlerTest.php              |   20 ++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php            |   31 +++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php                   |   23 +++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Test/Constraint/RequestAttributeValueSameTest.php                     |   41 +++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseCookieValueSameTest.php                       |   44 ++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseHasCookieTest.php                             |   42 ++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseHasHeaderTest.php                             |   39 +++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseHeaderSameTest.php                            |   39 +++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseIsRedirectedTest.php                          |   39 +++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseIsSuccessfulTest.php                          |   39 +++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseStatusCodeSameTest.php                        |   41 +++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/Tests/UrlHelperTest.php                                                     |  143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/http-foundation/UrlHelper.php                                                               |  102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/.gitignore                                                                             |    3 +++
 vendor/symfony/mime/Address.php                                                                            |   98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/BodyRendererInterface.php                                                              |   22 ++++++++++++++++++++++
 vendor/symfony/mime/CHANGELOG.md                                                                           |   12 ++++++++++++
 vendor/symfony/mime/CharacterStream.php                                                                    |  223 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php                                         |   48 ++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Email.php                                                                              |  593 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Encoder/AddressEncoderInterface.php                                                    |   30 ++++++++++++++++++++++++++++++
 vendor/symfony/mime/Encoder/Base64ContentEncoder.php                                                       |   50 ++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Encoder/Base64Encoder.php                                                              |   43 +++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Encoder/Base64MimeHeaderEncoder.php                                                    |   45 +++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Encoder/ContentEncoderInterface.php                                                    |   32 ++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Encoder/EightBitContentEncoder.php                                                     |   37 +++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Encoder/EncoderInterface.php                                                           |   28 ++++++++++++++++++++++++++++
 vendor/symfony/mime/Encoder/IdnAddressEncoder.php                                                          |   56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Encoder/MimeHeaderEncoderInterface.php                                                 |   25 +++++++++++++++++++++++++
 vendor/symfony/mime/Encoder/QpContentEncoder.php                                                           |   66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Encoder/QpEncoder.php                                                                  |  197 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Encoder/QpMimeHeaderEncoder.php                                                        |   42 ++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Encoder/Rfc2231Encoder.php                                                             |   52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Exception/AddressEncoderException.php                                                  |   21 +++++++++++++++++++++
 vendor/symfony/mime/Exception/ExceptionInterface.php                                                       |   21 +++++++++++++++++++++
 vendor/symfony/mime/Exception/InvalidArgumentException.php                                                 |   21 +++++++++++++++++++++
 vendor/symfony/mime/Exception/LogicException.php                                                           |   21 +++++++++++++++++++++
 vendor/symfony/mime/Exception/RfcComplianceException.php                                                   |   21 +++++++++++++++++++++
 vendor/symfony/mime/Exception/RuntimeException.php                                                         |   21 +++++++++++++++++++++
 vendor/symfony/mime/FileBinaryMimeTypeGuesser.php                                                          |   95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/FileinfoMimeTypeGuesser.php                                                            |   65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Header/AbstractHeader.php                                                              |  281 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Header/DateHeader.php                                                                  |   71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Header/HeaderInterface.php                                                             |   67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Header/Headers.php                                                                     |  285 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Header/IdentificationHeader.php                                                        |  115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Header/MailboxHeader.php                                                               |   90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Header/MailboxListHeader.php                                                           |  139 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Header/ParameterizedHeader.php                                                         |  176 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Header/PathHeader.php                                                                  |   67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Header/UnstructuredHeader.php                                                          |   71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/LICENSE                                                                                |   19 +++++++++++++++++++
 vendor/symfony/mime/Message.php                                                                            |  142 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/MessageConverter.php                                                                   |  127 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/MimeTypeGuesserInterface.php                                                           |   39 +++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/MimeTypes.php                                                                          | 3154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/MimeTypesInterface.php                                                                 |   34 ++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/NamedAddress.php                                                                       |   44 ++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Part/AbstractMultipartPart.php                                                         |   87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Part/AbstractPart.php                                                                  |   62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Part/DataPart.php                                                                      |  150 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Part/MessagePart.php                                                                   |   64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Part/Multipart/AlternativePart.php                                                     |   27 +++++++++++++++++++++++++++
 vendor/symfony/mime/Part/Multipart/DigestPart.php                                                          |   33 +++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Part/Multipart/FormDataPart.php                                                        |   96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Part/Multipart/MixedPart.php                                                           |   27 +++++++++++++++++++++++++++
 vendor/symfony/mime/Part/Multipart/RelatedPart.php                                                         |   57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Part/TextPart.php                                                                      |  190 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/README.md                                                                              |   18 ++++++++++++++++++
 vendor/symfony/mime/RawMessage.php                                                                         |   81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Resources/bin/update_mime_types.php                                                    |  166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/AbstractMimeTypeGuesserTest.php                                                  |  117 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/AddressTest.php                                                                  |   61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/CharacterStreamTest.php                                                          |   87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/DependencyInjection/AddMimeTypeGuesserPassTest.php                               |   42 ++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/EmailTest.php                                                                    |  387 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Encoder/Base64EncoderTest.php                                                    |  158 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Encoder/Base64MimeHeaderEncoderTest.php                                          |   23 +++++++++++++++++++++++
 vendor/symfony/mime/Tests/Encoder/QpEncoderTest.php                                                        |  213 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Encoder/QpMimeHeaderEncoderTest.php                                              |  139 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Encoder/Rfc2231EncoderTest.php                                                   |  129 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/FileBinaryMimeTypeGuesserTest.php                                                |   23 +++++++++++++++++++++++
 vendor/symfony/mime/Tests/FileinfoMimeTypeGuesserTest.php                                                  |   26 ++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Fixtures/mimetypes/-test                                                         | Bin 0 -> 35 bytes
 vendor/symfony/mime/Tests/Fixtures/mimetypes/.unknownextension                                             |    1 +
 vendor/symfony/mime/Tests/Fixtures/mimetypes/directory/.empty                                              |    0
 vendor/symfony/mime/Tests/Fixtures/mimetypes/other-file.example                                            |    0
 vendor/symfony/mime/Tests/Fixtures/mimetypes/test                                                          | Bin 0 -> 35 bytes
 vendor/symfony/mime/Tests/Fixtures/mimetypes/test.gif                                                      | Bin 0 -> 35 bytes
 vendor/symfony/mime/Tests/Fixtures/samples/charsets/iso-2022-jp/one.txt                                    |   11 +++++++++++
 vendor/symfony/mime/Tests/Fixtures/samples/charsets/iso-8859-1/one.txt                                     |   19 +++++++++++++++++++
 vendor/symfony/mime/Tests/Fixtures/samples/charsets/utf-8/one.txt                                          |   22 ++++++++++++++++++++++
 vendor/symfony/mime/Tests/Fixtures/samples/charsets/utf-8/three.txt                                        |   45 +++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Fixtures/samples/charsets/utf-8/two.txt                                          |    3 +++
 vendor/symfony/mime/Tests/Header/DateHeaderTest.php                                                        |   80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Header/HeadersTest.php                                                           |  241 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Header/IdentificationHeaderTest.php                                              |  173 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Header/MailboxHeaderTest.php                                                     |   77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Header/MailboxListHeaderTest.php                                                 |  131 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Header/ParameterizedHeaderTest.php                                               |  295 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Header/PathHeaderTest.php                                                        |   77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Header/UnstructuredHeaderTest.php                                                |  247 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/MessageConverterTest.php                                                         |   81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/MessageTest.php                                                                  |  151 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/MimeTypesTest.php                                                                |   61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/NamedAddressTest.php                                                             |   42 ++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Part/DataPartTest.php                                                            |  149 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Part/MessagePartTest.php                                                         |   42 ++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Part/Multipart/AlternativePartTest.php                                           |   25 +++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Part/Multipart/DigestPartTest.php                                                |   28 ++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Part/Multipart/FormDataPartTest.php                                              |   66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Part/Multipart/MixedPartTest.php                                                 |   25 +++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Part/Multipart/RelatedPartTest.php                                               |   30 ++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/Part/TextPartTest.php                                                            |   92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/Tests/RawMessageTest.php                                                               |   35 +++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/composer.json                                                                          |   39 +++++++++++++++++++++++++++++++++++++++
 vendor/symfony/mime/phpunit.xml.dist                                                                       |   31 +++++++++++++++++++++++++++++++
 vendor/symfony/polyfill-intl-idn/Idn.php                                                                   |  283 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/polyfill-intl-idn/LICENSE                                                                   |   19 +++++++++++++++++++
 vendor/symfony/polyfill-intl-idn/README.md                                                                 |   12 ++++++++++++
 vendor/symfony/polyfill-intl-idn/bootstrap.php                                                             |   59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/polyfill-intl-idn/composer.json                                                             |   36 ++++++++++++++++++++++++++++++++++++
 vendor/symfony/polyfill-php72/LICENSE                                                                      |   19 +++++++++++++++++++
 vendor/symfony/polyfill-php72/Php72.php                                                                    |  216 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/polyfill-php72/README.md                                                                    |   27 +++++++++++++++++++++++++++
 vendor/symfony/polyfill-php72/bootstrap.php                                                                |   36 ++++++++++++++++++++++++++++++++++++
 vendor/symfony/polyfill-php72/composer.json                                                                |   31 +++++++++++++++++++++++++++++++
 vendor/symfony/psr-http-message-bridge/.gitignore                                                          |    4 ++++
 vendor/symfony/service-contracts/.gitignore                                                                |    3 +++
 vendor/symfony/service-contracts/LICENSE                                                                   |   19 +++++++++++++++++++
 vendor/symfony/service-contracts/README.md                                                                 |    9 +++++++++
 vendor/symfony/service-contracts/ResetInterface.php                                                        |   30 ++++++++++++++++++++++++++++++
 vendor/symfony/service-contracts/ServiceLocatorTrait.php                                                   |  122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/service-contracts/ServiceProviderInterface.php                                              |   36 ++++++++++++++++++++++++++++++++++++
 vendor/symfony/service-contracts/ServiceSubscriberInterface.php                                            |   53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/service-contracts/ServiceSubscriberTrait.php                                                |   63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/service-contracts/Test/ServiceLocatorTest.php                                               |   92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/service-contracts/composer.json                                                             |   34 ++++++++++++++++++++++++++++++++++
 vendor/symfony/var-exporter/.gitignore                                                                     |    3 +++
 vendor/symfony/var-exporter/CHANGELOG.md                                                                   |    7 +++++++
 vendor/symfony/var-exporter/Exception/ClassNotFoundException.php                                           |   20 ++++++++++++++++++++
 vendor/symfony/var-exporter/Exception/ExceptionInterface.php                                               |   16 ++++++++++++++++
 vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php                                     |   20 ++++++++++++++++++++
 vendor/symfony/var-exporter/Instantiator.php                                                               |   94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/var-exporter/Internal/Exporter.php                                                          |  407 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/var-exporter/Internal/Hydrator.php                                                          |  151 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/var-exporter/Internal/Reference.php                                                         |   30 ++++++++++++++++++++++++++++++
 vendor/symfony/var-exporter/Internal/Registry.php                                                          |  136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/var-exporter/Internal/Values.php                                                            |   27 +++++++++++++++++++++++++++
 vendor/symfony/var-exporter/LICENSE                                                                        |   19 +++++++++++++++++++
 vendor/symfony/var-exporter/README.md                                                                      |   38 ++++++++++++++++++++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/abstract-parent.php                                             |   20 ++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/array-iterator-legacy.php                                       |   22 ++++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/array-iterator.php                                              |   19 +++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/array-object-custom-legacy.php                                  |   22 ++++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/array-object-custom.php                                         |   21 +++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/array-object-legacy.php                                         |   29 +++++++++++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/array-object.php                                                |   28 ++++++++++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/bool.php                                                        |    3 +++
 vendor/symfony/var-exporter/Tests/Fixtures/clone.php                                                       |   15 +++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/datetime.php                                                    |   25 +++++++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/error.php                                                       |   30 ++++++++++++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/external-references.php                                         |    7 +++++++
 vendor/symfony/var-exporter/Tests/Fixtures/final-array-iterator-legacy.php                                 |   11 +++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/final-array-iterator.php                                        |   17 +++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/final-error-legacy.php                                          |   27 +++++++++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/final-error.php                                                 |   27 +++++++++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/final-stdclass.php                                              |   11 +++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/foo-serializable.php                                            |   11 +++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/hard-references-recursive.php                                   |   16 ++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/hard-references.php                                             |   18 ++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/incomplete-class.php                                            |   11 +++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/lf-ending-string.php                                            |    4 ++++
 vendor/symfony/var-exporter/Tests/Fixtures/multiline-string.php                                            |    7 +++++++
 vendor/symfony/var-exporter/Tests/Fixtures/partially-indexed-array.php                                     |    8 ++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/php74-serializable.php                                          |   16 ++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/private-constructor.php                                         |   17 +++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/private.php                                                     |   26 ++++++++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/serializable.php                                                |   14 ++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/simple-array.php                                                |    8 ++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/spl-object-storage-legacy.php                                   |   21 +++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/spl-object-storage.php                                          |   20 ++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/var-on-sleep.php                                                |   17 +++++++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/wakeup-refl.php                                                 |   13 +++++++++++++
 vendor/symfony/var-exporter/Tests/Fixtures/wakeup.php                                                      |   25 +++++++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/InstantiatorTest.php                                                     |   73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/var-exporter/Tests/VarExporterTest.php                                                      |  428 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/var-exporter/VarExporter.php                                                                |  114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vendor/symfony/var-exporter/composer.json                                                                  |   36 ++++++++++++++++++++++++++++++++++++
 vendor/symfony/var-exporter/phpunit.xml.dist                                                               |   31 +++++++++++++++++++++++++++++++
 830 files changed, 72444 insertions(+), 656 deletions(-)
 create mode 100644 composer.lock
 create mode 100644 vendor/easywechat-composer/easywechat-composer/.gitignore
 create mode 100644 vendor/easywechat-composer/easywechat-composer/.php_cs
 create mode 100644 vendor/easywechat-composer/easywechat-composer/.travis.yml
 create mode 100644 vendor/easywechat-composer/easywechat-composer/LICENSE
 create mode 100644 vendor/easywechat-composer/easywechat-composer/README.md
 create mode 100644 vendor/easywechat-composer/easywechat-composer/composer.json
 create mode 100644 vendor/easywechat-composer/easywechat-composer/phpunit.xml
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Commands/ExtensionsCommand.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Commands/Provider.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Contracts/Encrypter.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationOptions.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationTo.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Delegation/Hydrate.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/EasyWeChat.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Encryption/DefaultEncrypter.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Exceptions/DecryptException.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Exceptions/DelegationException.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Exceptions/EncryptException.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Extension.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Http/DelegationResponse.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Http/Response.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Laravel/Http/Controllers/DelegatesController.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Laravel/ServiceProvider.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Laravel/config.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Laravel/routes.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/ManifestManager.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Plugin.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Traits/MakesHttpRequests.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/src/Traits/WithAggregator.php
 create mode 100644 vendor/easywechat-composer/easywechat-composer/tests/ManifestManagerTest.php
 create mode 100644 vendor/guzzlehttp/guzzle/.php_cs
 create mode 100644 vendor/guzzlehttp/guzzle/Dockerfile
 create mode 100644 vendor/guzzlehttp/guzzle/phpstan.neon.dist
 create mode 100644 vendor/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php
 create mode 100644 vendor/monolog/monolog/UPGRADE.md
 create mode 100644 vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/Handler.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php
 create mode 100644 vendor/monolog/monolog/src/Monolog/Test/TestCase.php
 create mode 100644 vendor/overtrue/socialite/.github/FUNDING.yml
 create mode 100644 vendor/overtrue/socialite/.gitignore
 create mode 100644 vendor/overtrue/socialite/src/Providers/DouYinProvider.php
 create mode 100644 vendor/overtrue/socialite/src/Providers/OutlookProvider.php
 create mode 100644 vendor/overtrue/socialite/src/Providers/TaobaoProvider.php
 create mode 100644 vendor/overtrue/socialite/tests/UserTest.php
 create mode 100644 vendor/overtrue/wechat/CHANGELOG.md
 create mode 100644 vendor/overtrue/wechat/CONTRIBUTING.md
 create mode 100644 vendor/overtrue/wechat/src/BasicService/Application.php
 create mode 100644 vendor/overtrue/wechat/src/BasicService/ContentSecurity/Client.php
 create mode 100644 vendor/overtrue/wechat/src/BasicService/ContentSecurity/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/BasicService/Jssdk/Client.php
 create mode 100644 vendor/overtrue/wechat/src/BasicService/Jssdk/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/BasicService/Media/Client.php
 create mode 100644 vendor/overtrue/wechat/src/BasicService/Media/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/BasicService/QrCode/Client.php
 create mode 100644 vendor/overtrue/wechat/src/BasicService/QrCode/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/BasicService/Url/Client.php
 create mode 100644 vendor/overtrue/wechat/src/BasicService/Url/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Factory.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/AccessToken.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/BaseClient.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Clauses/Clause.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Config.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Contracts/AccessTokenInterface.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Contracts/Arrayable.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Contracts/EventHandlerInterface.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Contracts/MediaInterface.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Contracts/MessageInterface.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Decorators/FinallyResult.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Decorators/TerminateResult.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Encryptor.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Events/AccessTokenRefreshed.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Events/ApplicationInitialized.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Events/HttpResponseCreated.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Events/ServerGuardResponseCreated.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/BadRequestException.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/DecryptException.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/Exception.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/HttpException.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidArgumentException.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidConfigException.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/RuntimeException.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Exceptions/UnboundServiceException.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Helpers.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Http/Response.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Http/StreamResponse.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Log/LogManager.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Article.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Card.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/DeviceEvent.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/DeviceText.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/File.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Image.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Link.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Location.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Media.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Message.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/MiniProgramPage.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Music.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/News.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/NewsItem.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Raw.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/ShortVideo.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/TaskCard.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Text.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/TextCard.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Transfer.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Video.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Messages/Voice.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Providers/ConfigServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Providers/EventDispatcherServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Providers/ExtensionServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Providers/HttpClientServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Providers/RequestServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/ServerGuard.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/ServiceContainer.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/AES.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/Arr.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/ArrayAccessible.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/Collection.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/File.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/Helpers.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/Str.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Support/XML.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Traits/HasAttributes.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Traits/HasHttpRequests.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Traits/InteractsWithCache.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Traits/Observable.php
 create mode 100644 vendor/overtrue/wechat/src/Kernel/Traits/ResponseCastable.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Application.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Base/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Base/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Certficates/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Certficates/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Kernel/BaseClient.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/EncryptException.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidExtensionException.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidSignException.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Material/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Material/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Media/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Media/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Withdraw/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MicroMerchant/Withdraw/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/AppCode/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/AppCode/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Application.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Auth/AccessToken.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Auth/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Auth/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Base/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Base/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/CustomerService/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/DataCube/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/DataCube/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Encryptor.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Express/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Express/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Mall/CartClient.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Mall/ForwardsMall.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Mall/MediaClient.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Mall/OrderClient.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Mall/ProductClient.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Mall/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/OCR/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/OpenData/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/OpenData/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Plugin/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Plugin/DevClient.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Plugin/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Server/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Soter/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/Soter/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/UniformMessage/Client.php
 create mode 100644 vendor/overtrue/wechat/src/MiniProgram/UniformMessage/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Application.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Auth/AccessToken.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Auth/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/AutoReply/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/AutoReply/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Base/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Base/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/MessageBuilder.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/BoardingPassClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/Card.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/CodeClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/CoinClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/GeneralCardClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/JssdkClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/MeetingTicketClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/MemberCardClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/MovieTicketClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Card/SubMerchantClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Comment/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Comment/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Messenger.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/CustomerService/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/CustomerService/SessionClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/DataCube/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/DataCube/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Device/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Device/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Goods/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Goods/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Material/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Material/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Menu/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Menu/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/OAuth/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/OCR/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/OCR/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/POI/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/POI/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Semantic/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Semantic/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Server/Guard.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Server/Handlers/EchoStrHandler.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Server/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/DeviceClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/GroupClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/MaterialClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/PageClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/RelationClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ShakeAround.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/StatsClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Store/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/Store/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/User/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/User/TagClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/User/UserClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/WiFi/CardClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/WiFi/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/WiFi/DeviceClient.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/WiFi/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OfficialAccount/WiFi/ShopClient.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Application.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Auth/AccessToken.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Auth/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Auth/VerifyTicket.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/Account/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/AggregateServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Auth/AccessToken.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Application.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Auth/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Account/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Application.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/OAuth/ComponentDelegate.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Server/Guard.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Base/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Base/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Component/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Component/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Server/Guard.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Authorized.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Unauthorized.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/UpdateAuthorized.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/VerifyTicketRefreshed.php
 create mode 100644 vendor/overtrue/wechat/src/OpenPlatform/Server/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/Application.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/Auth/AccessToken.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/Auth/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/Corp/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/Corp/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/MiniProgram/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/MiniProgram/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/Provider/Client.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/Provider/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/Server/Guard.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/Server/Handlers/EchoStrHandler.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/Server/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/SuiteAuth/AccessToken.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/SuiteAuth/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/SuiteAuth/SuiteTicket.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/Work/Application.php
 create mode 100644 vendor/overtrue/wechat/src/OpenWork/Work/Auth/AccessToken.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Application.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Base/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Base/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Bill/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Bill/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Coupon/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Coupon/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Jssdk/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Jssdk/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Kernel/BaseClient.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/InvalidSignException.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/SandboxException.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Merchant/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Merchant/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Notify/Handler.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Notify/Paid.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Notify/Refunded.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Notify/Scanned.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Order/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Order/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/ProfitSharing/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/ProfitSharing/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Redpack/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Redpack/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Refund/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Refund/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Reverse/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Reverse/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Sandbox/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Sandbox/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Security/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Security/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Transfer/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Payment/Transfer/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Agent/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Agent/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Application.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Auth/AccessToken.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Auth/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Base/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Base/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Chat/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Chat/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Department/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Department/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/ExternalContact/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Work/ExternalContact/ContactWayClient.php
 create mode 100644 vendor/overtrue/wechat/src/Work/ExternalContact/MessageClient.php
 create mode 100644 vendor/overtrue/wechat/src/Work/ExternalContact/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/ExternalContact/StatisticsClient.php
 create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Image.php
 create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Markdown.php
 create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Message.php
 create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/News.php
 create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/NewsItem.php
 create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Text.php
 create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/Messenger.php
 create mode 100644 vendor/overtrue/wechat/src/Work/GroupRobot/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Invoice/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Invoice/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Jssdk/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Jssdk/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Media/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Media/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Menu/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Menu/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Message/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Message/Messenger.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Message/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/MiniProgram/Application.php
 create mode 100644 vendor/overtrue/wechat/src/Work/MiniProgram/Auth/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Work/OA/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Work/OA/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/OAuth/AccessTokenDelegate.php
 create mode 100644 vendor/overtrue/wechat/src/Work/OAuth/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Server/Guard.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Server/Handlers/EchoStrHandler.php
 create mode 100644 vendor/overtrue/wechat/src/Work/Server/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/User/Client.php
 create mode 100644 vendor/overtrue/wechat/src/Work/User/ServiceProvider.php
 create mode 100644 vendor/overtrue/wechat/src/Work/User/TagClient.php
 create mode 100644 vendor/pimple/pimple/.gitignore
 create mode 100644 vendor/pimple/pimple/ext/pimple/.gitignore
 create mode 100644 vendor/psr/cache/CHANGELOG.md
 create mode 100644 vendor/psr/cache/LICENSE.txt
 create mode 100644 vendor/psr/cache/README.md
 create mode 100644 vendor/psr/cache/composer.json
 create mode 100644 vendor/psr/cache/src/CacheException.php
 create mode 100644 vendor/psr/cache/src/CacheItemInterface.php
 create mode 100644 vendor/psr/cache/src/CacheItemPoolInterface.php
 create mode 100644 vendor/psr/cache/src/InvalidArgumentException.php
 create mode 100644 vendor/psr/container/.gitignore
 create mode 100644 vendor/psr/log/.gitignore
 create mode 100644 vendor/symfony/cache-contracts/.gitignore
 create mode 100644 vendor/symfony/cache-contracts/CacheInterface.php
 create mode 100644 vendor/symfony/cache-contracts/CacheTrait.php
 create mode 100644 vendor/symfony/cache-contracts/CallbackInterface.php
 create mode 100644 vendor/symfony/cache-contracts/ItemInterface.php
 create mode 100644 vendor/symfony/cache-contracts/LICENSE
 create mode 100644 vendor/symfony/cache-contracts/README.md
 create mode 100644 vendor/symfony/cache-contracts/TagAwareCacheInterface.php
 create mode 100644 vendor/symfony/cache-contracts/composer.json
 create mode 100644 vendor/symfony/cache/.gitignore
 create mode 100644 vendor/symfony/cache/Adapter/AbstractAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/AdapterInterface.php
 create mode 100644 vendor/symfony/cache/Adapter/ApcuAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/ArrayAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/ChainAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/DoctrineAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/FilesystemAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/MemcachedAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/NullAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/PdoAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/PhpArrayAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/PhpFilesAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/ProxyAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/Psr16Adapter.php
 create mode 100644 vendor/symfony/cache/Adapter/RedisAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/SimpleCacheAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/TagAwareAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php
 create mode 100644 vendor/symfony/cache/Adapter/TraceableAdapter.php
 create mode 100644 vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php
 create mode 100644 vendor/symfony/cache/CHANGELOG.md
 create mode 100644 vendor/symfony/cache/CacheItem.php
 create mode 100644 vendor/symfony/cache/DataCollector/CacheDataCollector.php
 create mode 100644 vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php
 create mode 100644 vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php
 create mode 100644 vendor/symfony/cache/DependencyInjection/CachePoolPass.php
 create mode 100644 vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php
 create mode 100644 vendor/symfony/cache/DoctrineProvider.php
 create mode 100644 vendor/symfony/cache/Exception/CacheException.php
 create mode 100644 vendor/symfony/cache/Exception/InvalidArgumentException.php
 create mode 100644 vendor/symfony/cache/Exception/LogicException.php
 create mode 100644 vendor/symfony/cache/LICENSE
 create mode 100644 vendor/symfony/cache/LockRegistry.php
 create mode 100644 vendor/symfony/cache/Marshaller/DefaultMarshaller.php
 create mode 100644 vendor/symfony/cache/Marshaller/MarshallerInterface.php
 create mode 100644 vendor/symfony/cache/PruneableInterface.php
 create mode 100644 vendor/symfony/cache/Psr16Cache.php
 create mode 100644 vendor/symfony/cache/README.md
 create mode 100644 vendor/symfony/cache/ResettableInterface.php
 create mode 100644 vendor/symfony/cache/Simple/AbstractCache.php
 create mode 100644 vendor/symfony/cache/Simple/ApcuCache.php
 create mode 100644 vendor/symfony/cache/Simple/ArrayCache.php
 create mode 100644 vendor/symfony/cache/Simple/ChainCache.php
 create mode 100644 vendor/symfony/cache/Simple/DoctrineCache.php
 create mode 100644 vendor/symfony/cache/Simple/FilesystemCache.php
 create mode 100644 vendor/symfony/cache/Simple/MemcachedCache.php
 create mode 100644 vendor/symfony/cache/Simple/NullCache.php
 create mode 100644 vendor/symfony/cache/Simple/PdoCache.php
 create mode 100644 vendor/symfony/cache/Simple/PhpArrayCache.php
 create mode 100644 vendor/symfony/cache/Simple/PhpFilesCache.php
 create mode 100644 vendor/symfony/cache/Simple/Psr6Cache.php
 create mode 100644 vendor/symfony/cache/Simple/RedisCache.php
 create mode 100644 vendor/symfony/cache/Simple/TraceableCache.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/AbstractRedisAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/AdapterTestCase.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/ApcuAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/ArrayAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/ChainAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/DoctrineAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/FilesystemAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/FilesystemTagAwareAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/MaxIdLengthAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/MemcachedAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/NamespacedProxyAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/NullAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/PdoAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/PdoDbalAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/PhpArrayAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/PhpFilesAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/PredisAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/PredisClusterAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/PredisRedisClusterAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/PredisTagAwareAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/PredisTagAwareClusterAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/PredisTagAwareRedisClusterAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/ProxyAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/Psr16AdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/RedisAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/RedisArrayAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/RedisClusterAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/RedisTagAwareAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/RedisTagAwareArrayAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/RedisTagAwareClusterAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/SimpleCacheAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/TagAwareAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/TagAwareAndProxyAdapterIntegrationTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/TraceableAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Adapter/TraceableTagAwareAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/CacheItemTest.php
 create mode 100644 vendor/symfony/cache/Tests/DependencyInjection/CacheCollectorPassTest.php
 create mode 100644 vendor/symfony/cache/Tests/DependencyInjection/CachePoolClearerPassTest.php
 create mode 100644 vendor/symfony/cache/Tests/DependencyInjection/CachePoolPassTest.php
 create mode 100644 vendor/symfony/cache/Tests/DependencyInjection/CachePoolPrunerPassTest.php
 create mode 100644 vendor/symfony/cache/Tests/DoctrineProviderTest.php
 create mode 100644 vendor/symfony/cache/Tests/Fixtures/ArrayCache.php
 create mode 100644 vendor/symfony/cache/Tests/Fixtures/ExternalAdapter.php
 create mode 100644 vendor/symfony/cache/Tests/LockRegistryTest.php
 create mode 100644 vendor/symfony/cache/Tests/Marshaller/DefaultMarshallerTest.php
 create mode 100644 vendor/symfony/cache/Tests/Psr16CacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/AbstractRedisCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/ApcuCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/ArrayCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/CacheTestCase.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/ChainCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/DoctrineCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/FilesystemCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/MemcachedCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/MemcachedCacheTextModeTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/NullCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/PdoCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/PdoDbalCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/PhpArrayCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/PhpArrayCacheWithFallbackTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/PhpArrayCacheWrapper.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/PhpFilesCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/Psr6CacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/Psr6CacheWithAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/Psr6CacheWithoutAdapterTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/RedisArrayCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/RedisCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/RedisClusterCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Simple/TraceableCacheTest.php
 create mode 100644 vendor/symfony/cache/Tests/Traits/PdoPruneableTrait.php
 create mode 100644 vendor/symfony/cache/Tests/Traits/TagAwareTestTrait.php
 create mode 100644 vendor/symfony/cache/Traits/AbstractAdapterTrait.php
 create mode 100644 vendor/symfony/cache/Traits/AbstractTrait.php
 create mode 100644 vendor/symfony/cache/Traits/ApcuTrait.php
 create mode 100644 vendor/symfony/cache/Traits/ArrayTrait.php
 create mode 100644 vendor/symfony/cache/Traits/ContractsTrait.php
 create mode 100644 vendor/symfony/cache/Traits/DoctrineTrait.php
 create mode 100644 vendor/symfony/cache/Traits/FilesystemCommonTrait.php
 create mode 100644 vendor/symfony/cache/Traits/FilesystemTrait.php
 create mode 100644 vendor/symfony/cache/Traits/MemcachedTrait.php
 create mode 100644 vendor/symfony/cache/Traits/PdoTrait.php
 create mode 100644 vendor/symfony/cache/Traits/PhpArrayTrait.php
 create mode 100644 vendor/symfony/cache/Traits/PhpFilesTrait.php
 create mode 100644 vendor/symfony/cache/Traits/ProxyTrait.php
 create mode 100644 vendor/symfony/cache/Traits/RedisClusterProxy.php
 create mode 100644 vendor/symfony/cache/Traits/RedisProxy.php
 create mode 100644 vendor/symfony/cache/Traits/RedisTrait.php
 create mode 100644 vendor/symfony/cache/composer.json
 create mode 100644 vendor/symfony/cache/phpunit.xml.dist
 create mode 100644 vendor/symfony/event-dispatcher-contracts/.gitignore
 create mode 100644 vendor/symfony/event-dispatcher-contracts/Event.php
 create mode 100644 vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php
 create mode 100644 vendor/symfony/event-dispatcher-contracts/LICENSE
 create mode 100644 vendor/symfony/event-dispatcher-contracts/README.md
 create mode 100644 vendor/symfony/event-dispatcher-contracts/composer.json
 create mode 100644 vendor/symfony/event-dispatcher/.gitignore
 create mode 100644 vendor/symfony/event-dispatcher/CHANGELOG.md
 create mode 100644 vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
 create mode 100644 vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php
 create mode 100644 vendor/symfony/event-dispatcher/Debug/WrappedListener.php
 create mode 100644 vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php
 create mode 100644 vendor/symfony/event-dispatcher/Event.php
 create mode 100644 vendor/symfony/event-dispatcher/EventDispatcher.php
 create mode 100644 vendor/symfony/event-dispatcher/EventDispatcherInterface.php
 create mode 100644 vendor/symfony/event-dispatcher/EventSubscriberInterface.php
 create mode 100644 vendor/symfony/event-dispatcher/GenericEvent.php
 create mode 100644 vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php
 create mode 100644 vendor/symfony/event-dispatcher/LICENSE
 create mode 100644 vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php
 create mode 100644 vendor/symfony/event-dispatcher/LegacyEventProxy.php
 create mode 100644 vendor/symfony/event-dispatcher/README.md
 create mode 100644 vendor/symfony/event-dispatcher/Tests/ChildEventDispatcherTest.php
 create mode 100644 vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php
 create mode 100644 vendor/symfony/event-dispatcher/Tests/Debug/WrappedListenerTest.php
 create mode 100644 vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
 create mode 100644 vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php
 create mode 100644 vendor/symfony/event-dispatcher/Tests/EventTest.php
 create mode 100644 vendor/symfony/event-dispatcher/Tests/GenericEventTest.php
 create mode 100644 vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php
 create mode 100644 vendor/symfony/event-dispatcher/Tests/LegacyEventDispatcherProxyTest.php
 create mode 100644 vendor/symfony/event-dispatcher/composer.json
 create mode 100644 vendor/symfony/event-dispatcher/phpunit.xml.dist
 create mode 100644 vendor/symfony/http-foundation/.gitignore
 create mode 100644 vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php
 create mode 100644 vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php
 create mode 100644 vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php
 create mode 100644 vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php
 create mode 100644 vendor/symfony/http-foundation/File/Exception/NoFileException.php
 create mode 100644 vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php
 create mode 100644 vendor/symfony/http-foundation/File/Exception/PartialFileException.php
 create mode 100644 vendor/symfony/http-foundation/HeaderUtils.php
 create mode 100644 vendor/symfony/http-foundation/Session/SessionUtils.php
 create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php
 create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php
 create mode 100644 vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php
 create mode 100644 vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php
 create mode 100644 vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php
 create mode 100644 vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php
 create mode 100644 vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php
 create mode 100644 vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php
 create mode 100644 vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php
 create mode 100644 vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php
 create mode 100644 vendor/symfony/http-foundation/Tests/File/Fixtures/-test
 create mode 100644 vendor/symfony/http-foundation/Tests/HeaderUtilsTest.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.expected
 create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.expected
 create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MigratingSessionHandlerTest.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PredisSessionHandlerTest.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisArraySessionHandlerTest.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Test/Constraint/RequestAttributeValueSameTest.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseCookieValueSameTest.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseHasCookieTest.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseHasHeaderTest.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseHeaderSameTest.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseIsRedirectedTest.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseIsSuccessfulTest.php
 create mode 100644 vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseStatusCodeSameTest.php
 create mode 100644 vendor/symfony/http-foundation/Tests/UrlHelperTest.php
 create mode 100644 vendor/symfony/http-foundation/UrlHelper.php
 create mode 100644 vendor/symfony/mime/.gitignore
 create mode 100644 vendor/symfony/mime/Address.php
 create mode 100644 vendor/symfony/mime/BodyRendererInterface.php
 create mode 100644 vendor/symfony/mime/CHANGELOG.md
 create mode 100644 vendor/symfony/mime/CharacterStream.php
 create mode 100644 vendor/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php
 create mode 100644 vendor/symfony/mime/Email.php
 create mode 100644 vendor/symfony/mime/Encoder/AddressEncoderInterface.php
 create mode 100644 vendor/symfony/mime/Encoder/Base64ContentEncoder.php
 create mode 100644 vendor/symfony/mime/Encoder/Base64Encoder.php
 create mode 100644 vendor/symfony/mime/Encoder/Base64MimeHeaderEncoder.php
 create mode 100644 vendor/symfony/mime/Encoder/ContentEncoderInterface.php
 create mode 100644 vendor/symfony/mime/Encoder/EightBitContentEncoder.php
 create mode 100644 vendor/symfony/mime/Encoder/EncoderInterface.php
 create mode 100644 vendor/symfony/mime/Encoder/IdnAddressEncoder.php
 create mode 100644 vendor/symfony/mime/Encoder/MimeHeaderEncoderInterface.php
 create mode 100644 vendor/symfony/mime/Encoder/QpContentEncoder.php
 create mode 100644 vendor/symfony/mime/Encoder/QpEncoder.php
 create mode 100644 vendor/symfony/mime/Encoder/QpMimeHeaderEncoder.php
 create mode 100644 vendor/symfony/mime/Encoder/Rfc2231Encoder.php
 create mode 100644 vendor/symfony/mime/Exception/AddressEncoderException.php
 create mode 100644 vendor/symfony/mime/Exception/ExceptionInterface.php
 create mode 100644 vendor/symfony/mime/Exception/InvalidArgumentException.php
 create mode 100644 vendor/symfony/mime/Exception/LogicException.php
 create mode 100644 vendor/symfony/mime/Exception/RfcComplianceException.php
 create mode 100644 vendor/symfony/mime/Exception/RuntimeException.php
 create mode 100644 vendor/symfony/mime/FileBinaryMimeTypeGuesser.php
 create mode 100644 vendor/symfony/mime/FileinfoMimeTypeGuesser.php
 create mode 100644 vendor/symfony/mime/Header/AbstractHeader.php
 create mode 100644 vendor/symfony/mime/Header/DateHeader.php
 create mode 100644 vendor/symfony/mime/Header/HeaderInterface.php
 create mode 100644 vendor/symfony/mime/Header/Headers.php
 create mode 100644 vendor/symfony/mime/Header/IdentificationHeader.php
 create mode 100644 vendor/symfony/mime/Header/MailboxHeader.php
 create mode 100644 vendor/symfony/mime/Header/MailboxListHeader.php
 create mode 100644 vendor/symfony/mime/Header/ParameterizedHeader.php
 create mode 100644 vendor/symfony/mime/Header/PathHeader.php
 create mode 100644 vendor/symfony/mime/Header/UnstructuredHeader.php
 create mode 100644 vendor/symfony/mime/LICENSE
 create mode 100644 vendor/symfony/mime/Message.php
 create mode 100644 vendor/symfony/mime/MessageConverter.php
 create mode 100644 vendor/symfony/mime/MimeTypeGuesserInterface.php
 create mode 100644 vendor/symfony/mime/MimeTypes.php
 create mode 100644 vendor/symfony/mime/MimeTypesInterface.php
 create mode 100644 vendor/symfony/mime/NamedAddress.php
 create mode 100644 vendor/symfony/mime/Part/AbstractMultipartPart.php
 create mode 100644 vendor/symfony/mime/Part/AbstractPart.php
 create mode 100644 vendor/symfony/mime/Part/DataPart.php
 create mode 100644 vendor/symfony/mime/Part/MessagePart.php
 create mode 100644 vendor/symfony/mime/Part/Multipart/AlternativePart.php
 create mode 100644 vendor/symfony/mime/Part/Multipart/DigestPart.php
 create mode 100644 vendor/symfony/mime/Part/Multipart/FormDataPart.php
 create mode 100644 vendor/symfony/mime/Part/Multipart/MixedPart.php
 create mode 100644 vendor/symfony/mime/Part/Multipart/RelatedPart.php
 create mode 100644 vendor/symfony/mime/Part/TextPart.php
 create mode 100644 vendor/symfony/mime/README.md
 create mode 100644 vendor/symfony/mime/RawMessage.php
 create mode 100644 vendor/symfony/mime/Resources/bin/update_mime_types.php
 create mode 100644 vendor/symfony/mime/Tests/AbstractMimeTypeGuesserTest.php
 create mode 100644 vendor/symfony/mime/Tests/AddressTest.php
 create mode 100644 vendor/symfony/mime/Tests/CharacterStreamTest.php
 create mode 100644 vendor/symfony/mime/Tests/DependencyInjection/AddMimeTypeGuesserPassTest.php
 create mode 100644 vendor/symfony/mime/Tests/EmailTest.php
 create mode 100644 vendor/symfony/mime/Tests/Encoder/Base64EncoderTest.php
 create mode 100644 vendor/symfony/mime/Tests/Encoder/Base64MimeHeaderEncoderTest.php
 create mode 100644 vendor/symfony/mime/Tests/Encoder/QpEncoderTest.php
 create mode 100644 vendor/symfony/mime/Tests/Encoder/QpMimeHeaderEncoderTest.php
 create mode 100644 vendor/symfony/mime/Tests/Encoder/Rfc2231EncoderTest.php
 create mode 100644 vendor/symfony/mime/Tests/FileBinaryMimeTypeGuesserTest.php
 create mode 100644 vendor/symfony/mime/Tests/FileinfoMimeTypeGuesserTest.php
 create mode 100644 vendor/symfony/mime/Tests/Fixtures/mimetypes/-test
 create mode 100644 vendor/symfony/mime/Tests/Fixtures/mimetypes/.unknownextension
 create mode 100644 vendor/symfony/mime/Tests/Fixtures/mimetypes/directory/.empty
 create mode 100644 vendor/symfony/mime/Tests/Fixtures/mimetypes/other-file.example
 create mode 100644 vendor/symfony/mime/Tests/Fixtures/mimetypes/test
 create mode 100644 vendor/symfony/mime/Tests/Fixtures/mimetypes/test.gif
 create mode 100644 vendor/symfony/mime/Tests/Fixtures/samples/charsets/iso-2022-jp/one.txt
 create mode 100644 vendor/symfony/mime/Tests/Fixtures/samples/charsets/iso-8859-1/one.txt
 create mode 100644 vendor/symfony/mime/Tests/Fixtures/samples/charsets/utf-8/one.txt
 create mode 100644 vendor/symfony/mime/Tests/Fixtures/samples/charsets/utf-8/three.txt
 create mode 100644 vendor/symfony/mime/Tests/Fixtures/samples/charsets/utf-8/two.txt
 create mode 100644 vendor/symfony/mime/Tests/Header/DateHeaderTest.php
 create mode 100644 vendor/symfony/mime/Tests/Header/HeadersTest.php
 create mode 100644 vendor/symfony/mime/Tests/Header/IdentificationHeaderTest.php
 create mode 100644 vendor/symfony/mime/Tests/Header/MailboxHeaderTest.php
 create mode 100644 vendor/symfony/mime/Tests/Header/MailboxListHeaderTest.php
 create mode 100644 vendor/symfony/mime/Tests/Header/ParameterizedHeaderTest.php
 create mode 100644 vendor/symfony/mime/Tests/Header/PathHeaderTest.php
 create mode 100644 vendor/symfony/mime/Tests/Header/UnstructuredHeaderTest.php
 create mode 100644 vendor/symfony/mime/Tests/MessageConverterTest.php
 create mode 100644 vendor/symfony/mime/Tests/MessageTest.php
 create mode 100644 vendor/symfony/mime/Tests/MimeTypesTest.php
 create mode 100644 vendor/symfony/mime/Tests/NamedAddressTest.php
 create mode 100644 vendor/symfony/mime/Tests/Part/DataPartTest.php
 create mode 100644 vendor/symfony/mime/Tests/Part/MessagePartTest.php
 create mode 100644 vendor/symfony/mime/Tests/Part/Multipart/AlternativePartTest.php
 create mode 100644 vendor/symfony/mime/Tests/Part/Multipart/DigestPartTest.php
 create mode 100644 vendor/symfony/mime/Tests/Part/Multipart/FormDataPartTest.php
 create mode 100644 vendor/symfony/mime/Tests/Part/Multipart/MixedPartTest.php
 create mode 100644 vendor/symfony/mime/Tests/Part/Multipart/RelatedPartTest.php
 create mode 100644 vendor/symfony/mime/Tests/Part/TextPartTest.php
 create mode 100644 vendor/symfony/mime/Tests/RawMessageTest.php
 create mode 100644 vendor/symfony/mime/composer.json
 create mode 100644 vendor/symfony/mime/phpunit.xml.dist
 create mode 100644 vendor/symfony/polyfill-intl-idn/Idn.php
 create mode 100644 vendor/symfony/polyfill-intl-idn/LICENSE
 create mode 100644 vendor/symfony/polyfill-intl-idn/README.md
 create mode 100644 vendor/symfony/polyfill-intl-idn/bootstrap.php
 create mode 100644 vendor/symfony/polyfill-intl-idn/composer.json
 create mode 100644 vendor/symfony/polyfill-php72/LICENSE
 create mode 100644 vendor/symfony/polyfill-php72/Php72.php
 create mode 100644 vendor/symfony/polyfill-php72/README.md
 create mode 100644 vendor/symfony/polyfill-php72/bootstrap.php
 create mode 100644 vendor/symfony/polyfill-php72/composer.json
 create mode 100644 vendor/symfony/psr-http-message-bridge/.gitignore
 create mode 100644 vendor/symfony/service-contracts/.gitignore
 create mode 100644 vendor/symfony/service-contracts/LICENSE
 create mode 100644 vendor/symfony/service-contracts/README.md
 create mode 100644 vendor/symfony/service-contracts/ResetInterface.php
 create mode 100644 vendor/symfony/service-contracts/ServiceLocatorTrait.php
 create mode 100644 vendor/symfony/service-contracts/ServiceProviderInterface.php
 create mode 100644 vendor/symfony/service-contracts/ServiceSubscriberInterface.php
 create mode 100644 vendor/symfony/service-contracts/ServiceSubscriberTrait.php
 create mode 100644 vendor/symfony/service-contracts/Test/ServiceLocatorTest.php
 create mode 100644 vendor/symfony/service-contracts/composer.json
 create mode 100644 vendor/symfony/var-exporter/.gitignore
 create mode 100644 vendor/symfony/var-exporter/CHANGELOG.md
 create mode 100644 vendor/symfony/var-exporter/Exception/ClassNotFoundException.php
 create mode 100644 vendor/symfony/var-exporter/Exception/ExceptionInterface.php
 create mode 100644 vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php
 create mode 100644 vendor/symfony/var-exporter/Instantiator.php
 create mode 100644 vendor/symfony/var-exporter/Internal/Exporter.php
 create mode 100644 vendor/symfony/var-exporter/Internal/Hydrator.php
 create mode 100644 vendor/symfony/var-exporter/Internal/Reference.php
 create mode 100644 vendor/symfony/var-exporter/Internal/Registry.php
 create mode 100644 vendor/symfony/var-exporter/Internal/Values.php
 create mode 100644 vendor/symfony/var-exporter/LICENSE
 create mode 100644 vendor/symfony/var-exporter/README.md
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/abstract-parent.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/array-iterator-legacy.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/array-iterator.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/array-object-custom-legacy.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/array-object-custom.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/array-object-legacy.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/array-object.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/bool.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/clone.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/datetime.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/error.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/external-references.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/final-array-iterator-legacy.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/final-array-iterator.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/final-error-legacy.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/final-error.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/final-stdclass.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/foo-serializable.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/hard-references-recursive.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/hard-references.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/incomplete-class.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/lf-ending-string.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/multiline-string.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/partially-indexed-array.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/php74-serializable.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/private-constructor.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/private.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/serializable.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/simple-array.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/spl-object-storage-legacy.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/spl-object-storage.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/var-on-sleep.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/wakeup-refl.php
 create mode 100644 vendor/symfony/var-exporter/Tests/Fixtures/wakeup.php
 create mode 100644 vendor/symfony/var-exporter/Tests/InstantiatorTest.php
 create mode 100644 vendor/symfony/var-exporter/Tests/VarExporterTest.php
 create mode 100644 vendor/symfony/var-exporter/VarExporter.php
 create mode 100644 vendor/symfony/var-exporter/composer.json
 create mode 100644 vendor/symfony/var-exporter/phpunit.xml.dist

diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 2ebbab4..f114020 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -1,656 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="ChangeListManager">
-    <list default="true" id="e93f11c2-a7d9-4d35-8b1c-8dd18e7f5fef" name="Default" comment="">
-      <change type="DELETED" beforePath="$PROJECT_DIR$/application/api/controller/Ems.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/.coveralls.yml" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/.travis.yml" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/UPGRADE.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/build.properties" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/build.xml" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiGetCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/VoidCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/phpunit.xml.dist" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ApcCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ArrayCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/BaseFileCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CacheProviderTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ChainCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CouchbaseCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FileCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FilesystemCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcacheCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcachedCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MongoDBCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/PhpFileCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/PredisCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RedisCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RiakCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/SQLite3CacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/VoidCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/WinCacheCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/XcacheCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ZendDataCacheTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/DoctrineTestCase.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/Doctrine/Tests/TestInit.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/travis/php.ini" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/doctrine/cache/tests/travis/phpunit.travis.xml" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/.php_cs" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/doc/01-usage.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/doc/02-handlers-formatters-processors.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/doc/03-utilities.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/doc/04-extending.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/doc/sockets.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/phpunit.xml.dist" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/InsightOpsHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/RollbarHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/LoggerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/RegistryTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/SignalHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/tests/Monolog/TestCase.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/src/Providers/WeChatOpenPlatformProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/.phplint.yml" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/.styleci.yml" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/BACKERS.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Broadcast/Broadcast.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Broadcast/LICENSE.txt" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Broadcast/MessageBuilder.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Broadcast/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Broadcast/Transformer.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Broadcast/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Card/Card.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Card/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Card/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Card/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Comment/Comment.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Core/AbstractAPI.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Core/AccessToken.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Core/Exception.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Core/Exceptions/FaultException.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Core/Exceptions/HttpException.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Core/Exceptions/InvalidArgumentException.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Core/Exceptions/InvalidConfigException.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Core/Exceptions/RuntimeException.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Core/Exceptions/UnboundServiceException.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Core/Http.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Core/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Core/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Core/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Device/Device.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Device/DeviceHttpException.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Device/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Device/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Device/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Encryption/EncryptionException.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Encryption/Encryptor.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Encryption/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/Application.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/Config.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/BroadcastServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/CardServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/CommentServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/DeviceServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/FundamentalServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/JsServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/MaterialServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/MenuServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/MiniProgramServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/NoticeServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/OAuthServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/OpenPlatformServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/POIServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/PaymentServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/QRCodeServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/ReplyServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/SemanticServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/ServerServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/ShakeAroundServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/StaffServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/StatsServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/UrlServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Foundation/ServiceProviders/UserServiceProvider.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Fundamental/API.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Js/Js.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Js/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Js/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Js/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Material/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Material/Material.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Material/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Material/Temporary.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Material/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Menu/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Menu/Menu.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Menu/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Menu/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/AbstractMessage.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/Article.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/Card.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/DeviceEvent.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/DeviceText.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/Image.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/Link.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/Location.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/Material.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/MiniProgramPage.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/Music.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/News.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/Raw.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/ShortVideo.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/Text.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/Transfer.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/Video.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/Voice.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Message/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/MiniProgram/AccessToken.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/MiniProgram/Core/AbstractMiniProgram.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/MiniProgram/Encryption/Encryptor.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/MiniProgram/Material/Temporary.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/MiniProgram/MiniProgram.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/MiniProgram/Notice/Notice.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/MiniProgram/QRCode/QRCode.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/MiniProgram/Server/Guard.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/MiniProgram/Sns/Sns.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/MiniProgram/Staff/Staff.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/MiniProgram/Stats/Stats.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Notice/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Notice/Notice.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Notice/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Notice/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/OpenPlatform/AccessToken.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/OpenPlatform/Api/AbstractOpenPlatform.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/OpenPlatform/Api/BaseApi.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/OpenPlatform/Api/PreAuthorization.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/OpenPlatform/Authorizer.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/OpenPlatform/AuthorizerAccessToken.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/OpenPlatform/EventHandlers/Authorized.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/OpenPlatform/EventHandlers/ComponentVerifyTicket.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/OpenPlatform/EventHandlers/EventHandler.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/OpenPlatform/EventHandlers/Unauthorized.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/OpenPlatform/EventHandlers/UpdateAuthorized.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/OpenPlatform/Guard.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/OpenPlatform/OpenPlatform.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/OpenPlatform/VerifyTicket.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/POI/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/POI/POI.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/POI/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/POI/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/API.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/CashCoupon/API.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/CashCoupon/CashCoupon.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/LuckyMoney/API.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/LuckyMoney/LuckyMoney.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/Merchant.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/MerchantPay/API.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/MerchantPay/MerchantPay.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/Notify.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/Order.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/Payment.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/RefundNotify.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Payment/helpers.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/QRCode/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/QRCode/QRCode.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/QRCode/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/QRCode/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Reply/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Reply/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Reply/Reply.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Reply/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Semantic/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Semantic/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Semantic/Semantic.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Semantic/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Server/BadRequestException.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Server/Guard.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Server/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Server/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Server/Transformer.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Server/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/ShakeAround/Device.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/ShakeAround/Group.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/ShakeAround/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/ShakeAround/Material.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/ShakeAround/Page.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/ShakeAround/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/ShakeAround/Relation.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/ShakeAround/ShakeAround.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/ShakeAround/Stats.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/ShakeAround/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Staff/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Staff/MessageBuilder.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Staff/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Staff/Session.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Staff/Staff.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Staff/Transformer.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Staff/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Stats/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Stats/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Stats/Stats.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Stats/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Store/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Store/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Store/Store.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Store/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Support/Arr.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Support/Attribute.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Support/Collection.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Support/File.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Support/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Support/Log.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Support/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Support/Str.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Support/Traits/PrefixedContainer.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Support/Url.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Support/XML.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Support/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Url/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Url/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Url/Url.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/Url/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/User/Group.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/User/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/User/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/User/Tag.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/User/User.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/src/User/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/paragonie/random_compat/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/paragonie/random_compat/build-phar.sh" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/paragonie/random_compat/composer.json" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/paragonie/random_compat/lib/random.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/paragonie/random_compat/other/build_phar.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/paragonie/random_compat/psalm-autoload.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/paragonie/random_compat/psalm.xml" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/polyfill-php70/LICENSE" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/polyfill-php70/Php70.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/polyfill-php70/README.md" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/polyfill-php70/Resources/stubs/AssertionError.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/polyfill-php70/Resources/stubs/Error.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/polyfill-php70/Resources/stubs/ParseError.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/polyfill-php70/Resources/stubs/TypeError.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/polyfill-php70/bootstrap.php" afterPath="" />
-      <change type="DELETED" beforePath="$PROJECT_DIR$/vendor/symfony/polyfill-php70/composer.json" afterPath="" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/workspace.xml" afterPath="$PROJECT_DIR$/.idea/workspace.xml" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/application/api/controller/Pay.php" afterPath="$PROJECT_DIR$/application/api/controller/Pay.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/application/api/controller/User.php" afterPath="$PROJECT_DIR$/application/api/controller/User.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/composer.json" afterPath="$PROJECT_DIR$/composer.json" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/public/api.html" afterPath="$PROJECT_DIR$/public/api.html" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/public/assets/js/backend/user/user.js" afterPath="$PROJECT_DIR$/public/assets/js/backend/user/user.js" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/autoload.php" afterPath="$PROJECT_DIR$/vendor/autoload.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/composer/ClassLoader.php" afterPath="$PROJECT_DIR$/vendor/composer/ClassLoader.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/composer/LICENSE" afterPath="$PROJECT_DIR$/vendor/composer/LICENSE" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/composer/autoload_classmap.php" afterPath="$PROJECT_DIR$/vendor/composer/autoload_classmap.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/composer/autoload_files.php" afterPath="$PROJECT_DIR$/vendor/composer/autoload_files.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/composer/autoload_namespaces.php" afterPath="$PROJECT_DIR$/vendor/composer/autoload_namespaces.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/composer/autoload_psr4.php" afterPath="$PROJECT_DIR$/vendor/composer/autoload_psr4.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/composer/autoload_real.php" afterPath="$PROJECT_DIR$/vendor/composer/autoload_real.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/composer/autoload_static.php" afterPath="$PROJECT_DIR$/vendor/composer/autoload_static.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/composer/installed.json" afterPath="$PROJECT_DIR$/vendor/composer/installed.json" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/CHANGELOG.md" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/CHANGELOG.md" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/README.md" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/README.md" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/composer.json" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/composer.json" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Client.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Client.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/ClientInterface.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/ClientInterface.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Exception/ClientException.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Exception/ClientException.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Exception/TransferException.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Exception/TransferException.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/HandlerStack.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/HandlerStack.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Middleware.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Middleware.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Pool.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/Pool.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/RequestOptions.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/RequestOptions.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/RetryMiddleware.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/RetryMiddleware.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/TransferStats.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/TransferStats.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/functions.php" afterPath="$PROJECT_DIR$/vendor/guzzlehttp/guzzle/src/functions.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/CHANGELOG.md" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/CHANGELOG.md" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/LICENSE" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/LICENSE" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/README.md" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/README.md" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/composer.json" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/composer.json" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/ErrorHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/ErrorHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Logger.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Logger.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Registry.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Registry.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/ResettableInterface.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/ResettableInterface.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/SignalHandler.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/SignalHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Utils.php" afterPath="$PROJECT_DIR$/vendor/monolog/monolog/src/Monolog/Utils.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/.travis.yml" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/.travis.yml" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/README.md" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/README.md" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/composer.json" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/composer.json" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/src/AuthorizeFailedException.php" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/src/AuthorizeFailedException.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/src/Config.php" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/src/Config.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/src/Providers/AbstractProvider.php" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/src/Providers/AbstractProvider.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/src/Providers/FacebookProvider.php" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/src/Providers/FacebookProvider.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/src/Providers/GoogleProvider.php" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/src/Providers/GoogleProvider.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/src/Providers/LinkedinProvider.php" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/src/Providers/LinkedinProvider.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/src/Providers/QQProvider.php" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/src/Providers/QQProvider.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/src/Providers/WeChatProvider.php" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/src/Providers/WeChatProvider.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/src/SocialiteManager.php" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/src/SocialiteManager.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/src/User.php" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/src/User.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/tests/OAuthTest.php" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/tests/OAuthTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/tests/Providers/WeWorkProviderTest.php" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/tests/Providers/WeWorkProviderTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/socialite/tests/WechatProviderTest.php" afterPath="$PROJECT_DIR$/vendor/overtrue/socialite/tests/WechatProviderTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/LICENSE" afterPath="$PROJECT_DIR$/vendor/overtrue/wechat/LICENSE" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/README.md" afterPath="$PROJECT_DIR$/vendor/overtrue/wechat/README.md" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/overtrue/wechat/composer.json" afterPath="$PROJECT_DIR$/vendor/overtrue/wechat/composer.json" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/psr/log/Psr/Log/LoggerInterface.php" afterPath="$PROJECT_DIR$/vendor/psr/log/Psr/Log/LoggerInterface.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/psr/log/Psr/Log/LoggerTrait.php" afterPath="$PROJECT_DIR$/vendor/psr/log/Psr/Log/LoggerTrait.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/psr/log/Psr/Log/NullLogger.php" afterPath="$PROJECT_DIR$/vendor/psr/log/Psr/Log/NullLogger.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php" afterPath="$PROJECT_DIR$/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/psr/log/Psr/Log/Test/TestLogger.php" afterPath="$PROJECT_DIR$/vendor/psr/log/Psr/Log/Test/TestLogger.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/psr/log/README.md" afterPath="$PROJECT_DIR$/vendor/psr/log/README.md" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/psr/log/composer.json" afterPath="$PROJECT_DIR$/vendor/psr/log/composer.json" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/AcceptHeader.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/AcceptHeader.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/AcceptHeaderItem.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/AcceptHeaderItem.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/BinaryFileResponse.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/BinaryFileResponse.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/CHANGELOG.md" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/CHANGELOG.md" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Cookie.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Cookie.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/File.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/File.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/UploadedFile.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/File/UploadedFile.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/FileBag.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/FileBag.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/HeaderBag.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/HeaderBag.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/JsonResponse.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/JsonResponse.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/ParameterBag.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/ParameterBag.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/RedirectResponse.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/RedirectResponse.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Request.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Request.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/RequestMatcher.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/RequestMatcher.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/RequestStack.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/RequestStack.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Response.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Response.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/ResponseHeaderBag.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/ResponseHeaderBag.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/ServerBag.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/ServerBag.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Flash/FlashBag.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Flash/FlashBag.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/StreamedResponse.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/StreamedResponse.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/CookieTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/CookieTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/File/FileTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/File/FileTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/FileBagTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/FileBagTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.expected" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.expected" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/HeaderBagTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/HeaderBagTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/IpUtilsTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/IpUtilsTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/JsonResponseTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/JsonResponseTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/RequestTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/RequestTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/ResponseFunctionalTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/ResponseFunctionalTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/ResponseTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/ResponseTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/SessionTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/SessionTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.expected" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.expected" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/http-foundation/composer.json" afterPath="$PROJECT_DIR$/vendor/symfony/http-foundation/composer.json" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring/Mbstring.php" afterPath="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring/Mbstring.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring/bootstrap.php" afterPath="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring/bootstrap.php" />
-      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring/composer.json" afterPath="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring/composer.json" />
-    </list>
+    <list default="true" id="e93f11c2-a7d9-4d35-8b1c-8dd18e7f5fef" name="Default" comment="" />
     <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
     <option name="TRACKING_ENABLED" value="true" />
     <option name="SHOW_DIALOG" value="false" />
@@ -747,8 +98,8 @@
   </component>
   <component name="PhpWorkspaceProjectConfiguration" backward_compatibility_performed="true" />
   <component name="ProjectFrameBounds" extendedState="1">
-    <option name="x" value="4" />
-    <option name="y" value="116" />
+    <option name="x" value="-148" />
+    <option name="y" value="94" />
     <option name="width" value="1883" />
     <option name="height" value="946" />
   </component>
@@ -882,7 +233,7 @@
       <workItem from="1574141466352" duration="745000" />
       <workItem from="1574143209694" duration="3660000" />
       <workItem from="1574146994475" duration="23222000" />
-      <workItem from="1574209189943" duration="8915000" />
+      <workItem from="1574209189943" duration="9675000" />
     </task>
     <task id="LOCAL-00001" summary="调试">
       <created>1574145133364</created>
@@ -895,7 +246,7 @@
     <servers />
   </component>
   <component name="TimeTrackingManager">
-    <option name="totallyTimeSpent" value="80490000" />
+    <option name="totallyTimeSpent" value="81250000" />
   </component>
   <component name="TodoView">
     <todo-panel id="selected-file">
@@ -907,7 +258,8 @@
     </todo-panel>
   </component>
   <component name="ToolWindowManager">
-    <frame x="4" y="116" width="1883" height="946" extended-state="1" />
+    <frame x="-148" y="94" width="1883" height="946" extended-state="1" />
+    <editor active="true" />
     <layout>
       <window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.15743281" sideWeight="0.5" order="1" side_tool="false" content_ui="combo" />
       <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32912987" sideWeight="0.5" order="9" side_tool="false" content_ui="tabs" />
@@ -916,7 +268,7 @@
       <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
       <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
       <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
-      <window_info id="Terminal" active="true" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.33868974" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
+      <window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33868974" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
       <window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="true" content_ui="tabs" />
       <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
       <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..555d04e
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,2277 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "b8343514cebea18e015f5ef5226cb3fe",
+    "packages": [
+        {
+            "name": "easywechat-composer/easywechat-composer",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/mingyoung/easywechat-composer.git",
+                "reference": "bebd2fee768c5e47449d0317067f43bab10fe1eb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/mingyoung/easywechat-composer/zipball/bebd2fee768c5e47449d0317067f43bab10fe1eb",
+                "reference": "bebd2fee768c5e47449d0317067f43bab10fe1eb",
+                "shasum": ""
+            },
+            "require": {
+                "composer-plugin-api": "^1.0",
+                "php": ">=7.0"
+            },
+            "require-dev": {
+                "composer/composer": "^1.0",
+                "phpunit/phpunit": "^6.5 || ^7.0"
+            },
+            "type": "composer-plugin",
+            "extra": {
+                "class": "EasyWeChatComposer\\Plugin"
+            },
+            "autoload": {
+                "psr-4": {
+                    "EasyWeChatComposer\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "张铭阳",
+                    "email": "mingyoungcheung@gmail.com"
+                }
+            ],
+            "description": "The composer plugin for EasyWeChat",
+            "time": "2019-09-19T13:39:05+00:00"
+        },
+        {
+            "name": "endroid/qr-code",
+            "version": "1.9.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/endroid/qr-code.git",
+                "reference": "c9644bec2a9cc9318e98d1437de3c628dcd1ef93"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/endroid/qr-code/zipball/c9644bec2a9cc9318e98d1437de3c628dcd1ef93",
+                "reference": "c9644bec2a9cc9318e98d1437de3c628dcd1ef93",
+                "shasum": ""
+            },
+            "require": {
+                "ext-gd": "*",
+                "php": ">=5.4",
+                "symfony/options-resolver": "^2.3|^3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.0|^5.0",
+                "sensio/framework-extra-bundle": "^3.0",
+                "symfony/browser-kit": "^2.3|^3.0",
+                "symfony/framework-bundle": "^2.3|^3.0",
+                "symfony/http-kernel": "^2.3|^3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Endroid\\QrCode\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jeroen van den Enden",
+                    "email": "info@endroid.nl",
+                    "homepage": "http://endroid.nl/"
+                }
+            ],
+            "description": "Endroid QR Code",
+            "homepage": "https://github.com/endroid/QrCode",
+            "keywords": [
+                "bundle",
+                "code",
+                "endroid",
+                "qr",
+                "qrcode",
+                "symfony"
+            ],
+            "time": "2017-04-08T09:13:59+00:00"
+        },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "6.4.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "0895c932405407fd3a7368b6910c09a24d26db11"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0895c932405407fd3a7368b6910c09a24d26db11",
+                "reference": "0895c932405407fd3a7368b6910c09a24d26db11",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/promises": "^1.0",
+                "guzzlehttp/psr7": "^1.6.1",
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "ext-curl": "*",
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+                "psr/log": "^1.1"
+            },
+            "suggest": {
+                "psr/log": "Required for using the Log middleware"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "6.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Guzzle is a PHP HTTP client library",
+            "homepage": "http://guzzlephp.org/",
+            "keywords": [
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "rest",
+                "web service"
+            ],
+            "time": "2019-10-23T15:58:00+00:00"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "v1.3.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Guzzle promises library",
+            "keywords": [
+                "promise"
+            ],
+            "time": "2016-12-20T10:07:11+00:00"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "1.6.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "239400de7a173fe9901b9ac7c06497751f00727a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a",
+                "reference": "239400de7a173fe9901b9ac7c06497751f00727a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0",
+                "psr/http-message": "~1.0",
+                "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+            },
+            "provide": {
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "ext-zlib": "*",
+                "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
+            },
+            "suggest": {
+                "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.6-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "PSR-7 message implementation that also provides common utility methods",
+            "keywords": [
+                "http",
+                "message",
+                "psr-7",
+                "request",
+                "response",
+                "stream",
+                "uri",
+                "url"
+            ],
+            "time": "2019-07-01T23:21:34+00:00"
+        },
+        {
+            "name": "karsonzhang/fastadmin-addons",
+            "version": "1.1.9",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/karsonzhang/fastadmin-addons.git",
+                "reference": "c539b1c4aa99ea8c453cbd3a838f523136bc118a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/karsonzhang/fastadmin-addons/zipball/c539b1c4aa99ea8c453cbd3a838f523136bc118a",
+                "reference": "c539b1c4aa99ea8c453cbd3a838f523136bc118a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0",
+                "topthink/think-helper": ">=1.0.4",
+                "topthink/think-installer": ">=1.0.10"
+            },
+            "type": "library",
+            "extra": {
+                "think-config": {
+                    "addons": "src/config.php"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "think\\": "src/"
+                },
+                "files": [
+                    "src/common.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "xiaobo.sun",
+                    "email": "xiaobo.sun@qq.com"
+                },
+                {
+                    "name": "karsonzhang",
+                    "email": "karsonzhang@163.com"
+                }
+            ],
+            "description": "addons package for fastadmin",
+            "homepage": "https://github.com/karsonzhang/fastadmin-addons",
+            "time": "2019-06-11T09:05:52+00:00"
+        },
+        {
+            "name": "markbaker/complex",
+            "version": "1.4.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/MarkBaker/PHPComplex.git",
+                "reference": "1ea674a8308baf547cbcbd30c5fcd6d301b7c000"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/1ea674a8308baf547cbcbd30c5fcd6d301b7c000",
+                "reference": "1ea674a8308baf547cbcbd30c5fcd6d301b7c000",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.6.0|^7.0.0"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3",
+                "phpcompatibility/php-compatibility": "^8.0",
+                "phpdocumentor/phpdocumentor": "2.*",
+                "phploc/phploc": "2.*",
+                "phpmd/phpmd": "2.*",
+                "phpunit/phpunit": "^4.8.35|^5.4.0",
+                "sebastian/phpcpd": "2.*",
+                "squizlabs/php_codesniffer": "^3.3.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Complex\\": "classes/src/"
+                },
+                "files": [
+                    "classes/src/functions/abs.php",
+                    "classes/src/functions/acos.php",
+                    "classes/src/functions/acosh.php",
+                    "classes/src/functions/acot.php",
+                    "classes/src/functions/acoth.php",
+                    "classes/src/functions/acsc.php",
+                    "classes/src/functions/acsch.php",
+                    "classes/src/functions/argument.php",
+                    "classes/src/functions/asec.php",
+                    "classes/src/functions/asech.php",
+                    "classes/src/functions/asin.php",
+                    "classes/src/functions/asinh.php",
+                    "classes/src/functions/atan.php",
+                    "classes/src/functions/atanh.php",
+                    "classes/src/functions/conjugate.php",
+                    "classes/src/functions/cos.php",
+                    "classes/src/functions/cosh.php",
+                    "classes/src/functions/cot.php",
+                    "classes/src/functions/coth.php",
+                    "classes/src/functions/csc.php",
+                    "classes/src/functions/csch.php",
+                    "classes/src/functions/exp.php",
+                    "classes/src/functions/inverse.php",
+                    "classes/src/functions/ln.php",
+                    "classes/src/functions/log2.php",
+                    "classes/src/functions/log10.php",
+                    "classes/src/functions/negative.php",
+                    "classes/src/functions/pow.php",
+                    "classes/src/functions/rho.php",
+                    "classes/src/functions/sec.php",
+                    "classes/src/functions/sech.php",
+                    "classes/src/functions/sin.php",
+                    "classes/src/functions/sinh.php",
+                    "classes/src/functions/sqrt.php",
+                    "classes/src/functions/tan.php",
+                    "classes/src/functions/tanh.php",
+                    "classes/src/functions/theta.php",
+                    "classes/src/operations/add.php",
+                    "classes/src/operations/subtract.php",
+                    "classes/src/operations/multiply.php",
+                    "classes/src/operations/divideby.php",
+                    "classes/src/operations/divideinto.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mark Baker",
+                    "email": "mark@lange.demon.co.uk"
+                }
+            ],
+            "description": "PHP Class for working with complex numbers",
+            "homepage": "https://github.com/MarkBaker/PHPComplex",
+            "keywords": [
+                "complex",
+                "mathematics"
+            ],
+            "time": "2018-10-13T23:28:42+00:00"
+        },
+        {
+            "name": "markbaker/matrix",
+            "version": "1.1.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/MarkBaker/PHPMatrix.git",
+                "reference": "6ea97472b5baf12119b4f31f802835b820dd6d64"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/6ea97472b5baf12119b4f31f802835b820dd6d64",
+                "reference": "6ea97472b5baf12119b4f31f802835b820dd6d64",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.6.0|^7.0.0"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3",
+                "phpcompatibility/php-compatibility": "^8.0",
+                "phpdocumentor/phpdocumentor": "2.*",
+                "phploc/phploc": "2.*",
+                "phpmd/phpmd": "2.*",
+                "phpunit/phpunit": "^4.8.35|^5.4.0",
+                "sebastian/phpcpd": "2.*",
+                "squizlabs/php_codesniffer": "^3.3.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Matrix\\": "classes/src/"
+                },
+                "files": [
+                    "classes/src/functions/adjoint.php",
+                    "classes/src/functions/antidiagonal.php",
+                    "classes/src/functions/cofactors.php",
+                    "classes/src/functions/determinant.php",
+                    "classes/src/functions/diagonal.php",
+                    "classes/src/functions/identity.php",
+                    "classes/src/functions/inverse.php",
+                    "classes/src/functions/minors.php",
+                    "classes/src/functions/trace.php",
+                    "classes/src/functions/transpose.php",
+                    "classes/src/operations/add.php",
+                    "classes/src/operations/directsum.php",
+                    "classes/src/operations/subtract.php",
+                    "classes/src/operations/multiply.php",
+                    "classes/src/operations/divideby.php",
+                    "classes/src/operations/divideinto.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mark Baker",
+                    "email": "mark@lange.demon.co.uk"
+                }
+            ],
+            "description": "PHP Class for working with matrices",
+            "homepage": "https://github.com/MarkBaker/PHPMatrix",
+            "keywords": [
+                "mathematics",
+                "matrix",
+                "vector"
+            ],
+            "time": "2018-11-04T22:12:12+00:00"
+        },
+        {
+            "name": "monolog/monolog",
+            "version": "2.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Seldaek/monolog.git",
+                "reference": "f9d56fd2f5533322caccdfcddbb56aedd622ef1c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f9d56fd2f5533322caccdfcddbb56aedd622ef1c",
+                "reference": "f9d56fd2f5533322caccdfcddbb56aedd622ef1c",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2",
+                "psr/log": "^1.0.1"
+            },
+            "provide": {
+                "psr/log-implementation": "1.0.0"
+            },
+            "require-dev": {
+                "aws/aws-sdk-php": "^2.4.9 || ^3.0",
+                "doctrine/couchdb": "~1.0@dev",
+                "elasticsearch/elasticsearch": "^6.0",
+                "graylog2/gelf-php": "^1.4.2",
+                "jakub-onderka/php-parallel-lint": "^0.9",
+                "php-amqplib/php-amqplib": "~2.4",
+                "php-console/php-console": "^3.1.3",
+                "phpspec/prophecy": "^1.6.1",
+                "phpunit/phpunit": "^8.3",
+                "predis/predis": "^1.1",
+                "rollbar/rollbar": "^1.3",
+                "ruflin/elastica": ">=0.90 <3.0",
+                "swiftmailer/swiftmailer": "^5.3|^6.0"
+            },
+            "suggest": {
+                "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+                "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+                "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
+                "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+                "ext-mbstring": "Allow to work properly with unicode symbols",
+                "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
+                "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+                "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
+                "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+                "php-console/php-console": "Allow sending log messages to Google Chrome",
+                "rollbar/rollbar": "Allow sending log messages to Rollbar",
+                "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Monolog\\": "src/Monolog"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "j.boggiano@seld.be",
+                    "homepage": "http://seld.be"
+                }
+            ],
+            "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+            "homepage": "http://github.com/Seldaek/monolog",
+            "keywords": [
+                "log",
+                "logging",
+                "psr-3"
+            ],
+            "time": "2019-11-13T10:27:43+00:00"
+        },
+        {
+            "name": "mtdowling/cron-expression",
+            "version": "v1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/mtdowling/cron-expression.git",
+                "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/9504fa9ea681b586028adaaa0877db4aecf32bad",
+                "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0|~5.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Cron\\": "src/Cron/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
+            "keywords": [
+                "cron",
+                "schedule"
+            ],
+            "time": "2017-01-23T04:29:33+00:00"
+        },
+        {
+            "name": "overtrue/pinyin",
+            "version": "3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/overtrue/pinyin.git",
+                "reference": "3b781d267197b74752daa32814d3a2cf5d140779"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/overtrue/pinyin/zipball/3b781d267197b74752daa32814d3a2cf5d140779",
+                "reference": "3b781d267197b74752daa32814d3a2cf5d140779",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.8"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Overtrue\\Pinyin\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Carlos",
+                    "homepage": "http://github.com/overtrue"
+                }
+            ],
+            "description": "Chinese to pinyin translator.",
+            "homepage": "https://github.com/overtrue/pinyin",
+            "keywords": [
+                "Chinese",
+                "Pinyin",
+                "cn2pinyin"
+            ],
+            "time": "2017-07-10T07:20:01+00:00"
+        },
+        {
+            "name": "overtrue/socialite",
+            "version": "2.0.12",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/overtrue/socialite.git",
+                "reference": "f182ae99bf85e191982b68fcd02e464acaa72888"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/overtrue/socialite/zipball/f182ae99bf85e191982b68fcd02e464acaa72888",
+                "reference": "f182ae99bf85e191982b68fcd02e464acaa72888",
+                "shasum": ""
+            },
+            "require": {
+                "guzzlehttp/guzzle": "~5.0|~6.0",
+                "php": ">=7.0",
+                "symfony/http-foundation": "^2.7|^3.0|^4.0"
+            },
+            "conflict": {
+                "socialiteproviders/weixin": "*"
+            },
+            "require-dev": {
+                "mockery/mockery": "~1.2",
+                "phpunit/phpunit": "~6"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Overtrue\\Socialite\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "overtrue",
+                    "email": "anzhengchao@gmail.com"
+                }
+            ],
+            "description": "A collection of OAuth 2 packages that extracts from laravel/socialite.",
+            "keywords": [
+                "login",
+                "oauth",
+                "qq",
+                "social",
+                "wechat",
+                "weibo"
+            ],
+            "time": "2019-10-24T11:12:17+00:00"
+        },
+        {
+            "name": "overtrue/wechat",
+            "version": "4.2.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/overtrue/wechat.git",
+                "reference": "d8d68636d02d1c51d0edd7aaa854813f6747bf8d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/overtrue/wechat/zipball/d8d68636d02d1c51d0edd7aaa854813f6747bf8d",
+                "reference": "d8d68636d02d1c51d0edd7aaa854813f6747bf8d",
+                "shasum": ""
+            },
+            "require": {
+                "easywechat-composer/easywechat-composer": "^1.1",
+                "ext-fileinfo": "*",
+                "ext-openssl": "*",
+                "ext-simplexml": "*",
+                "guzzlehttp/guzzle": "^6.2",
+                "monolog/monolog": "^1.22 || ^2.0",
+                "overtrue/socialite": "~2.0",
+                "php": ">=7.1",
+                "pimple/pimple": "^3.0",
+                "psr/simple-cache": "^1.0",
+                "symfony/cache": "^3.3 || ^4.3",
+                "symfony/event-dispatcher": "^4.3",
+                "symfony/http-foundation": "^2.7 || ^3.0 || ^4.0",
+                "symfony/psr-http-message-bridge": "^0.3 || ^1.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^2.15",
+                "mikey179/vfsstream": "^1.6",
+                "mockery/mockery": "^1.2.3",
+                "phpstan/phpstan": "^0.11.12",
+                "phpunit/phpunit": "^7.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "EasyWeChat\\": "src/"
+                },
+                "files": [
+                    "src/Kernel/Support/Helpers.php",
+                    "src/Kernel/Helpers.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "overtrue",
+                    "email": "anzhengchao@gmail.com"
+                }
+            ],
+            "description": "微信SDK",
+            "keywords": [
+                "sdk",
+                "wechat",
+                "weixin",
+                "weixin-sdk"
+            ],
+            "time": "2019-11-08T10:32:21+00:00"
+        },
+        {
+            "name": "phpmailer/phpmailer",
+            "version": "v5.2.27",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPMailer/PHPMailer.git",
+                "reference": "dde1db116511aa4956389d75546c5be4c2beb2a6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/dde1db116511aa4956389d75546c5be4c2beb2a6",
+                "reference": "dde1db116511aa4956389d75546c5be4c2beb2a6",
+                "shasum": ""
+            },
+            "require": {
+                "ext-ctype": "*",
+                "php": ">=5.0.0"
+            },
+            "require-dev": {
+                "doctrine/annotations": "1.2.*",
+                "jms/serializer": "0.16.*",
+                "phpdocumentor/phpdocumentor": "2.*",
+                "phpunit/phpunit": "4.8.*",
+                "symfony/debug": "2.8.*",
+                "symfony/filesystem": "2.8.*",
+                "symfony/translation": "2.8.*",
+                "symfony/yaml": "2.8.*",
+                "zendframework/zend-cache": "2.5.1",
+                "zendframework/zend-config": "2.5.1",
+                "zendframework/zend-eventmanager": "2.5.1",
+                "zendframework/zend-filter": "2.5.1",
+                "zendframework/zend-i18n": "2.5.1",
+                "zendframework/zend-json": "2.5.1",
+                "zendframework/zend-math": "2.5.1",
+                "zendframework/zend-serializer": "2.5.*",
+                "zendframework/zend-servicemanager": "2.5.*",
+                "zendframework/zend-stdlib": "2.5.1"
+            },
+            "suggest": {
+                "league/oauth2-google": "Needed for Google XOAUTH2 authentication"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "class.phpmailer.php",
+                    "class.phpmaileroauth.php",
+                    "class.phpmaileroauthgoogle.php",
+                    "class.smtp.php",
+                    "class.pop3.php",
+                    "extras/EasyPeasyICS.php",
+                    "extras/ntlm_sasl_client.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1"
+            ],
+            "authors": [
+                {
+                    "name": "Jim Jagielski",
+                    "email": "jimjag@gmail.com"
+                },
+                {
+                    "name": "Marcus Bointon",
+                    "email": "phpmailer@synchromedia.co.uk"
+                },
+                {
+                    "name": "Andy Prevost",
+                    "email": "codeworxtech@users.sourceforge.net"
+                },
+                {
+                    "name": "Brent R. Matzelle"
+                }
+            ],
+            "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
+            "time": "2018-11-15T22:32:31+00:00"
+        },
+        {
+            "name": "phpoffice/phpspreadsheet",
+            "version": "1.8.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
+                "reference": "352c7002fefe4e4037d02654d853a1b09520946f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/352c7002fefe4e4037d02654d853a1b09520946f",
+                "reference": "352c7002fefe4e4037d02654d853a1b09520946f",
+                "shasum": ""
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-dom": "*",
+                "ext-fileinfo": "*",
+                "ext-gd": "*",
+                "ext-iconv": "*",
+                "ext-libxml": "*",
+                "ext-mbstring": "*",
+                "ext-simplexml": "*",
+                "ext-xml": "*",
+                "ext-xmlreader": "*",
+                "ext-xmlwriter": "*",
+                "ext-zip": "*",
+                "ext-zlib": "*",
+                "markbaker/complex": "^1.4",
+                "markbaker/matrix": "^1.1",
+                "php": "^5.6|^7.0",
+                "psr/simple-cache": "^1.0"
+            },
+            "require-dev": {
+                "doctrine/instantiator": "^1.0.0",
+                "dompdf/dompdf": "^0.8.0",
+                "friendsofphp/php-cs-fixer": "@stable",
+                "jpgraph/jpgraph": "^4.0",
+                "mpdf/mpdf": "^7.0.0",
+                "phpcompatibility/php-compatibility": "^8.0",
+                "phpunit/phpunit": "^5.7",
+                "squizlabs/php_codesniffer": "^3.3",
+                "tecnickcom/tcpdf": "^6.2"
+            },
+            "suggest": {
+                "dompdf/dompdf": "Option for rendering PDF with PDF Writer",
+                "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
+                "mpdf/mpdf": "Option for rendering PDF with PDF Writer",
+                "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "Erik Tilt"
+                },
+                {
+                    "name": "Adrien Crivelli"
+                },
+                {
+                    "name": "Maarten Balliauw",
+                    "homepage": "https://blog.maartenballiauw.be"
+                },
+                {
+                    "name": "Mark Baker",
+                    "homepage": "https://markbakeruk.net"
+                },
+                {
+                    "name": "Franck Lefevre",
+                    "homepage": "https://rootslabs.net"
+                }
+            ],
+            "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
+            "homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
+            "keywords": [
+                "OpenXML",
+                "excel",
+                "gnumeric",
+                "ods",
+                "php",
+                "spreadsheet",
+                "xls",
+                "xlsx"
+            ],
+            "time": "2019-07-01T20:46:51+00:00"
+        },
+        {
+            "name": "pimple/pimple",
+            "version": "v3.2.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/silexphp/Pimple.git",
+                "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32",
+                "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0",
+                "psr/container": "^1.0"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "^3.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Pimple": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                }
+            ],
+            "description": "Pimple, a simple Dependency Injection Container",
+            "homepage": "http://pimple.sensiolabs.org",
+            "keywords": [
+                "container",
+                "dependency injection"
+            ],
+            "time": "2018-01-21T07:42:36+00:00"
+        },
+        {
+            "name": "psr/cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/cache.git",
+                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
+                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Cache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for caching libraries",
+            "keywords": [
+                "cache",
+                "psr",
+                "psr-6"
+            ],
+            "time": "2016-08-06T20:24:11+00:00"
+        },
+        {
+            "name": "psr/container",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/container.git",
+                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Container\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common Container Interface (PHP FIG PSR-11)",
+            "homepage": "https://github.com/php-fig/container",
+            "keywords": [
+                "PSR-11",
+                "container",
+                "container-interface",
+                "container-interop",
+                "psr"
+            ],
+            "time": "2017-02-14T16:28:37+00:00"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "time": "2016-08-06T14:39:51+00:00"
+        },
+        {
+            "name": "psr/log",
+            "version": "1.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/log.git",
+                "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801",
+                "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Log\\": "Psr/Log/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for logging libraries",
+            "homepage": "https://github.com/php-fig/log",
+            "keywords": [
+                "log",
+                "psr",
+                "psr-3"
+            ],
+            "time": "2019-11-01T11:05:21+00:00"
+        },
+        {
+            "name": "psr/simple-cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/simple-cache.git",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\SimpleCache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for simple caching",
+            "keywords": [
+                "cache",
+                "caching",
+                "psr",
+                "psr-16",
+                "simple-cache"
+            ],
+            "time": "2017-10-23T01:57:42+00:00"
+        },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "ralph.khattar@gmail.com"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "time": "2019-03-08T08:55:37+00:00"
+        },
+        {
+            "name": "symfony/cache",
+            "version": "v4.3.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/cache.git",
+                "reference": "83dca34362a0aba2b93aa1226dac6ef7cfea1262"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/cache/zipball/83dca34362a0aba2b93aa1226dac6ef7cfea1262",
+                "reference": "83dca34362a0aba2b93aa1226dac6ef7cfea1262",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "psr/cache": "~1.0",
+                "psr/log": "~1.0",
+                "symfony/cache-contracts": "^1.1",
+                "symfony/service-contracts": "^1.1",
+                "symfony/var-exporter": "^4.2"
+            },
+            "conflict": {
+                "doctrine/dbal": "<2.5",
+                "symfony/dependency-injection": "<3.4",
+                "symfony/var-dumper": "<3.4"
+            },
+            "provide": {
+                "psr/cache-implementation": "1.0",
+                "psr/simple-cache-implementation": "1.0",
+                "symfony/cache-implementation": "1.0"
+            },
+            "require-dev": {
+                "cache/integration-tests": "dev-master",
+                "doctrine/cache": "~1.6",
+                "doctrine/dbal": "~2.5",
+                "predis/predis": "~1.1",
+                "psr/simple-cache": "^1.0",
+                "symfony/config": "~4.2",
+                "symfony/dependency-injection": "~3.4|~4.1",
+                "symfony/var-dumper": "^4.1.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Cache\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Cache component with PSR-6, PSR-16, and tags",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "caching",
+                "psr6"
+            ],
+            "time": "2019-11-12T13:07:20+00:00"
+        },
+        {
+            "name": "symfony/cache-contracts",
+            "version": "v1.1.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/cache-contracts.git",
+                "reference": "af50d14ada9e4e82cfabfabdc502d144f89be0a1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/af50d14ada9e4e82cfabfabdc502d144f89be0a1",
+                "reference": "af50d14ada9e4e82cfabfabdc502d144f89be0a1",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "psr/cache": "^1.0"
+            },
+            "suggest": {
+                "symfony/cache-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Cache\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to caching",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "time": "2019-10-04T21:43:27+00:00"
+        },
+        {
+            "name": "symfony/event-dispatcher",
+            "version": "v4.3.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/event-dispatcher.git",
+                "reference": "0df002fd4f500392eabd243c2947061a50937287"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0df002fd4f500392eabd243c2947061a50937287",
+                "reference": "0df002fd4f500392eabd243c2947061a50937287",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "symfony/event-dispatcher-contracts": "^1.1"
+            },
+            "conflict": {
+                "symfony/dependency-injection": "<3.4"
+            },
+            "provide": {
+                "psr/event-dispatcher-implementation": "1.0",
+                "symfony/event-dispatcher-implementation": "1.1"
+            },
+            "require-dev": {
+                "psr/log": "~1.0",
+                "symfony/config": "~3.4|~4.0",
+                "symfony/dependency-injection": "~3.4|~4.0",
+                "symfony/expression-language": "~3.4|~4.0",
+                "symfony/http-foundation": "^3.4|^4.0",
+                "symfony/service-contracts": "^1.1",
+                "symfony/stopwatch": "~3.4|~4.0"
+            },
+            "suggest": {
+                "symfony/dependency-injection": "",
+                "symfony/http-kernel": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\EventDispatcher\\": ""
+                },
+                "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 EventDispatcher Component",
+            "homepage": "https://symfony.com",
+            "time": "2019-11-03T09:04:05+00:00"
+        },
+        {
+            "name": "symfony/event-dispatcher-contracts",
+            "version": "v1.1.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/event-dispatcher-contracts.git",
+                "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c43ab685673fb6c8d84220c77897b1d6cdbe1d18",
+                "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3"
+            },
+            "suggest": {
+                "psr/event-dispatcher": "",
+                "symfony/event-dispatcher-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\EventDispatcher\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to dispatching event",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "time": "2019-09-17T09:54:03+00:00"
+        },
+        {
+            "name": "symfony/http-foundation",
+            "version": "v4.3.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/http-foundation.git",
+                "reference": "cabe67275034e173350e158f3b1803d023880227"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/cabe67275034e173350e158f3b1803d023880227",
+                "reference": "cabe67275034e173350e158f3b1803d023880227",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "symfony/mime": "^4.3",
+                "symfony/polyfill-mbstring": "~1.1"
+            },
+            "require-dev": {
+                "predis/predis": "~1.0",
+                "symfony/expression-language": "~3.4|~4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\HttpFoundation\\": ""
+                },
+                "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 HttpFoundation Component",
+            "homepage": "https://symfony.com",
+            "time": "2019-11-12T13:07:20+00:00"
+        },
+        {
+            "name": "symfony/mime",
+            "version": "v4.3.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/mime.git",
+                "reference": "22aecf6b11638ef378fab25d6c5a2da8a31a1448"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/mime/zipball/22aecf6b11638ef378fab25d6c5a2da8a31a1448",
+                "reference": "22aecf6b11638ef378fab25d6c5a2da8a31a1448",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "symfony/polyfill-intl-idn": "^1.10",
+                "symfony/polyfill-mbstring": "^1.0"
+            },
+            "require-dev": {
+                "egulias/email-validator": "^2.1.10",
+                "symfony/dependency-injection": "~3.4|^4.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Mime\\": ""
+                },
+                "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": "A library to manipulate MIME messages",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "mime",
+                "mime-type"
+            ],
+            "time": "2019-11-12T13:10:02+00:00"
+        },
+        {
+            "name": "symfony/options-resolver",
+            "version": "v3.4.29",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/options-resolver.git",
+                "reference": "ed3b397f9c07c8ca388b2a1ef744403b4d4ecc44"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/options-resolver/zipball/ed3b397f9c07c8ca388b2a1ef744403b4d4ecc44",
+                "reference": "ed3b397f9c07c8ca388b2a1ef744403b4d4ecc44",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.5.9|>=7.0.8"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.4-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\OptionsResolver\\": ""
+                },
+                "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 OptionsResolver Component",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "config",
+                "configuration",
+                "options"
+            ],
+            "time": "2019-04-10T16:00:48+00:00"
+        },
+        {
+            "name": "symfony/polyfill-intl-idn",
+            "version": "v1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-idn.git",
+                "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6af626ae6fa37d396dc90a399c0ff08e5cfc45b2",
+                "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "symfony/polyfill-mbstring": "^1.3",
+                "symfony/polyfill-php72": "^1.9"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.12-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Idn\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Laurent Bassin",
+                    "email": "laurent@bassin.info"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "idn",
+                "intl",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2019-08-06T08:03:45+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.12-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2019-08-06T08:03:45+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php72",
+            "version": "v1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php72.git",
+                "reference": "04ce3335667451138df4307d6a9b61565560199e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/04ce3335667451138df4307d6a9b61565560199e",
+                "reference": "04ce3335667451138df4307d6a9b61565560199e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.12-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php72\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2019-08-06T08:03:45+00:00"
+        },
+        {
+            "name": "symfony/psr-http-message-bridge",
+            "version": "v1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/psr-http-message-bridge.git",
+                "reference": "9ab9d71f97d5c7d35a121a7fb69f74fee95cd0ad"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/9ab9d71f97d5c7d35a121a7fb69f74fee95cd0ad",
+                "reference": "9ab9d71f97d5c7d35a121a7fb69f74fee95cd0ad",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1",
+                "psr/http-message": "^1.0",
+                "symfony/http-foundation": "^3.4 || ^4.0"
+            },
+            "require-dev": {
+                "nyholm/psr7": "^1.1",
+                "symfony/phpunit-bridge": "^3.4.20 || ^4.0",
+                "zendframework/zend-diactoros": "^1.4.1 || ^2.0"
+            },
+            "suggest": {
+                "nyholm/psr7": "For a super lightweight PSR-7/17 implementation"
+            },
+            "type": "symfony-bridge",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Bridge\\PsrHttpMessage\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Symfony Community",
+                    "homepage": "http://symfony.com/contributors"
+                },
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                }
+            ],
+            "description": "PSR HTTP message bridge",
+            "homepage": "http://symfony.com",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr-17",
+                "psr-7"
+            ],
+            "time": "2019-03-11T18:22:33+00:00"
+        },
+        {
+            "name": "symfony/service-contracts",
+            "version": "v1.1.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/service-contracts.git",
+                "reference": "ffc7f5692092df31515df2a5ecf3b7302b3ddacf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ffc7f5692092df31515df2a5ecf3b7302b3ddacf",
+                "reference": "ffc7f5692092df31515df2a5ecf3b7302b3ddacf",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "psr/container": "^1.0"
+            },
+            "suggest": {
+                "symfony/service-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Service\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to writing services",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "time": "2019-10-14T12:27:06+00:00"
+        },
+        {
+            "name": "symfony/var-exporter",
+            "version": "v4.3.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/var-exporter.git",
+                "reference": "097aa4c02954dabe9d508229be86213723973ac0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/var-exporter/zipball/097aa4c02954dabe9d508229be86213723973ac0",
+                "reference": "097aa4c02954dabe9d508229be86213723973ac0",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3"
+            },
+            "require-dev": {
+                "symfony/var-dumper": "^4.1.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\VarExporter\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "clone",
+                "construct",
+                "export",
+                "hydrate",
+                "instantiate",
+                "serialize"
+            ],
+            "time": "2019-11-11T12:48:54+00:00"
+        },
+        {
+            "name": "topthink/framework",
+            "version": "v5.0.24",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/framework.git",
+                "reference": "c255c22b2f5fa30f320ecf6c1d29f7740eb3e8be"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/framework/zipball/c255c22b2f5fa30f320ecf6c1d29f7740eb3e8be",
+                "reference": "c255c22b2f5fa30f320ecf6c1d29f7740eb3e8be",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0",
+                "topthink/think-installer": "~1.0"
+            },
+            "require-dev": {
+                "johnkary/phpunit-speedtrap": "^1.0",
+                "mikey179/vfsstream": "~1.6",
+                "phpdocumentor/reflection-docblock": "^2.0",
+                "phploc/phploc": "2.*",
+                "phpunit/phpunit": "4.8.*",
+                "sebastian/phpcpd": "2.*"
+            },
+            "type": "think-framework",
+            "autoload": {
+                "psr-4": {
+                    "think\\": "library/think"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "the new thinkphp framework",
+            "homepage": "http://thinkphp.cn/",
+            "keywords": [
+                "framework",
+                "orm",
+                "thinkphp"
+            ],
+            "time": "2019-01-11T08:04:58+00:00"
+        },
+        {
+            "name": "topthink/think-captcha",
+            "version": "v1.0.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-captcha.git",
+                "reference": "1d64363c814c92f6086c4fa5e3223fe7e23db09d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-captcha/zipball/1d64363c814c92f6086c4fa5e3223fe7e23db09d",
+                "reference": "1d64363c814c92f6086c4fa5e3223fe7e23db09d",
+                "shasum": ""
+            },
+            "require": {
+                "topthink/framework": "~5.0.0",
+                "topthink/think-installer": ">=1.0.10"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "think\\captcha\\": "src/"
+                },
+                "files": [
+                    "src/helper.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "captcha package for thinkphp5",
+            "time": "2019-01-28T04:48:36+00:00"
+        },
+        {
+            "name": "topthink/think-helper",
+            "version": "v3.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-helper.git",
+                "reference": "8ba5f66e68106369fcc3211e7d2dbaf7bc9ce455"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-helper/zipball/8ba5f66e68106369fcc3211e7d2dbaf7bc9ce455",
+                "reference": "8ba5f66e68106369fcc3211e7d2dbaf7bc9ce455",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "think\\helper\\": "src"
+                },
+                "files": [
+                    "src/helper.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP5 Helper Package",
+            "time": "2019-03-14T09:28:59+00:00"
+        },
+        {
+            "name": "topthink/think-installer",
+            "version": "v1.0.12",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-installer.git",
+                "reference": "1be326e68f63de4e95977ed50f46ae75f017556d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-installer/zipball/1be326e68f63de4e95977ed50f46ae75f017556d",
+                "reference": "1be326e68f63de4e95977ed50f46ae75f017556d",
+                "shasum": ""
+            },
+            "require": {
+                "composer-plugin-api": "^1.0"
+            },
+            "require-dev": {
+                "composer/composer": "1.0.*@dev"
+            },
+            "type": "composer-plugin",
+            "extra": {
+                "class": "think\\composer\\Plugin"
+            },
+            "autoload": {
+                "psr-4": {
+                    "think\\composer\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "time": "2017-05-27T06:58:09+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=5.6.0"
+    },
+    "platform-dev": []
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/.gitignore b/vendor/easywechat-composer/easywechat-composer/.gitignore
new file mode 100644
index 0000000..c7a0a65
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/.gitignore
@@ -0,0 +1,5 @@
+.idea/
+/vendor
+composer.lock
+extensions.php
+.php_cs.cache
diff --git a/vendor/easywechat-composer/easywechat-composer/.php_cs b/vendor/easywechat-composer/easywechat-composer/.php_cs
new file mode 100644
index 0000000..d256932
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/.php_cs
@@ -0,0 +1,29 @@
+<?php
+
+$header = <<<EOF
+This file is part of the EasyWeChatComposer.
+
+(c) 张铭阳 <mingyoungcheung@gmail.com>
+
+This source file is subject to the MIT license that is bundled
+with this source code in the file LICENSE.
+EOF;
+
+return PhpCsFixer\Config::create()
+    ->setRiskyAllowed(true)
+    ->setRules([
+        '@Symfony' => true,
+        'header_comment' => ['header' => $header],
+        'declare_strict_types' => true,
+        'ordered_imports' => true,
+        'strict_comparison' => true,
+        'no_empty_comment' => false,
+        'yoda_style' => false,
+    ])
+    ->setFinder(
+        PhpCsFixer\Finder::create()
+            ->exclude('vendor')
+            ->notPath('src/Laravel/config.php', 'src/Laravel/routes.php')
+            ->in(__DIR__)
+    )
+;
diff --git a/vendor/easywechat-composer/easywechat-composer/.travis.yml b/vendor/easywechat-composer/easywechat-composer/.travis.yml
new file mode 100644
index 0000000..e819807
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/.travis.yml
@@ -0,0 +1,12 @@
+language: php
+
+php:
+  - 7.0
+  - 7.1
+  - 7.2
+  - 7.3
+
+install:
+  - travis_retry composer install --no-interaction --no-suggest
+
+script: ./vendor/bin/phpunit
diff --git a/vendor/easywechat-composer/easywechat-composer/LICENSE b/vendor/easywechat-composer/easywechat-composer/LICENSE
new file mode 100644
index 0000000..3559904
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 张铭阳
+
+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.
diff --git a/vendor/easywechat-composer/easywechat-composer/README.md b/vendor/easywechat-composer/easywechat-composer/README.md
new file mode 100644
index 0000000..a08c1be
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/README.md
@@ -0,0 +1,55 @@
+<p align="center">
+    <h1 align="center">EasyWeChat Composer Plugin</h1>
+</p>
+
+<p align="center">
+    <a href="https://travis-ci.org/mingyoung/easywechat-composer"><img src="https://travis-ci.org/mingyoung/easywechat-composer.svg" alt="Build Status"></a>
+    <a href="https://scrutinizer-ci.com/g/mingyoung/easywechat-composer/?branch=master"><img src="https://scrutinizer-ci.com/g/mingyoung/easywechat-composer/badges/quality-score.png?b=master" alt="Scrutinizer Code Quality"></a>
+    <a href="https://packagist.org/packages/easywechat-composer/easywechat-composer"><img src="https://poser.pugx.org/easywechat-composer/easywechat-composer/v/stable.svg" alt="Latest Stable Version"></a>
+    <a href="https://packagist.org/packages/easywechat-composer/easywechat-composer"><img src="https://poser.pugx.org/easywechat-composer/easywechat-composer/d/total.svg" alt="Total Downloads"></a>
+    <a href="https://packagist.org/packages/easywechat-composer/easywechat-composer"><img src="https://poser.pugx.org/easywechat-composer/easywechat-composer/license.svg" alt="License"></a>
+</p>
+
+Usage
+---
+
+Set the `type` to be `easywechat-extension` in your package composer.json file:
+
+```json
+{
+    "name": "your/package",
+    "type": "easywechat-extension"
+}
+```
+
+Specify server observer classes in the extra section:
+
+```json
+{
+    "name": "your/package",
+    "type": "easywechat-extension",
+    "extra": {
+        "observers": [
+            "Acme\\Observers\\Handler"
+        ]
+    }
+}
+```
+
+Examples
+---
+* [easywechat-composer/open-platform-testcase](https://github.com/mingyoung/open-platform-testcase)
+
+Server Delegation
+---
+
+> 目前仅支持 Laravel
+
+1. 在 `config/app.php` 中添加 `EasyWeChatComposer\Laravel\ServiceProvider::class`
+
+2. 在**本地项目**的 `.env` 文件中添加如下配置:
+
+```
+EASYWECHAT_DELEGATION=true # false 则不启用
+EASYWECHAT_DELEGATION_HOST=https://example.com # 线上域名
+```
diff --git a/vendor/easywechat-composer/easywechat-composer/composer.json b/vendor/easywechat-composer/easywechat-composer/composer.json
new file mode 100644
index 0000000..032e0d8
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/composer.json
@@ -0,0 +1,35 @@
+{
+    "name": "easywechat-composer/easywechat-composer",
+    "description": "The composer plugin for EasyWeChat",
+    "type": "composer-plugin",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "张铭阳",
+            "email": "mingyoungcheung@gmail.com"
+        }
+    ],
+    "require": {
+        "php": ">=7.0",
+        "composer-plugin-api": "^1.0"
+    },
+    "require-dev": {
+        "composer/composer": "^1.0",
+        "phpunit/phpunit": "^6.5 || ^7.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "EasyWeChatComposer\\": "src/"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "EasyWeChatComposer\\Tests\\": "tests/"
+        }
+    },
+    "extra": {
+        "class": "EasyWeChatComposer\\Plugin"
+    },
+    "minimum-stability": "dev",
+    "prefer-stable": true
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/phpunit.xml b/vendor/easywechat-composer/easywechat-composer/phpunit.xml
new file mode 100644
index 0000000..361d92c
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/phpunit.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/7.0/phpunit.xsd"
+         bootstrap="vendor/autoload.php"
+         colors="true"
+         forceCoversAnnotation="true"
+         beStrictAboutCoversAnnotation="true"
+         beStrictAboutOutputDuringTests="true"
+         beStrictAboutTodoAnnotatedTests="true"
+         verbose="true">
+    <testsuite name="EasyWeChatComposer Test">
+        <directory suffix="Test.php">tests</directory>
+    </testsuite>
+
+    <filter>
+        <whitelist processUncoveredFilesFromWhitelist="true">
+            <directory suffix=".php">src</directory>
+        </whitelist>
+    </filter>
+</phpunit>
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Commands/ExtensionsCommand.php b/vendor/easywechat-composer/easywechat-composer/src/Commands/ExtensionsCommand.php
new file mode 100644
index 0000000..bc0155e
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Commands/ExtensionsCommand.php
@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Commands;
+
+use Composer\Command\BaseCommand;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ExtensionsCommand extends BaseCommand
+{
+    /**
+     * Configures the current command.
+     */
+    protected function configure()
+    {
+        $this->setName('easywechat:extensions')
+            ->setDescription('Lists all installed extensions.');
+    }
+
+    /**
+     * Executes the current command.
+     *
+     * @param InputInterface  $input
+     * @param OutputInterface $output
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $extensions = require __DIR__.'/../../extensions.php';
+
+        if (empty($extensions) || !is_array($extensions)) {
+            return $output->writeln('<info>No extension installed.</info>');
+        }
+
+        $table = new Table($output);
+        $table->setHeaders(['Name', 'Observers'])
+            ->setRows(
+                array_map([$this, 'getRows'], array_keys($extensions), $extensions)
+            )->render();
+    }
+
+    /**
+     * @param string $name
+     * @param array  $extension
+     *
+     * @return array
+     */
+    protected function getRows($name, $extension)
+    {
+        return [$name, implode("\n", $extension['observers'] ?? [])];
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Commands/Provider.php b/vendor/easywechat-composer/easywechat-composer/src/Commands/Provider.php
new file mode 100644
index 0000000..928f096
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Commands/Provider.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Commands;
+
+use Composer\Plugin\Capability\CommandProvider;
+
+class Provider implements CommandProvider
+{
+    /**
+     * Retrieves an array of commands.
+     *
+     * @return \Composer\Command\BaseCommand[]
+     */
+    public function getCommands()
+    {
+        return [
+            new ExtensionsCommand(),
+        ];
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Contracts/Encrypter.php b/vendor/easywechat-composer/easywechat-composer/src/Contracts/Encrypter.php
new file mode 100644
index 0000000..af8b8d1
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Contracts/Encrypter.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Contracts;
+
+interface Encrypter
+{
+    /**
+     * Encrypt the given value.
+     *
+     * @param string $value
+     *
+     * @return string
+     */
+    public function encrypt($value);
+
+    /**
+     * Decrypt the given value.
+     *
+     * @param string $payload
+     *
+     * @return string
+     */
+    public function decrypt($payload);
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationOptions.php b/vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationOptions.php
new file mode 100644
index 0000000..a333261
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationOptions.php
@@ -0,0 +1,80 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Delegation;
+
+use EasyWeChatComposer\EasyWeChat;
+
+class DelegationOptions
+{
+    /**
+     * @var array
+     */
+    protected $config = [
+        'enabled' => false,
+    ];
+
+    /**
+     * @return $this
+     */
+    public function enable()
+    {
+        $this->config['enabled'] = true;
+
+        return $this;
+    }
+
+    /**
+     * @return $this
+     */
+    public function disable()
+    {
+        $this->config['enabled'] = false;
+
+        return $this;
+    }
+
+    /**
+     * @param bool $ability
+     *
+     * @return $this
+     */
+    public function ability($ability)
+    {
+        $this->config['enabled'] = (bool) $ability;
+
+        return $this;
+    }
+
+    /**
+     * @param string $host
+     *
+     * @return $this
+     */
+    public function toHost($host)
+    {
+        $this->config['host'] = $host;
+
+        return $this;
+    }
+
+    /**
+     * Destructor.
+     */
+    public function __destruct()
+    {
+        EasyWeChat::mergeConfig([
+            'delegation' => $this->config,
+        ]);
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationTo.php b/vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationTo.php
new file mode 100644
index 0000000..2e9e6db
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Delegation/DelegationTo.php
@@ -0,0 +1,83 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Delegation;
+
+use EasyWeChatComposer\Traits\MakesHttpRequests;
+
+class DelegationTo
+{
+    use MakesHttpRequests;
+
+    /**
+     * @var \EasyWeChat\Kernel\ServiceContainer
+     */
+    protected $app;
+
+    /**
+     * @var array
+     */
+    protected $identifiers = [];
+
+    /**
+     * @param \EasyWeChat\Kernel\ServiceContainer $app
+     * @param string                              $identifier
+     */
+    public function __construct($app, $identifier)
+    {
+        $this->app = $app;
+
+        $this->push($identifier);
+    }
+
+    /**
+     * @param string $identifier
+     */
+    public function push($identifier)
+    {
+        $this->identifiers[] = $identifier;
+    }
+
+    /**
+     * @param string $identifier
+     *
+     * @return $this
+     */
+    public function __get($identifier)
+    {
+        $this->push($identifier);
+
+        return $this;
+    }
+
+    /**
+     * @param string $method
+     * @param array  $arguments
+     *
+     * @return mixed
+     */
+    public function __call($method, $arguments)
+    {
+        $config = array_intersect_key($this->app->getConfig(), array_flip(['app_id', 'secret', 'token', 'aes_key', 'response_type', 'component_app_id', 'refresh_token']));
+
+        $data = [
+            'config' => $config,
+            'application' => get_class($this->app),
+            'identifiers' => $this->identifiers,
+            'method' => $method,
+            'arguments' => $arguments,
+        ];
+
+        return $this->request('easywechat-composer/delegate', $data);
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Delegation/Hydrate.php b/vendor/easywechat-composer/easywechat-composer/src/Delegation/Hydrate.php
new file mode 100644
index 0000000..b83bbe9
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Delegation/Hydrate.php
@@ -0,0 +1,83 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Delegation;
+
+use EasyWeChat;
+use EasyWeChatComposer\Http\DelegationResponse;
+
+class Hydrate
+{
+    /**
+     * @var array
+     */
+    protected $attributes;
+
+    /**
+     * @param array $attributes
+     */
+    public function __construct(array $attributes)
+    {
+        $this->attributes = $attributes;
+    }
+
+    /**
+     * @return array
+     */
+    public function handle()
+    {
+        $app = $this->createsApplication()->shouldntDelegate();
+
+        foreach ($this->attributes['identifiers'] as $identifier) {
+            $app = $app->$identifier;
+        }
+
+        return call_user_func_array([$app, $this->attributes['method']], $this->attributes['arguments']);
+    }
+
+    /**
+     * @return \EasyWeChat\Kernel\ServiceContainer
+     */
+    protected function createsApplication()
+    {
+        $application = $this->attributes['application'];
+
+        if ($application === EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Application::class) {
+            return $this->createsOpenPlatformApplication('officialAccount');
+        }
+
+        if ($application === EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Application::class) {
+            return $this->createsOpenPlatformApplication('miniProgram');
+        }
+
+        return new $application($this->buildConfig($this->attributes['config']));
+    }
+
+    protected function createsOpenPlatformApplication($type)
+    {
+        $config = $this->attributes['config'];
+
+        $authorizerAppId = $config['app_id'];
+
+        $config['app_id'] = $config['component_app_id'];
+
+        return EasyWeChat\Factory::openPlatform($this->buildConfig($config))->$type($authorizerAppId, $config['refresh_token']);
+    }
+
+    protected function buildConfig(array $config)
+    {
+        $config['response_type'] = DelegationResponse::class;
+
+        return $config;
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/EasyWeChat.php b/vendor/easywechat-composer/easywechat-composer/src/EasyWeChat.php
new file mode 100644
index 0000000..4ff3d9b
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/EasyWeChat.php
@@ -0,0 +1,79 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer;
+
+use EasyWeChatComposer\Delegation\DelegationOptions;
+
+class EasyWeChat
+{
+    /**
+     * @var array
+     */
+    protected static $config = [];
+
+    /**
+     * Encryption key.
+     *
+     * @var string
+     */
+    protected static $encryptionKey;
+
+    /**
+     * @param array $config
+     */
+    public static function mergeConfig(array $config)
+    {
+        static::$config = array_merge(static::$config, $config);
+    }
+
+    /**
+     * @return array
+     */
+    public static function config()
+    {
+        return static::$config;
+    }
+
+    /**
+     * Set encryption key.
+     *
+     * @param string $key
+     *
+     * @return static
+     */
+    public static function setEncryptionKey(string $key)
+    {
+        static::$encryptionKey = $key;
+
+        return new static();
+    }
+
+    /**
+     * Get encryption key.
+     *
+     * @return string
+     */
+    public static function getEncryptionKey(): string
+    {
+        return static::$encryptionKey;
+    }
+
+    /**
+     * @return \EasyWeChatComposer\Delegation\DelegationOptions
+     */
+    public static function withDelegation()
+    {
+        return new DelegationOptions();
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Encryption/DefaultEncrypter.php b/vendor/easywechat-composer/easywechat-composer/src/Encryption/DefaultEncrypter.php
new file mode 100644
index 0000000..2c4cd53
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Encryption/DefaultEncrypter.php
@@ -0,0 +1,89 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Encryption;
+
+use EasyWeChatComposer\Contracts\Encrypter;
+use EasyWeChatComposer\Exceptions\DecryptException;
+use EasyWeChatComposer\Exceptions\EncryptException;
+
+class DefaultEncrypter implements Encrypter
+{
+    /**
+     * @var string
+     */
+    protected $key;
+
+    /**
+     * @var string
+     */
+    protected $cipher;
+
+    /**
+     * @param string $key
+     * @param string $cipher
+     */
+    public function __construct($key, $cipher = 'AES-256-CBC')
+    {
+        $this->key = $key;
+        $this->cipher = $cipher;
+    }
+
+    /**
+     * Encrypt the given value.
+     *
+     * @param string $value
+     *
+     * @return string
+     *
+     * @throws \EasyWeChatComposer\Exceptions\EncryptException
+     */
+    public function encrypt($value)
+    {
+        $iv = random_bytes(openssl_cipher_iv_length($this->cipher));
+
+        $value = openssl_encrypt($value, $this->cipher, $this->key, 0, $iv);
+
+        if ($value === false) {
+            throw new EncryptException('Could not encrypt the data.');
+        }
+
+        $iv = base64_encode($iv);
+
+        return base64_encode(json_encode(compact('iv', 'value')));
+    }
+
+    /**
+     * Decrypt the given value.
+     *
+     * @param string $payload
+     *
+     * @return string
+     *
+     * @throws \EasyWeChatComposer\Exceptions\DecryptException
+     */
+    public function decrypt($payload)
+    {
+        $payload = json_decode(base64_decode($payload), true);
+
+        $iv = base64_decode($payload['iv']);
+
+        $decrypted = openssl_decrypt($payload['value'], $this->cipher, $this->key, 0, $iv);
+
+        if ($decrypted === false) {
+            throw new DecryptException('Could not decrypt the data.');
+        }
+
+        return $decrypted;
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Exceptions/DecryptException.php b/vendor/easywechat-composer/easywechat-composer/src/Exceptions/DecryptException.php
new file mode 100644
index 0000000..e210d1f
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Exceptions/DecryptException.php
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Exceptions;
+
+use Exception;
+
+class DecryptException extends Exception
+{
+    //
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Exceptions/DelegationException.php b/vendor/easywechat-composer/easywechat-composer/src/Exceptions/DelegationException.php
new file mode 100644
index 0000000..0af9c2d
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Exceptions/DelegationException.php
@@ -0,0 +1,42 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Exceptions;
+
+use Exception;
+
+class DelegationException extends Exception
+{
+    /**
+     * @var string
+     */
+    protected $exception;
+
+    /**
+     * @param string $exception
+     */
+    public function setException($exception)
+    {
+        $this->exception = $exception;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getException()
+    {
+        return $this->exception;
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Exceptions/EncryptException.php b/vendor/easywechat-composer/easywechat-composer/src/Exceptions/EncryptException.php
new file mode 100644
index 0000000..f88f8f4
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Exceptions/EncryptException.php
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Exceptions;
+
+use Exception;
+
+class EncryptException extends Exception
+{
+    //
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Extension.php b/vendor/easywechat-composer/easywechat-composer/src/Extension.php
new file mode 100644
index 0000000..6a521d1
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Extension.php
@@ -0,0 +1,143 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer;
+
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+use Pimple\Container;
+use ReflectionClass;
+
+class Extension
+{
+    /**
+     * @var \Pimple\Container
+     */
+    protected $app;
+
+    /**
+     * @var string
+     */
+    protected $manifestPath;
+
+    /**
+     * @var array|null
+     */
+    protected $manifest;
+
+    /**
+     * @param \Pimple\Container $app
+     */
+    public function __construct(Container $app)
+    {
+        $this->app = $app;
+        $this->manifestPath = __DIR__.'/../extensions.php';
+    }
+
+    /**
+     * Get observers.
+     *
+     * @return array
+     */
+    public function observers(): array
+    {
+        if ($this->shouldIgnore()) {
+            return [];
+        }
+
+        $observers = [];
+
+        foreach ($this->getManifest() as $name => $extra) {
+            $observers = array_merge($observers, $extra['observers'] ?? []);
+        }
+
+        return array_map([$this, 'listObserver'], array_filter($observers, [$this, 'validateObserver']));
+    }
+
+    /**
+     * @param mixed $observer
+     *
+     * @return bool
+     */
+    protected function isDisable($observer): bool
+    {
+        return in_array($observer, $this->app->config->get('disable_observers', []));
+    }
+
+    /**
+     * Get the observers should be ignore.
+     *
+     * @return bool
+     */
+    protected function shouldIgnore(): bool
+    {
+        return !file_exists($this->manifestPath) || $this->isDisable('*');
+    }
+
+    /**
+     * Validate the given observer.
+     *
+     * @param mixed $observer
+     *
+     * @return bool
+     *
+     * @throws \ReflectionException
+     */
+    protected function validateObserver($observer): bool
+    {
+        return !$this->isDisable($observer)
+            && (new ReflectionClass($observer))->implementsInterface(EventHandlerInterface::class)
+            && $this->accessible($observer);
+    }
+
+    /**
+     * Determine whether the given observer is accessible.
+     *
+     * @param string $observer
+     *
+     * @return bool
+     */
+    protected function accessible($observer): bool
+    {
+        if (!method_exists($observer, 'getAccessor')) {
+            return true;
+        }
+
+        return in_array(get_class($this->app), (array) $observer::getAccessor());
+    }
+
+    /**
+     * @param mixed $observer
+     *
+     * @return array
+     */
+    protected function listObserver($observer): array
+    {
+        $condition = method_exists($observer, 'onCondition') ? $observer::onCondition() : '*';
+
+        return [$observer, $condition];
+    }
+
+    /**
+     * Get the easywechat manifest.
+     *
+     * @return array
+     */
+    protected function getManifest(): array
+    {
+        if (!is_null($this->manifest)) {
+            return $this->manifest;
+        }
+
+        return $this->manifest = file_exists($this->manifestPath) ? require $this->manifestPath : [];
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Http/DelegationResponse.php b/vendor/easywechat-composer/easywechat-composer/src/Http/DelegationResponse.php
new file mode 100644
index 0000000..329eb54
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Http/DelegationResponse.php
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Http;
+
+class DelegationResponse extends Response
+{
+    /**
+     * @return string
+     */
+    public function getBodyContents()
+    {
+        return $this->response->getBodyContents();
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Http/Response.php b/vendor/easywechat-composer/easywechat-composer/src/Http/Response.php
new file mode 100644
index 0000000..534bf54
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Http/Response.php
@@ -0,0 +1,104 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Http;
+
+use EasyWeChat\Kernel\Contracts\Arrayable;
+use EasyWeChat\Kernel\Http\Response as HttpResponse;
+use JsonSerializable;
+
+class Response implements Arrayable, JsonSerializable
+{
+    /**
+     * @var \EasyWeChat\Kernel\Http\Response
+     */
+    protected $response;
+
+    /**
+     * @var array
+     */
+    protected $array;
+
+    /**
+     * @param \EasyWeChat\Kernel\Http\Response $response
+     */
+    public function __construct(HttpResponse $response)
+    {
+        $this->response = $response;
+    }
+
+    /**
+     * @see \ArrayAccess::offsetExists
+     *
+     * @param string $offset
+     *
+     * @return bool
+     */
+    public function offsetExists($offset)
+    {
+        return isset($this->toArray()[$offset]);
+    }
+
+    /**
+     * @see \ArrayAccess::offsetGet
+     *
+     * @param string $offset
+     *
+     * @return mixed
+     */
+    public function offsetGet($offset)
+    {
+        return $this->toArray()[$offset] ?? null;
+    }
+
+    /**
+     * @see \ArrayAccess::offsetSet
+     *
+     * @param string $offset
+     * @param mixed  $value
+     */
+    public function offsetSet($offset, $value)
+    {
+        //
+    }
+
+    /**
+     * @see \ArrayAccess::offsetUnset
+     *
+     * @param string $offset
+     */
+    public function offsetUnset($offset)
+    {
+        //
+    }
+
+    /**
+     * Get the instance as an array.
+     *
+     * @return array
+     */
+    public function toArray()
+    {
+        return $this->array ?: $this->array = $this->response->toArray();
+    }
+
+    /**
+     * Convert the object into something JSON serializable.
+     *
+     * @return array
+     */
+    public function jsonSerialize()
+    {
+        return $this->toArray();
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Laravel/Http/Controllers/DelegatesController.php b/vendor/easywechat-composer/easywechat-composer/src/Laravel/Http/Controllers/DelegatesController.php
new file mode 100644
index 0000000..ad014d8
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Laravel/Http/Controllers/DelegatesController.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Laravel\Http\Controllers;
+
+use EasyWeChatComposer\Delegation\Hydrate;
+use EasyWeChatComposer\Encryption\DefaultEncrypter;
+use Illuminate\Http\Request;
+use Throwable;
+
+class DelegatesController
+{
+    /**
+     * @param \Illuminate\Http\Request                        $request
+     * @param \EasyWeChatComposer\Encryption\DefaultEncrypter $encrypter
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function __invoke(Request $request, DefaultEncrypter $encrypter)
+    {
+        try {
+            $data = json_decode($encrypter->decrypt($request->get('encrypted')), true);
+
+            $hydrate = new Hydrate($data);
+
+            $response = $hydrate->handle();
+
+            return response()->json([
+                'response_type' => get_class($response),
+                'response' => $encrypter->encrypt($response->getBodyContents()),
+            ]);
+        } catch (Throwable $t) {
+            return [
+                'exception' => get_class($t),
+                'message' => $t->getMessage(),
+            ];
+        }
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Laravel/ServiceProvider.php b/vendor/easywechat-composer/easywechat-composer/src/Laravel/ServiceProvider.php
new file mode 100644
index 0000000..5b6c55f
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Laravel/ServiceProvider.php
@@ -0,0 +1,113 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Laravel;
+
+use EasyWeChatComposer\EasyWeChat;
+use Illuminate\Support\Arr;
+use EasyWeChatComposer\Encryption\DefaultEncrypter;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Route;
+use Illuminate\Support\ServiceProvider as LaravelServiceProvider;
+use RuntimeException;
+
+class ServiceProvider extends LaravelServiceProvider
+{
+    /**
+     * Bootstrap any application services.
+     */
+    public function boot()
+    {
+        $this->registerRoutes();
+        $this->publishes([
+            __DIR__.'/config.php' => config_path('easywechat-composer.php'),
+        ]);
+
+        EasyWeChat::setEncryptionKey(
+            $defaultKey = $this->getKey()
+        );
+
+        EasyWeChat::withDelegation()
+                    ->toHost($this->config('delegation.host'))
+                    ->ability($this->config('delegation.enabled'));
+
+        $this->app->when(DefaultEncrypter::class)->needs('$key')->give($defaultKey);
+    }
+
+    /**
+     * Register routes.
+     */
+    protected function registerRoutes()
+    {
+        Route::prefix('easywechat-composer')->namespace('EasyWeChatComposer\Laravel\Http\Controllers')->group(function () {
+            $this->loadRoutesFrom(__DIR__.'/routes.php');
+        });
+    }
+
+    /**
+     * Register any application services.
+     */
+    public function register()
+    {
+        $this->configure();
+    }
+
+    /**
+     * Register config.
+     */
+    protected function configure()
+    {
+        $this->mergeConfigFrom(
+            __DIR__.'/config.php', 'easywechat-composer'
+        );
+    }
+
+    /**
+     * Get the specified configuration value.
+     *
+     * @param string|null $key
+     * @param mixed       $default
+     *
+     * @return mixed
+     */
+    protected function config($key = null, $default = null)
+    {
+        $config = $this->app['config']->get('easywechat-composer');
+
+        if (is_null($key)) {
+            return $config;
+        }
+
+        return Arr::get($config, $key, $default);
+    }
+
+    /**
+     * @return string
+     */
+    protected function getKey()
+    {
+        return $this->config('encryption.key') ?: $this->getMd5Key();
+    }
+
+    /**
+     * @return string
+     */
+    protected function getMd5Key()
+    {
+        return Cache::remember('easywechat-composer.encryption_key', 30, function () {
+            throw_unless(file_exists($path = base_path('composer.lock')), RuntimeException::class, 'No encryption key provided.');
+
+            return md5_file($path);
+        });
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Laravel/config.php b/vendor/easywechat-composer/easywechat-composer/src/Laravel/config.php
new file mode 100644
index 0000000..5deabef
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Laravel/config.php
@@ -0,0 +1,29 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) mingyoung <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+return [
+
+    'encryption' => [
+
+        'key' => env('EASYWECHAT_KEY'),
+
+    ],
+
+    'delegation' => [
+
+        'enabled' => env('EASYWECHAT_DELEGATION', false),
+
+        'host' => env('EASYWECHAT_DELEGATION_HOST'),
+    ],
+
+];
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Laravel/routes.php b/vendor/easywechat-composer/easywechat-composer/src/Laravel/routes.php
new file mode 100644
index 0000000..6a3041a
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Laravel/routes.php
@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+use Illuminate\Support\Facades\Route;
+
+Route::post('delegate', 'DelegatesController');
diff --git a/vendor/easywechat-composer/easywechat-composer/src/ManifestManager.php b/vendor/easywechat-composer/easywechat-composer/src/ManifestManager.php
new file mode 100644
index 0000000..ecf17a3
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/ManifestManager.php
@@ -0,0 +1,120 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer;
+
+class ManifestManager
+{
+    const PACKAGE_TYPE = 'easywechat-extension';
+
+    const EXTRA_OBSERVER = 'observers';
+
+    /**
+     * The vendor path.
+     *
+     * @var string
+     */
+    protected $vendorPath;
+
+    /**
+     * The manifest path.
+     *
+     * @var string
+     */
+    protected $manifestPath;
+
+    /**
+     * @param string      $vendorPath
+     * @param string|null $manifestPath
+     */
+    public function __construct(string $vendorPath, string $manifestPath = null)
+    {
+        $this->vendorPath = $vendorPath;
+        $this->manifestPath = $manifestPath ?: $vendorPath.'/easywechat-composer/easywechat-composer/extensions.php';
+    }
+
+    /**
+     * Remove manifest file.
+     *
+     * @return $this
+     */
+    public function unlink()
+    {
+        if (file_exists($this->manifestPath)) {
+            @unlink($this->manifestPath);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Build the manifest file.
+     */
+    public function build()
+    {
+        $packages = [];
+
+        if (file_exists($installed = $this->vendorPath.'/composer/installed.json')) {
+            $packages = json_decode(file_get_contents($installed), true);
+        }
+
+        $this->write($this->map($packages));
+    }
+
+    /**
+     * @param array $packages
+     *
+     * @return array
+     */
+    protected function map(array $packages): array
+    {
+        $manifest = [];
+
+        $packages = array_filter($packages, function ($package) {
+            return $package['type'] === self::PACKAGE_TYPE;
+        });
+
+        foreach ($packages as $package) {
+            $manifest[$package['name']] = [self::EXTRA_OBSERVER => $package['extra'][self::EXTRA_OBSERVER] ?? []];
+        }
+
+        return $manifest;
+    }
+
+    /**
+     * Write the manifest array to a file.
+     *
+     * @param array $manifest
+     */
+    protected function write(array $manifest)
+    {
+        file_put_contents(
+            $this->manifestPath,
+            '<?php return '.var_export($manifest, true).';'
+        );
+
+        $this->invalidate($this->manifestPath);
+    }
+
+    /**
+     * Invalidate the given file.
+     *
+     * @param string $file
+     */
+    protected function invalidate($file)
+    {
+        if (function_exists('opcache_invalidate')) {
+            @opcache_invalidate($file, true);
+        }
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Plugin.php b/vendor/easywechat-composer/easywechat-composer/src/Plugin.php
new file mode 100644
index 0000000..ca48099
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Plugin.php
@@ -0,0 +1,92 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer;
+
+use Composer\Composer;
+use Composer\EventDispatcher\EventSubscriberInterface;
+use Composer\Installer\PackageEvent;
+use Composer\Installer\PackageEvents;
+use Composer\IO\IOInterface;
+use Composer\Plugin\Capable;
+use Composer\Plugin\PluginInterface;
+use Composer\Script\Event;
+use Composer\Script\ScriptEvents;
+
+class Plugin implements PluginInterface, EventSubscriberInterface, Capable
+{
+    /**
+     * @var bool
+     */
+    protected $activated = true;
+
+    /**
+     * Apply plugin modifications to Composer.
+     *
+     * @param \Composer\Composer       $composer
+     * @param \Composer\IO\IOInterface $io
+     */
+    public function activate(Composer $composer, IOInterface $io)
+    {
+        //
+    }
+
+    /**
+     * @return array
+     */
+    public function getCapabilities()
+    {
+        return [
+            'Composer\Plugin\Capability\CommandProvider' => 'EasyWeChatComposer\Commands\Provider',
+        ];
+    }
+
+    /**
+     * Listen events.
+     *
+     * @return array
+     */
+    public static function getSubscribedEvents()
+    {
+        return [
+            PackageEvents::PRE_PACKAGE_UNINSTALL => 'prePackageUninstall',
+            ScriptEvents::POST_AUTOLOAD_DUMP => 'postAutoloadDump',
+        ];
+    }
+
+    /**
+     * @param \Composer\Installer\PackageEvent
+     */
+    public function prePackageUninstall(PackageEvent $event)
+    {
+        if ($event->getOperation()->getPackage()->getName() === 'overtrue/wechat') {
+            $this->activated = false;
+        }
+    }
+
+    /**
+     * @param \Composer\Script\Event $event
+     */
+    public function postAutoloadDump(Event $event)
+    {
+        if (!$this->activated) {
+            return;
+        }
+
+        $manifest = new ManifestManager(
+            rtrim($event->getComposer()->getConfig()->get('vendor-dir'), '/')
+        );
+
+        $manifest->unlink()->build();
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Traits/MakesHttpRequests.php b/vendor/easywechat-composer/easywechat-composer/src/Traits/MakesHttpRequests.php
new file mode 100644
index 0000000..091a75c
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Traits/MakesHttpRequests.php
@@ -0,0 +1,110 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Traits;
+
+use EasyWeChat\Kernel\Http\StreamResponse;
+use EasyWeChat\Kernel\Traits\ResponseCastable;
+use EasyWeChatComposer\Contracts\Encrypter;
+use EasyWeChatComposer\EasyWeChat;
+use EasyWeChatComposer\Encryption\DefaultEncrypter;
+use EasyWeChatComposer\Exceptions\DelegationException;
+use GuzzleHttp\Client;
+use GuzzleHttp\ClientInterface;
+
+trait MakesHttpRequests
+{
+    use ResponseCastable;
+
+    /**
+     * @var \GuzzleHttp\ClientInterface
+     */
+    protected $httpClient;
+
+    /**
+     * @var \EasyWeChatComposer\Contracts\Encrypter
+     */
+    protected $encrypter;
+
+    /**
+     * @param string $endpoint
+     * @param array  $payload
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function request($endpoint, array $payload)
+    {
+        $response = $this->getHttpClient()->request('POST', $endpoint, [
+            'form_params' => $this->buildFormParams($payload),
+        ]);
+
+        $parsed = $this->parseResponse($response);
+
+        return $this->detectAndCastResponseToType(
+            $this->getEncrypter()->decrypt($parsed['response']),
+            ($parsed['response_type'] === StreamResponse::class) ? 'raw' : $this->app['config']['response_type']
+        );
+    }
+
+    /**
+     * @param array $payload
+     *
+     * @return array
+     */
+    protected function buildFormParams($payload)
+    {
+        return [
+            'encrypted' => $this->getEncrypter()->encrypt(json_encode($payload)),
+        ];
+    }
+
+    /**
+     * @param \Psr\Http\Message\ResponseInterface $response
+     *
+     * @return array
+     */
+    protected function parseResponse($response)
+    {
+        $result = json_decode((string) $response->getBody(), true);
+
+        if (isset($result['exception'])) {
+            throw (new DelegationException($result['message']))->setException($result['exception']);
+        }
+
+        return $result;
+    }
+
+    /**
+     * @return \GuzzleHttp\ClientInterface
+     */
+    protected function getHttpClient(): ClientInterface
+    {
+        return $this->httpClient ?: $this->httpClient = new Client([
+            'base_uri' => $this->app['config']['delegation']['host'],
+        ]);
+    }
+
+    /**
+     * @return \EasyWeChatComposer\Contracts\Encrypter
+     */
+    protected function getEncrypter(): Encrypter
+    {
+        return $this->encrypter ?: $this->encrypter = new DefaultEncrypter(
+            EasyWeChat::getEncryptionKey()
+        );
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/src/Traits/WithAggregator.php b/vendor/easywechat-composer/easywechat-composer/src/Traits/WithAggregator.php
new file mode 100644
index 0000000..a3eb16e
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/src/Traits/WithAggregator.php
@@ -0,0 +1,60 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Traits;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChatComposer\Delegation\DelegationTo;
+use EasyWeChatComposer\EasyWeChat;
+
+trait WithAggregator
+{
+    /**
+     * Aggregate.
+     */
+    protected function aggregate()
+    {
+        foreach (EasyWeChat::config() as $key => $value) {
+            $this['config']->set($key, $value);
+        }
+    }
+
+    /**
+     * @return bool
+     */
+    public function shouldDelegate($id)
+    {
+        return $this['config']->get('delegation.enabled')
+            && $this->offsetGet($id) instanceof BaseClient;
+    }
+
+    /**
+     * @return $this
+     */
+    public function shouldntDelegate()
+    {
+        $this['config']->set('delegation.enabled', false);
+
+        return $this;
+    }
+
+    /**
+     * @param string $id
+     *
+     * @return \EasyWeChatComposer\Delegation
+     */
+    public function delegateTo($id)
+    {
+        return new DelegationTo($this, $id);
+    }
+}
diff --git a/vendor/easywechat-composer/easywechat-composer/tests/ManifestManagerTest.php b/vendor/easywechat-composer/easywechat-composer/tests/ManifestManagerTest.php
new file mode 100644
index 0000000..23b8e2c
--- /dev/null
+++ b/vendor/easywechat-composer/easywechat-composer/tests/ManifestManagerTest.php
@@ -0,0 +1,37 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the EasyWeChatComposer.
+ *
+ * (c) 张铭阳 <mingyoungcheung@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChatComposer\Tests;
+
+use EasyWeChatComposer\ManifestManager;
+use PHPUnit\Framework\TestCase;
+
+class ManifestManagerTest extends TestCase
+{
+    private $vendorPath;
+    private $manifestPath;
+
+    protected function getManifestManager()
+    {
+        return new ManifestManager(
+            $this->vendorPath = __DIR__.'/__fixtures__/vendor/',
+            $this->manifestPath = __DIR__.'/__fixtures__/extensions.php'
+        );
+    }
+
+    public function testUnlink()
+    {
+        $this->assertInstanceOf(ManifestManager::class, $this->getManifestManager()->unlink());
+        $this->assertFalse(file_exists($this->manifestPath));
+    }
+}
diff --git a/vendor/guzzlehttp/guzzle/.php_cs b/vendor/guzzlehttp/guzzle/.php_cs
new file mode 100644
index 0000000..a8ace8a
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/.php_cs
@@ -0,0 +1,21 @@
+<?php
+
+$config = PhpCsFixer\Config::create()
+    ->setRiskyAllowed(true)
+    ->setRules([
+        '@PSR2' => true,
+        'array_syntax' => ['syntax' => 'short'],
+        'declare_strict_types' => false,
+        'concat_space' => ['spacing'=>'one'],
+        // 'ordered_imports' => true,
+        // 'phpdoc_align' => ['align'=>'vertical'],
+        // 'native_function_invocation' => true,
+    ])
+    ->setFinder(
+        PhpCsFixer\Finder::create()
+            ->in(__DIR__.'/src')
+            ->name('*.php')
+    )
+;
+
+return $config;
diff --git a/vendor/guzzlehttp/guzzle/Dockerfile b/vendor/guzzlehttp/guzzle/Dockerfile
new file mode 100644
index 0000000..f6a0952
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/Dockerfile
@@ -0,0 +1,18 @@
+FROM composer:latest as setup
+
+RUN mkdir /guzzle
+
+WORKDIR /guzzle
+
+RUN set -xe \
+    && composer init --name=guzzlehttp/test --description="Simple project for testing Guzzle scripts" --author="Márk Sági-Kazár <mark.sagikazar@gmail.com>" --no-interaction \
+    && composer require guzzlehttp/guzzle
+
+
+FROM php:7.3
+
+RUN mkdir /guzzle
+
+WORKDIR /guzzle
+
+COPY --from=setup /guzzle /guzzle
diff --git a/vendor/guzzlehttp/guzzle/phpstan.neon.dist b/vendor/guzzlehttp/guzzle/phpstan.neon.dist
new file mode 100644
index 0000000..4ef4192
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/phpstan.neon.dist
@@ -0,0 +1,9 @@
+parameters:
+    level: 1
+    paths:
+      - src
+
+    ignoreErrors:
+        -
+            message: '#Function uri_template not found#'
+            path: %currentWorkingDirectory%/src/functions.php
diff --git a/vendor/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php b/vendor/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..bfd20e2
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace GuzzleHttp\Exception;
+
+final class InvalidArgumentException extends \InvalidArgumentException implements GuzzleException
+{
+}
diff --git a/vendor/monolog/monolog/UPGRADE.md b/vendor/monolog/monolog/UPGRADE.md
new file mode 100644
index 0000000..84e15e6
--- /dev/null
+++ b/vendor/monolog/monolog/UPGRADE.md
@@ -0,0 +1,72 @@
+### 2.0.0
+
+- `Monolog\Logger::API` can be used to distinguish between a Monolog `1` and `2`
+  install of Monolog when writing integration code.
+
+- Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`)
+  methods as well as `emerg`, `crit`, `err` and `warn`.
+
+- DateTime are now formatted with a timezone and microseconds (unless disabled).
+  Various formatters and log output might be affected, which may mess with log parsing
+  in some cases.
+
+- The `datetime` in every record array is now a DateTimeImmutable, not that you
+  should have been modifying these anyway.
+
+- The timezone is now set per Logger instance and not statically, either
+  via ->setTimezone or passed in the constructor. Calls to Logger::setTimezone
+  should be converted.
+
+- `HandlerInterface` has been split off and two new interfaces now exist for
+  more granular controls: `ProcessableHandlerInterface` and
+  `FormattableHandlerInterface`. Handlers not extending `AbstractHandler`
+  should make sure to implement the relevant interfaces.
+
+- `HandlerInterface` now requires the `close` method to be implemented. This
+  only impacts you if you implement the interface yourself, but you can extend
+  the new `Monolog\Handler\Handler` base class too.
+
+- There is no more default handler configured on empty Logger instances, if
+  you were relying on that you will not get any output anymore, make sure to
+  configure the handler you need.
+
+#### LogglyFormatter
+
+- The records' `datetime` is not sent anymore. Only `timestamp` is sent to Loggly.
+
+#### AmqpHandler
+
+- Log levels are not shortened to 4 characters anymore. e.g. a warning record
+  will be sent using the `warning.channel` routing key instead of `warn.channel`
+  as in 1.x.
+- The exchange name does not default to 'log' anymore, and it is completely ignored
+  now for the AMQP extension users. Only PHPAmqpLib uses it if provided.
+
+#### RotatingFileHandler
+
+- The file name format must now contain `{date}` and the date format must be set
+  to one of the predefined FILE_PER_* constants to avoid issues with file rotation.
+  See `setFilenameFormat`.
+
+#### LogstashFormatter
+
+- Removed Logstash V0 support
+- Context/extra prefix has been removed in favor of letting users configure the exact key being sent
+- Context/extra data are now sent as an object instead of single keys
+
+#### HipChatHandler
+
+- Removed deprecated HipChat handler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead
+
+#### SlackbotHandler
+
+- Removed deprecated SlackbotHandler handler, use SlackWebhookHandler or SlackHandler instead
+
+#### RavenHandler
+
+- Removed deprecated RavenHandler handler, use sentry/sentry 2.x and their Sentry\Monolog\Handler instead
+
+#### ElasticSearchHandler
+
+- As support for the official Elasticsearch library was added, the former ElasticSearchHandler has been
+  renamed to ElasticaHandler and the new one added as ElasticsearchHandler.
diff --git a/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php b/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php
new file mode 100644
index 0000000..6a1ba9b
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php
@@ -0,0 +1,49 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog;
+
+use DateTimeZone;
+
+/**
+ * Overrides default json encoding of date time objects
+ *
+ * @author Menno Holtkamp
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable
+{
+    /**
+     * @var bool
+     */
+    private $useMicroseconds;
+
+    public function __construct(bool $useMicroseconds, ?DateTimeZone $timezone = null)
+    {
+        $this->useMicroseconds = $useMicroseconds;
+
+        parent::__construct('now', $timezone);
+    }
+
+    public function jsonSerialize(): string
+    {
+        if ($this->useMicroseconds) {
+            return $this->format('Y-m-d\TH:i:s.uP');
+        }
+
+        return $this->format('Y-m-d\TH:i:sP');
+    }
+
+    public function __toString(): string
+    {
+        return $this->jsonSerialize();
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php
new file mode 100644
index 0000000..84affef
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php
@@ -0,0 +1,89 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use DateTime;
+
+/**
+ * Format a log message into an Elasticsearch record
+ *
+ * @author Avtandil Kikabidze <akalongman@gmail.com>
+ */
+class ElasticsearchFormatter extends NormalizerFormatter
+{
+    /**
+     * @var string Elasticsearch index name
+     */
+    protected $index;
+
+    /**
+     * @var string Elasticsearch record type
+     */
+    protected $type;
+
+    /**
+     * @param string $index Elasticsearch index name
+     * @param string $type  Elasticsearch record type
+     */
+    public function __construct(string $index, string $type)
+    {
+        // Elasticsearch requires an ISO 8601 format date with optional millisecond precision.
+        parent::__construct(DateTime::ISO8601);
+
+        $this->index = $index;
+        $this->type = $type;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function format(array $record)
+    {
+        $record = parent::format($record);
+
+        return $this->getDocument($record);
+    }
+
+    /**
+     * Getter index
+     *
+     * @return string
+     */
+    public function getIndex(): string
+    {
+        return $this->index;
+    }
+
+    /**
+     * Getter type
+     *
+     * @return string
+     */
+    public function getType(): string
+    {
+        return $this->type;
+    }
+
+    /**
+     * Convert a log message into an Elasticsearch record
+     *
+     * @param  array $record Log message
+     * @return array
+     */
+    protected function getDocument(array $record): array
+    {
+        $record['_index'] = $this->index;
+        $record['_type'] = $this->type;
+
+        return $record;
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php
new file mode 100644
index 0000000..b0451ab
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php
@@ -0,0 +1,66 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+/**
+ * Encodes message information into JSON in a format compatible with Logmatic.
+ *
+ * @author Julien Breux <julien.breux@gmail.com>
+ */
+class LogmaticFormatter extends JsonFormatter
+{
+    protected const MARKERS = ["sourcecode", "php"];
+
+    /**
+     * @var string
+     */
+    protected $hostname = '';
+
+    /**
+     * @var string
+     */
+    protected $appname = '';
+
+    public function setHostname(string $hostname): self
+    {
+        $this->hostname = $hostname;
+
+        return $this;
+    }
+
+    public function setAppname(string $appname): self
+    {
+        $this->appname = $appname;
+
+        return $this;
+    }
+
+    /**
+     * Appends the 'hostname' and 'appname' parameter for indexing by Logmatic.
+     *
+     * @see http://doc.logmatic.io/docs/basics-to-send-data
+     * @see \Monolog\Formatter\JsonFormatter::format()
+     */
+    public function format(array $record): string
+    {
+        if (!empty($this->hostname)) {
+            $record["hostname"] = $this->hostname;
+        }
+        if (!empty($this->appname)) {
+            $record["appname"] = $this->appname;
+        }
+
+        $record["@marker"] = static::MARKERS;
+
+        return parent::format($record);
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php
new file mode 100644
index 0000000..78a8d1e
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php
@@ -0,0 +1,124 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\ElasticaFormatter;
+use Monolog\Logger;
+use Elastica\Client;
+use Elastica\Exception\ExceptionInterface;
+
+/**
+ * Elastic Search handler
+ *
+ * Usage example:
+ *
+ *    $client = new \Elastica\Client();
+ *    $options = array(
+ *        'index' => 'elastic_index_name',
+ *        'type' => 'elastic_doc_type',
+ *    );
+ *    $handler = new ElasticaHandler($client, $options);
+ *    $log = new Logger('application');
+ *    $log->pushHandler($handler);
+ *
+ * @author Jelle Vink <jelle.vink@gmail.com>
+ */
+class ElasticaHandler extends AbstractProcessingHandler
+{
+    /**
+     * @var Client
+     */
+    protected $client;
+
+    /**
+     * @var array Handler config options
+     */
+    protected $options = [];
+
+    /**
+     * @param Client     $client  Elastica Client object
+     * @param array      $options Handler configuration
+     * @param int|string $level   The minimum logging level at which this handler will be triggered
+     * @param bool       $bubble  Whether the messages that are handled can bubble up the stack or not
+     */
+    public function __construct(Client $client, array $options = [], $level = Logger::DEBUG, bool $bubble = true)
+    {
+        parent::__construct($level, $bubble);
+        $this->client = $client;
+        $this->options = array_merge(
+            [
+                'index'          => 'monolog',      // Elastic index name
+                'type'           => 'record',       // Elastic document type
+                'ignore_error'   => false,          // Suppress Elastica exceptions
+            ],
+            $options
+        );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected function write(array $record): void
+    {
+        $this->bulkSend([$record['formatted']]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setFormatter(FormatterInterface $formatter): HandlerInterface
+    {
+        if ($formatter instanceof ElasticaFormatter) {
+            return parent::setFormatter($formatter);
+        }
+
+        throw new \InvalidArgumentException('ElasticaHandler is only compatible with ElasticaFormatter');
+    }
+
+    public function getOptions(): array
+    {
+        return $this->options;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected function getDefaultFormatter(): FormatterInterface
+    {
+        return new ElasticaFormatter($this->options['index'], $this->options['type']);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function handleBatch(array $records): void
+    {
+        $documents = $this->getFormatter()->formatBatch($records);
+        $this->bulkSend($documents);
+    }
+
+    /**
+     * Use Elasticsearch bulk API to send list of documents
+     * @throws \RuntimeException
+     */
+    protected function bulkSend(array $documents): void
+    {
+        try {
+            $this->client->addDocuments($documents);
+        } catch (ExceptionInterface $e) {
+            if (!$this->options['ignore_error']) {
+                throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e);
+            }
+        }
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php
new file mode 100644
index 0000000..ea06574
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php
@@ -0,0 +1,59 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Throwable;
+
+class FallbackGroupHandler extends GroupHandler
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function handle(array $record): bool
+    {
+        if ($this->processors) {
+            $record = $this->processRecord($record);
+        }
+        foreach ($this->handlers as $handler) {
+            try {
+                $handler->handle($record);
+                break;
+            } catch (Throwable $e) {
+                // What throwable?
+            }
+        }
+        return false === $this->bubble;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function handleBatch(array $records): void
+    {
+        if ($this->processors) {
+            $processed = [];
+            foreach ($records as $record) {
+                $processed[] = $this->processRecord($record);
+            }
+            $records = $processed;
+        }
+
+        foreach ($this->handlers as $handler) {
+            try {
+                $handler->handleBatch($records);
+                break;
+            } catch (Throwable $e) {
+                // What throwable?
+            }
+        }
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php
new file mode 100644
index 0000000..fc1693c
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php
@@ -0,0 +1,37 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\FormatterInterface;
+
+/**
+ * Interface to describe loggers that have a formatter
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+interface FormattableHandlerInterface
+{
+    /**
+     * Sets the formatter.
+     *
+     * @param  FormatterInterface $formatter
+     * @return HandlerInterface   self
+     */
+    public function setFormatter(FormatterInterface $formatter): HandlerInterface;
+
+    /**
+     * Gets the formatter.
+     *
+     * @return FormatterInterface
+     */
+    public function getFormatter(): FormatterInterface;
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php
new file mode 100644
index 0000000..00140b4
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php
@@ -0,0 +1,61 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\LineFormatter;
+
+/**
+ * Helper trait for implementing FormattableInterface
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+trait FormattableHandlerTrait
+{
+    /**
+     * @var FormatterInterface
+     */
+    protected $formatter;
+
+    /**
+     * {@inheritdoc}
+     * @suppress PhanTypeMismatchReturn
+     */
+    public function setFormatter(FormatterInterface $formatter): HandlerInterface
+    {
+        $this->formatter = $formatter;
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getFormatter(): FormatterInterface
+    {
+        if (!$this->formatter) {
+            $this->formatter = $this->getDefaultFormatter();
+        }
+
+        return $this->formatter;
+    }
+
+    /**
+     * Gets the default formatter.
+     *
+     * Overwrite this if the LineFormatter is not a good default for your handler.
+     */
+    protected function getDefaultFormatter(): FormatterInterface
+    {
+        return new LineFormatter();
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Handler.php b/vendor/monolog/monolog/src/Monolog/Handler/Handler.php
new file mode 100644
index 0000000..9f43fe1
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/Handler.php
@@ -0,0 +1,53 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+/**
+ * Base Handler class providing basic close() support as well as handleBatch
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+abstract class Handler implements HandlerInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function handleBatch(array $records): void
+    {
+        foreach ($records as $record) {
+            $this->handle($record);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function close(): void
+    {
+    }
+
+    public function __destruct()
+    {
+        try {
+            $this->close();
+        } catch (\Throwable $e) {
+            // do nothing
+        }
+    }
+
+    public function __sleep()
+    {
+        $this->close();
+
+        return array_keys(get_object_vars($this));
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php
new file mode 100644
index 0000000..209af16
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php
@@ -0,0 +1,88 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Logger;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\LogmaticFormatter;
+
+/**
+ * @author Julien Breux <julien.breux@gmail.com>
+ */
+class LogmaticHandler extends SocketHandler
+{
+    /**
+     * @var string
+     */
+    private $logToken;
+
+    /**
+     * @var string
+     */
+    private $hostname;
+
+    /**
+     * @var string
+     */
+    private $appname;
+
+    /**
+     * @param string     $token    Log token supplied by Logmatic.
+     * @param string     $hostname Host name supplied by Logmatic.
+     * @param string     $appname  Application name supplied by Logmatic.
+     * @param bool       $useSSL   Whether or not SSL encryption should be used.
+     * @param int|string $level    The minimum logging level to trigger this handler.
+     * @param bool       $bubble   Whether or not messages that are handled should bubble up the stack.
+     *
+     * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing
+     */
+    public function __construct(string $token, string $hostname = '', string $appname = '', bool $useSSL = true, $level = Logger::DEBUG, bool $bubble = true)
+    {
+        if ($useSSL && !extension_loaded('openssl')) {
+            throw new MissingExtensionException('The OpenSSL PHP extension is required to use SSL encrypted connection for LogmaticHandler');
+        }
+
+        $endpoint = $useSSL ? 'ssl://api.logmatic.io:10515' : 'api.logmatic.io:10514';
+        $endpoint .= '/v1/';
+
+        parent::__construct($endpoint, $level, $bubble);
+
+        $this->logToken = $token;
+        $this->hostname = $hostname;
+        $this->appname  = $appname;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function generateDataStream(array $record): string
+    {
+        return $this->logToken . ' ' . $record['formatted'];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getDefaultFormatter(): FormatterInterface
+    {
+        $formatter = new LogmaticFormatter();
+
+        if (!empty($this->hostname)) {
+            $formatter->setHostname($this->hostname);
+        }
+        if (!empty($this->appname)) {
+            $formatter->setAppname($this->appname);
+        }
+
+        return $formatter;
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php
new file mode 100644
index 0000000..8ee2b4c
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php
@@ -0,0 +1,40 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+/**
+ * No-op
+ *
+ * This handler handles anything, but does nothing, and does not stop bubbling to the rest of the stack.
+ * This can be used for testing, or to disable a handler when overriding a configuration without
+ * influencing the rest of the stack.
+ *
+ * @author Roel Harbers <roelharbers@gmail.com>
+ */
+class NoopHandler extends Handler
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function isHandling(array $record): bool
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function handle(array $record): bool
+    {
+        return false;
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php
new file mode 100644
index 0000000..448f633
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php
@@ -0,0 +1,146 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Logger;
+use Monolog\Formatter\FormatterInterface;
+
+/**
+ * Handler to only pass log messages when a certain threshold of number of messages is reached.
+ *
+ * This can be useful in cases of processing a batch of data, but you're for example only interested
+ * in case it fails catastrophically instead of a warning for 1 or 2 events. Worse things can happen, right?
+ *
+ * Usage example:
+ *
+ * ```
+ *   $log = new Logger('application');
+ *   $handler = new SomeHandler(...)
+ *
+ *   // Pass all warnings to the handler when more than 10 & all error messages when more then 5
+ *   $overflow = new OverflowHandler($handler, [Logger::WARNING => 10, Logger::ERROR => 5]);
+ *
+ *   $log->pushHandler($overflow);
+ *```
+ *
+ * @author Kris Buist <krisbuist@gmail.com>
+ */
+class OverflowHandler extends AbstractHandler implements FormattableHandlerInterface
+{
+    /** @var HandlerInterface */
+    private $handler;
+
+    /** @var int[] */
+    private $thresholdMap = [
+        Logger::DEBUG => 0,
+        Logger::INFO => 0,
+        Logger::NOTICE => 0,
+        Logger::WARNING => 0,
+        Logger::ERROR => 0,
+        Logger::CRITICAL => 0,
+        Logger::ALERT => 0,
+        Logger::EMERGENCY => 0,
+    ];
+
+    /**
+     * Buffer of all messages passed to the handler before the threshold was reached
+     *
+     * @var mixed[][]
+     */
+    private $buffer = [];
+
+    /**
+     * @param HandlerInterface $handler
+     * @param int[]            $thresholdMap Dictionary of logger level => threshold
+     * @param int              $level
+     * @param bool             $bubble
+     */
+    public function __construct(
+        HandlerInterface $handler,
+        array $thresholdMap = [],
+        int $level = Logger::DEBUG,
+        bool $bubble = true
+    ) {
+        $this->handler = $handler;
+        foreach ($thresholdMap as $thresholdLevel => $threshold) {
+            $this->thresholdMap[$thresholdLevel] = $threshold;
+        }
+        parent::__construct($level, $bubble);
+    }
+
+    /**
+     * Handles a record.
+     *
+     * All records may be passed to this method, and the handler should discard
+     * those that it does not want to handle.
+     *
+     * The return value of this function controls the bubbling process of the handler stack.
+     * Unless the bubbling is interrupted (by returning true), the Logger class will keep on
+     * calling further handlers in the stack with a given log record.
+     *
+     * @param array $record The record to handle
+     *
+     * @return Boolean true means that this handler handled the record, and that bubbling is not permitted.
+     *                 false means the record was either not processed or that this handler allows bubbling.
+     */
+    public function handle(array $record): bool
+    {
+        if ($record['level'] < $this->level) {
+            return false;
+        }
+
+        $level = $record['level'];
+
+        if (!isset($this->thresholdMap[$level])) {
+            $this->thresholdMap[$level] = 0;
+        }
+
+        if ($this->thresholdMap[$level] > 0) {
+            // The overflow threshold is not yet reached, so we're buffering the record and lowering the threshold by 1
+            $this->thresholdMap[$level]--;
+            $this->buffer[$level][] = $record;
+
+            return false === $this->bubble;
+        }
+
+        if ($this->thresholdMap[$level] == 0) {
+            // This current message is breaking the threshold. Flush the buffer and continue handling the current record
+            foreach ($this->buffer[$level] ?? [] as $buffered) {
+                $this->handler->handle($buffered);
+            }
+            $this->thresholdMap[$level]--;
+            unset($this->buffer[$level]);
+        }
+
+        $this->handler->handle($record);
+
+        return false === $this->bubble;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setFormatter(FormatterInterface $formatter): HandlerInterface
+    {
+        $this->handler->setFormatter($formatter);
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getFormatter(): FormatterInterface
+    {
+        return $this->handler->getFormatter();
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php
new file mode 100644
index 0000000..36e30b8
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php
@@ -0,0 +1,193 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Logger;
+
+/**
+ * Stores to STDIN of any process, specified by a command.
+ *
+ * Usage example:
+ * <pre>
+ * $log = new Logger('myLogger');
+ * $log->pushHandler(new ProcessHandler('/usr/bin/php /var/www/monolog/someScript.php'));
+ * </pre>
+ *
+ * @author Kolja Zuelsdorf <koljaz@web.de>
+ */
+class ProcessHandler extends AbstractProcessingHandler
+{
+    /**
+     * Holds the process to receive data on its STDIN.
+     *
+     * @var resource|bool|null
+     */
+    private $process;
+
+    /**
+     * @var string
+     */
+    private $command;
+
+    /**
+     * @var string|null
+     */
+    private $cwd;
+
+    /**
+     * @var array
+     */
+    private $pipes = [];
+
+    /**
+     * @var array
+     */
+    protected const DESCRIPTOR_SPEC = [
+        0 => ['pipe', 'r'],  // STDIN is a pipe that the child will read from
+        1 => ['pipe', 'w'],  // STDOUT is a pipe that the child will write to
+        2 => ['pipe', 'w'],  // STDERR is a pipe to catch the any errors
+    ];
+
+    /**
+     * @param  string                    $command Command for the process to start. Absolute paths are recommended,
+     *                                            especially if you do not use the $cwd parameter.
+     * @param  string|int                $level   The minimum logging level at which this handler will be triggered.
+     * @param  bool                      $bubble  Whether the messages that are handled can bubble up the stack or not.
+     * @param  string|null               $cwd     "Current working directory" (CWD) for the process to be executed in.
+     * @throws \InvalidArgumentException
+     */
+    public function __construct(string $command, $level = Logger::DEBUG, bool $bubble = true, ?string $cwd = null)
+    {
+        if ($command === '') {
+            throw new \InvalidArgumentException('The command argument must be a non-empty string.');
+        }
+        if ($cwd === '') {
+            throw new \InvalidArgumentException('The optional CWD argument must be a non-empty string or null.');
+        }
+
+        parent::__construct($level, $bubble);
+
+        $this->command = $command;
+        $this->cwd = $cwd;
+    }
+
+    /**
+     * Writes the record down to the log of the implementing handler
+     *
+     * @throws \UnexpectedValueException
+     */
+    protected function write(array $record): void
+    {
+        $this->ensureProcessIsStarted();
+
+        $this->writeProcessInput($record['formatted']);
+
+        $errors = $this->readProcessErrors();
+        if (empty($errors) === false) {
+            throw new \UnexpectedValueException(sprintf('Errors while writing to process: %s', $errors));
+        }
+    }
+
+    /**
+     * Makes sure that the process is actually started, and if not, starts it,
+     * assigns the stream pipes, and handles startup errors, if any.
+     */
+    private function ensureProcessIsStarted(): void
+    {
+        if (is_resource($this->process) === false) {
+            $this->startProcess();
+
+            $this->handleStartupErrors();
+        }
+    }
+
+    /**
+     * Starts the actual process and sets all streams to non-blocking.
+     */
+    private function startProcess(): void
+    {
+        $this->process = proc_open($this->command, static::DESCRIPTOR_SPEC, $this->pipes, $this->cwd);
+
+        foreach ($this->pipes as $pipe) {
+            stream_set_blocking($pipe, false);
+        }
+    }
+
+    /**
+     * Selects the STDERR stream, handles upcoming startup errors, and throws an exception, if any.
+     *
+     * @throws \UnexpectedValueException
+     */
+    private function handleStartupErrors(): void
+    {
+        $selected = $this->selectErrorStream();
+        if (false === $selected) {
+            throw new \UnexpectedValueException('Something went wrong while selecting a stream.');
+        }
+
+        $errors = $this->readProcessErrors();
+
+        if (is_resource($this->process) === false || empty($errors) === false) {
+            throw new \UnexpectedValueException(
+                sprintf('The process "%s" could not be opened: ' . $errors, $this->command)
+            );
+        }
+    }
+
+    /**
+     * Selects the STDERR stream.
+     *
+     * @return int|bool
+     */
+    protected function selectErrorStream()
+    {
+        $empty = [];
+        $errorPipes = [$this->pipes[2]];
+
+        return stream_select($errorPipes, $empty, $empty, 1);
+    }
+
+    /**
+     * Reads the errors of the process, if there are any.
+     *
+     * @codeCoverageIgnore
+     * @return string Empty string if there are no errors.
+     */
+    protected function readProcessErrors(): string
+    {
+        return stream_get_contents($this->pipes[2]);
+    }
+
+    /**
+     * Writes to the input stream of the opened process.
+     *
+     * @codeCoverageIgnore
+     */
+    protected function writeProcessInput(string $string): void
+    {
+        fwrite($this->pipes[0], $string);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function close(): void
+    {
+        if (is_resource($this->process)) {
+            foreach ($this->pipes as $pipe) {
+                fclose($pipe);
+            }
+            proc_close($this->process);
+            $this->process = null;
+        }
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php
new file mode 100644
index 0000000..2c9557b
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php
@@ -0,0 +1,38 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Processor\ProcessorInterface;
+
+/**
+ * Interface to describe loggers that have processors
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+interface ProcessableHandlerInterface
+{
+    /**
+     * Adds a processor in the stack.
+     *
+     * @param  ProcessorInterface|callable $callback
+     * @return HandlerInterface            self
+     */
+    public function pushProcessor(callable $callback): HandlerInterface;
+
+    /**
+     * Removes the processor on top of the stack and returns it.
+     *
+     * @throws \LogicException In case the processor stack is empty
+     * @return callable
+     */
+    public function popProcessor(): callable;
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php
new file mode 100644
index 0000000..c4c38ec
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php
@@ -0,0 +1,71 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\ResettableInterface;
+
+/**
+ * Helper trait for implementing ProcessableInterface
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+trait ProcessableHandlerTrait
+{
+    /**
+     * @var callable[]
+     */
+    protected $processors = [];
+
+    /**
+     * {@inheritdoc}
+     * @suppress PhanTypeMismatchReturn
+     */
+    public function pushProcessor(callable $callback): HandlerInterface
+    {
+        array_unshift($this->processors, $callback);
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function popProcessor(): callable
+    {
+        if (!$this->processors) {
+            throw new \LogicException('You tried to pop from an empty processor stack.');
+        }
+
+        return array_shift($this->processors);
+    }
+
+    /**
+     * Processes a record.
+     */
+    protected function processRecord(array $record): array
+    {
+        foreach ($this->processors as $processor) {
+            $record = $processor($record);
+        }
+
+        return $record;
+    }
+
+    protected function resetProcessors(): void
+    {
+        foreach ($this->processors as $processor) {
+            if ($processor instanceof ResettableInterface) {
+                $processor->reset();
+            }
+        }
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php
new file mode 100644
index 0000000..c154a5d
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php
@@ -0,0 +1,100 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Logger;
+
+/**
+ * SendGridrHandler uses the SendGrid API v2 function to send Log emails, more information in https://sendgrid.com/docs/API_Reference/Web_API/mail.html
+ *
+ * @author Ricardo Fontanelli <ricardo.fontanelli@hotmail.com>
+ */
+class SendGridHandler extends MailHandler
+{
+    /**
+     * The SendGrid API User
+     * @var string
+     */
+    protected $apiUser;
+
+    /**
+     * The SendGrid API Key
+     * @var string
+     */
+    protected $apiKey;
+
+    /**
+     * The email addresses to which the message will be sent
+     * @var string
+     */
+    protected $from;
+
+    /**
+     * The email addresses to which the message will be sent
+     * @var array
+     */
+    protected $to;
+
+    /**
+     * The subject of the email
+     * @var string
+     */
+    protected $subject;
+
+    /**
+     * @param string       $apiUser The SendGrid API User
+     * @param string       $apiKey  The SendGrid API Key
+     * @param string       $from    The sender of the email
+     * @param string|array $to      The recipients of the email
+     * @param string       $subject The subject of the mail
+     * @param int          $level   The minimum logging level at which this handler will be triggered
+     * @param bool         $bubble  Whether the messages that are handled can bubble up the stack or not
+     */
+    public function __construct(string $apiUser, string $apiKey, string $from, $to, string $subject, int $level = Logger::ERROR, bool $bubble = true)
+    {
+        parent::__construct($level, $bubble);
+        $this->apiUser = $apiUser;
+        $this->apiKey = $apiKey;
+        $this->from = $from;
+        $this->to = (array) $to;
+        $this->subject = $subject;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function send(string $content, array $records): void
+    {
+        $message = [];
+        $message['api_user'] = $this->apiUser;
+        $message['api_key'] = $this->apiKey;
+        $message['from'] = $this->from;
+        foreach ($this->to as $recipient) {
+            $message['to[]'] = $recipient;
+        }
+        $message['subject'] = $this->subject;
+        $message['date'] = date('r');
+
+        if ($this->isHtmlBody($content)) {
+            $message['html'] = $content;
+        } else {
+            $message['text'] = $content;
+        }
+
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, 'https://api.sendgrid.com/api/mail.send.json');
+        curl_setopt($ch, CURLOPT_POST, 1);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($message));
+        Curl\Util::execute($ch, 2);
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php
new file mode 100644
index 0000000..a98c87b
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php
@@ -0,0 +1,64 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Aws\Sqs\SqsClient;
+use Monolog\Logger;
+use Monolog\Utils;
+
+/**
+ * Writes to any sqs queue.
+ *
+ * @author Martijn van Calker <git@amvc.nl>
+ */
+class SqsHandler extends AbstractProcessingHandler
+{
+    /** 256 KB in bytes - maximum message size in SQS */
+    protected const MAX_MESSAGE_SIZE = 262144;
+    /** 100 KB in bytes - head message size for new error log */
+    protected const HEAD_MESSAGE_SIZE = 102400;
+
+    /** @var SqsClient */
+    private $client;
+    /** @var string */
+    private $queueUrl;
+
+    public function __construct(SqsClient $sqsClient, string $queueUrl, $level = Logger::DEBUG, bool $bubble = true)
+    {
+        parent::__construct($level, $bubble);
+
+        $this->client = $sqsClient;
+        $this->queueUrl = $queueUrl;
+    }
+
+    /**
+     * Writes the record down to the log of the implementing handler.
+     *
+     * @param array $record
+     */
+    protected function write(array $record): void
+    {
+        if (!isset($record['formatted']) || 'string' !== gettype($record['formatted'])) {
+            throw new \InvalidArgumentException('SqsHandler accepts only formatted records as a string');
+        }
+
+        $messageBody = $record['formatted'];
+        if (strlen($messageBody) >= static::MAX_MESSAGE_SIZE) {
+            $messageBody = Utils::substr($messageBody, 0, static::HEAD_MESSAGE_SIZE);
+        }
+
+        $this->client->sendMessage([
+            'QueueUrl' => $this->queueUrl,
+            'MessageBody' => $messageBody,
+        ]);
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php
new file mode 100644
index 0000000..5dae9f5
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php
@@ -0,0 +1,99 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use RuntimeException;
+use Monolog\Logger;
+
+/**
+ * Handler send logs to Telegram using Telegram Bot API.
+ *
+ * How to use:
+ *  1) Create telegram bot with https://telegram.me/BotFather
+ *  2) Create a telegram channel where logs will be recorded.
+ *  3) Add created bot from step 1 to the created channel from step 2.
+ *
+ * Use telegram bot API key from step 1 and channel name with '@' prefix from step 2 to create instance of TelegramBotHandler
+ *
+ * @link https://core.telegram.org/bots/api
+ *
+ * @author Mazur Alexandr <alexandrmazur96@gmail.com>
+ */
+class TelegramBotHandler extends AbstractProcessingHandler
+{
+    private const BOT_API = 'https://api.telegram.org/bot';
+
+    /**
+     * Telegram bot access token provided by BotFather.
+     * Create telegram bot with https://telegram.me/BotFather and use access token from it.
+     * @var string
+     */
+    private $apiKey;
+
+    /**
+     * Telegram channel name.
+     * Since to start with '@' symbol as prefix.
+     * @var string
+     */
+    private $channel;
+
+    /**
+     * @param string $apiKey  Telegram bot access token provided by BotFather
+     * @param string $channel Telegram channel name
+     * @inheritDoc
+     */
+    public function __construct(
+        string $apiKey,
+        string $channel,
+        $level = Logger::DEBUG,
+        bool $bubble = true
+    ) {
+        parent::__construct($level, $bubble);
+
+        $this->apiKey = $apiKey;
+        $this->channel = $channel;
+        $this->level = $level;
+        $this->bubble = $bubble;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    protected function write(array $record): void
+    {
+        $this->send($record['formatted']);
+    }
+
+    /**
+     * Send request to @link https://api.telegram.org/bot on SendMessage action.
+     * @param string $message
+     */
+    protected function send(string $message): void
+    {
+        $ch = curl_init();
+        $url = self::BOT_API . $this->apiKey . '/SendMessage';
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
+            'text' => $message,
+            'chat_id' => $this->channel,
+        ]));
+
+        $result = Curl\Util::execute($ch);
+        $result = json_decode($result, true);
+
+        if ($result['ok'] === false) {
+            throw new RuntimeException('Telegram API error. Description: ' . $result['description']);
+        }
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php
new file mode 100644
index 0000000..c818352
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php
@@ -0,0 +1,24 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+trait WebRequestRecognizerTrait
+{
+    /**
+     * Checks if PHP's serving a web request
+     * @return bool
+     */
+    protected function isWebRequest(): bool
+    {
+        return 'cli' !== \PHP_SAPI && 'phpdbg' !== \PHP_SAPI;
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php
new file mode 100644
index 0000000..7c23db8
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php
@@ -0,0 +1,32 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+/**
+ * Injects value of gethostname in all records
+ */
+class HostnameProcessor implements ProcessorInterface
+{
+    private static $host;
+
+    public function __construct()
+    {
+        self::$host = (string) gethostname();
+    }
+
+    public function __invoke(array $record): array
+    {
+        $record['extra']['hostname'] = self::$host;
+
+        return $record;
+    }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Test/TestCase.php b/vendor/monolog/monolog/src/Monolog/Test/TestCase.php
new file mode 100644
index 0000000..ecb8907
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Test/TestCase.php
@@ -0,0 +1,66 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Test;
+
+use Monolog\Logger;
+use Monolog\DateTimeImmutable;
+use Monolog\Formatter\FormatterInterface;
+
+/**
+ * Lets you easily generate log records and a dummy formatter for testing purposes
+ * *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class TestCase extends \PHPUnit\Framework\TestCase
+{
+    /**
+     * @return array Record
+     */
+    protected function getRecord($level = Logger::WARNING, $message = 'test', array $context = []): array
+    {
+        return [
+            'message' => (string) $message,
+            'context' => $context,
+            'level' => $level,
+            'level_name' => Logger::getLevelName($level),
+            'channel' => 'test',
+            'datetime' => new DateTimeImmutable(true),
+            'extra' => [],
+        ];
+    }
+
+    protected function getMultipleRecords(): array
+    {
+        return [
+            $this->getRecord(Logger::DEBUG, 'debug message 1'),
+            $this->getRecord(Logger::DEBUG, 'debug message 2'),
+            $this->getRecord(Logger::INFO, 'information'),
+            $this->getRecord(Logger::WARNING, 'warning'),
+            $this->getRecord(Logger::ERROR, 'error'),
+        ];
+    }
+
+    /**
+     * @suppress PhanTypeMismatchReturn
+     */
+    protected function getIdentityFormatter(): FormatterInterface
+    {
+        $formatter = $this->createMock(FormatterInterface::class);
+        $formatter->expects($this->any())
+            ->method('format')
+            ->will($this->returnCallback(function ($record) {
+                return $record['message'];
+            }));
+
+        return $formatter;
+    }
+}
diff --git a/vendor/overtrue/socialite/.github/FUNDING.yml b/vendor/overtrue/socialite/.github/FUNDING.yml
new file mode 100644
index 0000000..a3be7fa
--- /dev/null
+++ b/vendor/overtrue/socialite/.github/FUNDING.yml
@@ -0,0 +1,9 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: overtrue
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+custom: # Replace with a single custom sponsorship URL
diff --git a/vendor/overtrue/socialite/.gitignore b/vendor/overtrue/socialite/.gitignore
new file mode 100644
index 0000000..d6eb268
--- /dev/null
+++ b/vendor/overtrue/socialite/.gitignore
@@ -0,0 +1,9 @@
+/vendor
+composer.phar
+composer.lock
+.DS_Store
+/.idea
+Thumbs.db
+/*.php
+sftp-config.json
+.php_cs.cache
\ No newline at end of file
diff --git a/vendor/overtrue/socialite/src/Providers/DouYinProvider.php b/vendor/overtrue/socialite/src/Providers/DouYinProvider.php
new file mode 100644
index 0000000..6e0b108
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/DouYinProvider.php
@@ -0,0 +1,168 @@
+<?php
+
+namespace Overtrue\Socialite\Providers;
+
+use Overtrue\Socialite\AccessToken;
+use Overtrue\Socialite\AccessTokenInterface;
+use Overtrue\Socialite\ProviderInterface;
+use Overtrue\Socialite\User;
+
+/**
+ * Class DouYinProvider.
+ *
+ * @author haoliang@qiyuankeji.vip
+ *
+ * @see http://open.douyin.com/platform
+ */
+class DouYinProvider extends AbstractProvider implements ProviderInterface
+{
+    /**
+     * 抖音接口域名.
+     *
+     * @var string
+     */
+    protected $baseUrl = 'https://open.douyin.com';
+
+    /**
+     * 应用授权作用域.
+     *
+     * @var array
+     */
+    protected $scopes = ['user_info'];
+
+    /**
+     * 获取登录页面地址.
+     *
+     * {@inheritdoc}
+     */
+    protected function getAuthUrl($state)
+    {
+        return $this->buildAuthUrlFromBase($this->baseUrl.'/platform/oauth/connect', $state);
+    }
+
+    /**
+     * 获取授权码接口参数.
+     *
+     * @param string|null $state
+     *
+     * @return array
+     */
+    public function getCodeFields($state = null)
+    {
+        $fields = [
+            'client_key' => $this->clientId,
+            'redirect_uri' => $this->redirectUrl,
+            'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator),
+            'response_type' => 'code',
+        ];
+
+        if ($this->usesState()) {
+            $fields['state'] = $state;
+        }
+
+        return $fields;
+    }
+
+    /**
+     * 获取access_token地址.
+     *
+     * {@inheritdoc}
+     */
+    protected function getTokenUrl()
+    {
+        return $this->baseUrl.'/oauth/access_token';
+    }
+
+    /**
+     * 通过code获取access_token.
+     *
+     * @param string $code
+     *
+     * @return \Overtrue\Socialite\AccessToken
+     */
+    public function getAccessToken($code)
+    {
+        $response = $this->getHttpClient()->get($this->getTokenUrl(), [
+            'query' => $this->getTokenFields($code),
+        ]);
+
+        return $this->parseAccessToken($response->getBody()->getContents());
+    }
+
+    /**
+     * 获取access_token接口参数.
+     *
+     * @param string $code
+     *
+     * @return array
+     */
+    protected function getTokenFields($code)
+    {
+        return [
+            'client_key' => $this->clientId,
+            'client_secret' => $this->clientSecret,
+            'code' => $code,
+            'grant_type' => 'authorization_code',
+        ];
+    }
+
+    /**
+     * 格式化token.
+     *
+     * @param \Psr\Http\Message\StreamInterface|array $body
+     *
+     * @return \Overtrue\Socialite\AccessTokenInterface
+     */
+    protected function parseAccessToken($body)
+    {
+        if (!is_array($body)) {
+            $body = json_decode($body, true);
+        }
+
+        if (empty($body['data']['access_token'])) {
+            throw new AuthorizeFailedException('Authorize Failed: '.json_encode($body, JSON_UNESCAPED_UNICODE), $body);
+        }
+
+        return new AccessToken($body['data']);
+    }
+
+    /**
+     * 通过token 获取用户信息.
+     *
+     * @param AccessTokenInterface $token
+     *
+     * @return array|mixed
+     */
+    protected function getUserByToken(AccessTokenInterface $token)
+    {
+        $userUrl = $this->baseUrl.'/oauth/userinfo/';
+
+        $response = $this->getHttpClient()->get(
+            $userUrl, [
+                'query' => [
+                    'access_token' => $token->getToken(),
+                    'open_id' => $token['open_id'],
+                ],
+            ]
+        );
+
+        return json_decode($response->getBody(), true);
+    }
+
+    /**
+     * 格式化用户信息.
+     *
+     * @param array $user
+     *
+     * @return User
+     */
+    protected function mapUserToObject(array $user)
+    {
+        return new User([
+            'id' => $this->arrayItem($user, 'open_id'),
+            'username' => $this->arrayItem($user, 'nickname'),
+            'nickname' => $this->arrayItem($user, 'nickname'),
+            'avatar' => $this->arrayItem($user, 'avatar'),
+        ]);
+    }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/OutlookProvider.php b/vendor/overtrue/socialite/src/Providers/OutlookProvider.php
new file mode 100644
index 0000000..3a0fb56
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/OutlookProvider.php
@@ -0,0 +1,88 @@
+<?php
+
+/*
+ * This file is part of the overtrue/socialite.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Overtrue\Socialite\Providers;
+
+use Overtrue\Socialite\AccessTokenInterface;
+use Overtrue\Socialite\ProviderInterface;
+use Overtrue\Socialite\User;
+
+/**
+ * Class OutlookProvider.
+ */
+class OutlookProvider extends AbstractProvider implements ProviderInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    protected $scopes = ['User.Read'];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected $scopeSeparator = ' ';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthUrl($state)
+    {
+        return $this->buildAuthUrlFromBase('https://login.microsoftonline.com/common/oauth2/v2.0/authorize', $state);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getTokenUrl()
+    {
+        return 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getUserByToken(AccessTokenInterface $token)
+    {
+        $response = $this->getHttpClient()->get(
+            'https://graph.microsoft.com/v1.0/me',
+            ['headers' => [
+                'Accept' => 'application/json',
+                'Authorization' => 'Bearer '.$token->getToken(),
+            ],
+            ]);
+
+        return json_decode($response->getBody()->getContents(), true);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function mapUserToObject(array $user)
+    {
+        return new User([
+            'id' => $this->arrayItem($user, 'id'),
+            'nickname' => null,
+            'name' => $this->arrayItem($user, 'displayName'),
+            'email' => $this->arrayItem($user, 'userPrincipalName'),
+            'avatar' => null,
+        ]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getTokenFields($code)
+    {
+        return array_merge(parent::getTokenFields($code), [
+            'grant_type' => 'authorization_code',
+        ]);
+    }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/TaobaoProvider.php b/vendor/overtrue/socialite/src/Providers/TaobaoProvider.php
new file mode 100644
index 0000000..f7da073
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/TaobaoProvider.php
@@ -0,0 +1,242 @@
+<?php
+
+/*
+ * This file is part of the overtrue/socialite.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Overtrue\Socialite\Providers;
+
+use Overtrue\Socialite\AccessTokenInterface;
+use Overtrue\Socialite\ProviderInterface;
+use Overtrue\Socialite\User;
+
+/**
+ * Class TaobaoProvider.
+ *
+ * @author mechono <haodouliu@gmail.com>
+ *
+ * @see    https://open.taobao.com/doc.htm?docId=102635&docType=1&source=search [Taobao - OAuth 2.0 授权登录]
+ */
+class TaobaoProvider extends AbstractProvider implements ProviderInterface
+{
+    /**
+     * The base url of Taobao API.
+     *
+     * @var string
+     */
+    protected $baseUrl = 'https://oauth.taobao.com';
+
+    /**
+     * Taobao API service URL address.
+     *
+     * @var string
+     */
+    protected $gatewayUrl = 'https://eco.taobao.com/router/rest';
+
+    /**
+     * The API version for the request.
+     *
+     * @var string
+     */
+    protected $version = '2.0';
+
+    /**
+     * @var string
+     */
+    protected $format = 'json';
+
+    /**
+     * @var string
+     */
+    protected $signMethod = 'md5';
+
+    /**
+     * Web 对应 PC 端(淘宝 logo )浏览器页面样式;Tmall 对应天猫的浏览器页面样式;Wap 对应无线端的浏览器页面样式。
+     */
+    protected $view = 'web';
+
+    /**
+     * The scopes being requested.
+     *
+     * @var array
+     */
+    protected $scopes = ['user_info'];
+
+    /**
+     * Get the authentication URL for the provider.
+     *
+     * @param string $state
+     *
+     * @return string
+     */
+    protected function getAuthUrl($state)
+    {
+        return $this->buildAuthUrlFromBase($this->baseUrl.'/authorize', $state);
+    }
+
+    /**
+     * 获取授权码接口参数.
+     *
+     * @param string|null $state
+     *
+     * @return array
+     */
+    public function getCodeFields($state = null)
+    {
+        $fields = [
+            'client_id' => $this->clientId,
+            'redirect_uri' => $this->redirectUrl,
+            'view' => $this->view,
+            'response_type' => 'code',
+        ];
+
+        if ($this->usesState()) {
+            $fields['state'] = $state;
+        }
+
+        return $fields;
+    }
+
+    /**
+     * Get the token URL for the provider.
+     *
+     * @return string
+     */
+    protected function getTokenUrl()
+    {
+        return $this->baseUrl.'/token';
+    }
+
+    /**
+     * Get the Post fields for the token request.
+     *
+     * @param string $code
+     *
+     * @return array
+     */
+    protected function getTokenFields($code)
+    {
+        return parent::getTokenFields($code) + ['grant_type' => 'authorization_code', 'view' => $this->view];
+    }
+
+    /**
+     * Get the access token for the given code.
+     *
+     * @param string $code
+     *
+     * @return \Overtrue\Socialite\AccessToken
+     */
+    public function getAccessToken($code)
+    {
+        $response = $this->getHttpClient()->post($this->getTokenUrl(), [
+            'query' => $this->getTokenFields($code),
+        ]);
+
+        return $this->parseAccessToken($response->getBody()->getContents());
+    }
+
+    /**
+     * Get the access token from the token response body.
+     *
+     * @param string $body
+     *
+     * @return \Overtrue\Socialite\AccessToken
+     */
+    public function parseAccessToken($body)
+    {
+        return parent::parseAccessToken($body);
+    }
+
+    /**
+     * Get the raw user for the given access token.
+     *
+     * @param \Overtrue\Socialite\AccessTokenInterface $token
+     *
+     * @return array
+     */
+    protected function getUserByToken(AccessTokenInterface $token)
+    {
+        $response = $this->getHttpClient()->post($this->getUserInfoUrl($this->gatewayUrl, $token));
+
+        return json_decode($response->getBody(), true);
+    }
+
+    /**
+     * Map the raw user array to a Socialite User instance.
+     *
+     * @param array $user
+     *
+     * @return \Overtrue\Socialite\User
+     */
+    protected function mapUserToObject(array $user)
+    {
+        return new User([
+            'id' => $this->arrayItem($user, 'open_id'),
+            'nickname' => $this->arrayItem($user, 'nick'),
+            'name' => $this->arrayItem($user, 'nick'),
+            'avatar' => $this->arrayItem($user, 'avatar'),
+        ]);
+    }
+
+    /**
+     * @param $params
+     *
+     * @return string
+     */
+    protected function generateSign($params)
+    {
+        ksort($params);
+
+        $stringToBeSigned = $this->clientSecret;
+
+        foreach ($params as $k => $v) {
+            if (!is_array($v) && '@' != substr($v, 0, 1)) {
+                $stringToBeSigned .= "$k$v";
+            }
+        }
+
+        $stringToBeSigned .= $this->clientSecret;
+
+        return strtoupper(md5($stringToBeSigned));
+    }
+
+    /**
+     * @param \Overtrue\Socialite\AccessTokenInterface $token
+     * @param array                                    $apiFields
+     *
+     * @return array
+     */
+    protected function getPublicFields(AccessTokenInterface $token, array $apiFields = [])
+    {
+        $fields = [
+            'app_key' => $this->clientId,
+            'sign_method' => $this->signMethod,
+            'session' => $token->getToken(),
+            'timestamp' => date('Y-m-d H:i:s'),
+            'v' => $this->version,
+            'format' => $this->format,
+        ];
+
+        $fields = array_merge($apiFields, $fields);
+        $fields['sign'] = $this->generateSign($fields);
+
+        return $fields;
+    }
+
+    /**
+     * {@inheritdoc}.
+     */
+    protected function getUserInfoUrl($url, AccessTokenInterface $token)
+    {
+        $apiFields = ['method' => 'taobao.miniapp.userInfo.get'];
+
+        $query = http_build_query($this->getPublicFields($token, $apiFields), '', '&', $this->encodingType);
+
+        return $url.'?'.$query;
+    }
+}
diff --git a/vendor/overtrue/socialite/tests/UserTest.php b/vendor/overtrue/socialite/tests/UserTest.php
new file mode 100644
index 0000000..dd42010
--- /dev/null
+++ b/vendor/overtrue/socialite/tests/UserTest.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of the overtrue/socialite.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+use Overtrue\Socialite\AccessToken;
+use Overtrue\Socialite\User;
+use PHPUnit\Framework\TestCase;
+
+class UserTest extends TestCase
+{
+    public function testJsonserialize()
+    {
+        $this->assertSame('[]', json_encode(new User([])));
+
+        $this->assertSame('{"token":"mock-token"}', json_encode(new User(['token' => new AccessToken(['access_token' => 'mock-token'])])));
+    }
+}
diff --git a/vendor/overtrue/wechat/CHANGELOG.md b/vendor/overtrue/wechat/CHANGELOG.md
new file mode 100644
index 0000000..df42aa6
--- /dev/null
+++ b/vendor/overtrue/wechat/CHANGELOG.md
@@ -0,0 +1,1401 @@
+# Change Log
+
+## [Unreleased](https://github.com/overtrue/wechat/tree/HEAD)
+
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0...HEAD)
+
+**Closed issues:**
+
+- 能否增加对symfony4的支持 [\#1044](https://github.com/overtrue/wechat/issues/1044)
+
+## [4.0.0](https://github.com/overtrue/wechat/tree/4.0.0) (2017-12-11)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.21...4.0.0)
+
+**Closed issues:**
+
+- filecache.php 文件     createPathIfNeeded\(string $path\) : bool [\#1046](https://github.com/overtrue/wechat/issues/1046)
+- 沙箱模式的Notify总是出错:Invalid request payloads. [\#1045](https://github.com/overtrue/wechat/issues/1045)
+- 你好我是SwooleDistributed框架的作者 [\#1040](https://github.com/overtrue/wechat/issues/1040)
+
+## [3.3.21](https://github.com/overtrue/wechat/tree/3.3.21) (2017-12-10)
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.4...3.3.21)
+
+**Closed issues:**
+
+- 开启开放平台自动路由后报错 [\#1042](https://github.com/overtrue/wechat/issues/1042)
+- 关于3.x升级4.x的问题 [\#1041](https://github.com/overtrue/wechat/issues/1041)
+- 获取不了unionid [\#1038](https://github.com/overtrue/wechat/issues/1038)
+- authorizer\_refresh\_token刷新问题 [\#1033](https://github.com/overtrue/wechat/issues/1033)
+- lumen+swoole无法获取request信息。 [\#1032](https://github.com/overtrue/wechat/issues/1032)
+- 上传素材报错, empty post data hint  [\#1031](https://github.com/overtrue/wechat/issues/1031)
+- 开放平台不支持全网发布接入检测(第三方) [\#1029](https://github.com/overtrue/wechat/issues/1029)
+- 公众号模板消息不兼容跳转小程序 [\#1025](https://github.com/overtrue/wechat/issues/1025)
+- swoole下无法使用 [\#1017](https://github.com/overtrue/wechat/issues/1017)
+- 请教有没有高清素材下载方法? [\#997](https://github.com/overtrue/wechat/issues/997)
+- 自动回复多图文素材,错误 [\#996](https://github.com/overtrue/wechat/issues/996)
+- xml解释失败 [\#989](https://github.com/overtrue/wechat/issues/989)
+- Curl error 77 [\#982](https://github.com/overtrue/wechat/issues/982)
+- 3.1.10 H5支付不晓得算不算BUG的BUG [\#968](https://github.com/overtrue/wechat/issues/968)
+- 请问是否有遇到微信扫码或内部打开外部网站出现请求2次的情况 [\#963](https://github.com/overtrue/wechat/issues/963)
+- 请问4.0何时正式发布? [\#962](https://github.com/overtrue/wechat/issues/962)
+- dev-master 不能用于laravel5.1 [\#952](https://github.com/overtrue/wechat/issues/952)
+- 请教小程序的模板消息是否支持 [\#920](https://github.com/overtrue/wechat/issues/920)
+- 模板消息的颜色设置问题 [\#914](https://github.com/overtrue/wechat/issues/914)
+- 英文文档跳转问题 [\#854](https://github.com/overtrue/wechat/issues/854)
+- \[4.0\] 功能测试 [\#849](https://github.com/overtrue/wechat/issues/849)
+- \[4.0\] 命名变更 [\#743](https://github.com/overtrue/wechat/issues/743)
+
+**Merged pull requests:**
+
+- Scrutinizer Auto-Fixes [\#1043](https://github.com/overtrue/wechat/pull/1043) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- 修复解密小程序转发信息数据\(wx.getShareInfo\)失败的问题 [\#1037](https://github.com/overtrue/wechat/pull/1037) ([yyqqing](https://github.com/yyqqing))
+- 修復微信支付沙盒模式的通知結果本地校驗失敗錯誤。 [\#1036](https://github.com/overtrue/wechat/pull/1036) ([amyuki](https://github.com/amyuki))
+- 修复 verifyTicket 使用不了自定义缓存的问题 [\#1034](https://github.com/overtrue/wechat/pull/1034) ([mingyoung](https://github.com/mingyoung))
+- 🚧 Auto discover extensions. [\#1027](https://github.com/overtrue/wechat/pull/1027) ([mingyoung](https://github.com/mingyoung))
+
+## [4.0.0-beta.4](https://github.com/overtrue/wechat/tree/4.0.0-beta.4) (2017-11-21)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.20...4.0.0-beta.4)
+
+**Closed issues:**
+
+- Order对象的$attributes中不能传入device\_info参数 [\#1030](https://github.com/overtrue/wechat/issues/1030)
+- 默认文件缓存的路径是否可以简单修改? [\#1023](https://github.com/overtrue/wechat/issues/1023)
+- 3.3.17 版本获取 token 的问题 [\#1022](https://github.com/overtrue/wechat/issues/1022)
+- \[V3\] AccessToken.php:243 [\#1021](https://github.com/overtrue/wechat/issues/1021)
+
+**Merged pull requests:**
+
+- more detailed cache key. [\#1028](https://github.com/overtrue/wechat/pull/1028) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#1026](https://github.com/overtrue/wechat/pull/1026) ([mingyoung](https://github.com/mingyoung))
+- Specify the request instance. [\#1024](https://github.com/overtrue/wechat/pull/1024) ([mingyoung](https://github.com/mingyoung))
+
+## [3.3.20](https://github.com/overtrue/wechat/tree/3.3.20) (2017-11-13)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.18...3.3.20)
+
+## [3.3.18](https://github.com/overtrue/wechat/tree/3.3.18) (2017-11-11)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.17...3.3.18)
+
+**Closed issues:**
+
+- 临时二维码接口无法生成以字符串为参数的二维码 [\#1020](https://github.com/overtrue/wechat/issues/1020)
+- 现金红包出现了500错误,跟进显示http\_error:true [\#1016](https://github.com/overtrue/wechat/issues/1016)
+- 4.0 企业微信agent OA的错误 [\#1015](https://github.com/overtrue/wechat/issues/1015)
+- 求thinkphp框架demo [\#1010](https://github.com/overtrue/wechat/issues/1010)
+- 沙箱模式获取验签 key 时产生无限循环 , 无法正常获取 [\#1009](https://github.com/overtrue/wechat/issues/1009)
+- JSSDK里面url导致的invalid signature错误 [\#1002](https://github.com/overtrue/wechat/issues/1002)
+- 微信支付沙箱模式下,回调验签错误 [\#998](https://github.com/overtrue/wechat/issues/998)
+- 有微信退款回调接口吗? [\#985](https://github.com/overtrue/wechat/issues/985)
+- 希望兼容新出的微信H5支付 [\#966](https://github.com/overtrue/wechat/issues/966)
+- 小程序生成无限量二维码接口缺少参数 [\#965](https://github.com/overtrue/wechat/issues/965)
+
+**Merged pull requests:**
+
+- 查询企业付款接口参数调整,加入企业付款到银行卡接口(RSA 参数加密待完成) [\#1019](https://github.com/overtrue/wechat/pull/1019) ([tianyong90](https://github.com/tianyong90))
+- Token AESKey can be null. [\#1013](https://github.com/overtrue/wechat/pull/1013) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#1012](https://github.com/overtrue/wechat/pull/1012) ([mingyoung](https://github.com/mingyoung))
+- Add Mini-program tester's binding/unbinding feature [\#1011](https://github.com/overtrue/wechat/pull/1011) ([caikeal](https://github.com/caikeal))
+- Apply fixes from StyleCI [\#1008](https://github.com/overtrue/wechat/pull/1008) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#1007](https://github.com/overtrue/wechat/pull/1007) ([overtrue](https://github.com/overtrue))
+- Added open-platform's mini-program code management [\#1003](https://github.com/overtrue/wechat/pull/1003) ([caikeal](https://github.com/caikeal))
+- Cleanup payment [\#1001](https://github.com/overtrue/wechat/pull/1001) ([mingyoung](https://github.com/mingyoung))
+- Unify get stream. [\#995](https://github.com/overtrue/wechat/pull/995) ([mingyoung](https://github.com/mingyoung))
+- Add appCode `page` param. [\#991](https://github.com/overtrue/wechat/pull/991) ([mingyoung](https://github.com/mingyoung))
+
+## [3.3.17](https://github.com/overtrue/wechat/tree/3.3.17) (2017-10-27)
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.3...3.3.17)
+
+**Closed issues:**
+
+- open platform component\_verify\_ticket 错误 [\#984](https://github.com/overtrue/wechat/issues/984)
+- 请教下载语音后的文件不完整怎么处理? [\#980](https://github.com/overtrue/wechat/issues/980)
+- 微信支付 API 调用下单解析缓缓 [\#977](https://github.com/overtrue/wechat/issues/977)
+- 是否可以加入微信收款(个人转账版)服务接口 [\#970](https://github.com/overtrue/wechat/issues/970)
+- 微信公众号消息加解密方式‘兼容模式’也需要填写‘aes\_key’参数,不能为空 [\#967](https://github.com/overtrue/wechat/issues/967)
+- 第三方平台 接收消息一直报错 但是能回复消息 也会提示错误 [\#961](https://github.com/overtrue/wechat/issues/961)
+- 中文官网无法访问 [\#960](https://github.com/overtrue/wechat/issues/960)
+- laravel队列中使用了SDK报Component verify ticket does not exists. [\#958](https://github.com/overtrue/wechat/issues/958)
+- 接口调用次数每日限额清零方法没有? [\#953](https://github.com/overtrue/wechat/issues/953)
+- 获取access\_toekn失败之后抛出异常的地方,能够与其他地方统一使用下述这个 resolveResponse 返回数据 [\#951](https://github.com/overtrue/wechat/issues/951)
+- 官网挂了 [\#950](https://github.com/overtrue/wechat/issues/950)
+- 无法接收到菜单点击事件推送的消息 [\#949](https://github.com/overtrue/wechat/issues/949)
+- 请教这个sdk是否可用于android 或者ios 登录? [\#948](https://github.com/overtrue/wechat/issues/948)
+- 关于access token 后端分布式部署的中控服务器的问题 [\#947](https://github.com/overtrue/wechat/issues/947)
+- 4.0 不支持laravel 5.2? [\#946](https://github.com/overtrue/wechat/issues/946)
+- log不能打印出来 [\#945](https://github.com/overtrue/wechat/issues/945)
+- EasyWeChat.org域名挂了?? [\#940](https://github.com/overtrue/wechat/issues/940)
+- 微信静默授权的时候,页面上老是会显示一段很长的英文Redirecting to http://xxxx,很影响用户体验,有没有什么方法可以去掉,保留空白页,或者允许自定义显示内容 [\#939](https://github.com/overtrue/wechat/issues/939)
+- 微信小程序生成二维码(接口B)微信扫描不出来结果 [\#938](https://github.com/overtrue/wechat/issues/938)
+- 官网可否支持看老版本的文档? [\#937](https://github.com/overtrue/wechat/issues/937)
+- 客服发送消息 收到的中文信息被unicode 编码 [\#935](https://github.com/overtrue/wechat/issues/935)
+- 有多个商户时,订单通知的 $payment 怎么创建 [\#934](https://github.com/overtrue/wechat/issues/934)
+- console中使用$app-\>user-\>get报错 [\#932](https://github.com/overtrue/wechat/issues/932)
+- PC端扫描登录的问题 [\#930](https://github.com/overtrue/wechat/issues/930)
+- 关于小程序支付的疑问 [\#912](https://github.com/overtrue/wechat/issues/912)
+- 服务商api模式使用可以更加详细吗 [\#653](https://github.com/overtrue/wechat/issues/653)
+
+**Merged pull requests:**
+
+- 修正 微信公众号要求 所有接口使用 HTTPS 方式访问 [\#988](https://github.com/overtrue/wechat/pull/988) ([drogjh](https://github.com/drogjh))
+- Apply fixes from StyleCI [\#987](https://github.com/overtrue/wechat/pull/987) ([mingyoung](https://github.com/mingyoung))
+- 修复微信收款(个人转账版)商户添加、查询含有多余字段导致签名失败的问题 [\#986](https://github.com/overtrue/wechat/pull/986) ([chenhaizano](https://github.com/chenhaizano))
+- Add merchant client. [\#983](https://github.com/overtrue/wechat/pull/983) ([mingyoung](https://github.com/mingyoung))
+- Fix PKCS7 unpad issue. [\#981](https://github.com/overtrue/wechat/pull/981) ([mingyoung](https://github.com/mingyoung))
+- 💯 Add unit tests. [\#979](https://github.com/overtrue/wechat/pull/979) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#978](https://github.com/overtrue/wechat/pull/978) ([overtrue](https://github.com/overtrue))
+- Add sub-merchant support. [\#976](https://github.com/overtrue/wechat/pull/976) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#974](https://github.com/overtrue/wechat/pull/974) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#973](https://github.com/overtrue/wechat/pull/973) ([mingyoung](https://github.com/mingyoung))
+- Refactoring payment [\#972](https://github.com/overtrue/wechat/pull/972) ([mingyoung](https://github.com/mingyoung))
+- Fix request method. [\#964](https://github.com/overtrue/wechat/pull/964) ([mingyoung](https://github.com/mingyoung))
+- MiniProgram template. [\#959](https://github.com/overtrue/wechat/pull/959) ([mingyoung](https://github.com/mingyoung))
+- 企业微信 jssdk ticket [\#954](https://github.com/overtrue/wechat/pull/954) ([mingyoung](https://github.com/mingyoung))
+- Scrutinizer Auto-Fixes [\#944](https://github.com/overtrue/wechat/pull/944) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- 简化子商户js config [\#943](https://github.com/overtrue/wechat/pull/943) ([HanSon](https://github.com/HanSon))
+- Apply fixes from StyleCI [\#942](https://github.com/overtrue/wechat/pull/942) ([overtrue](https://github.com/overtrue))
+- 支持子商户JS CONFIG生成 [\#941](https://github.com/overtrue/wechat/pull/941) ([HanSon](https://github.com/HanSon))
+
+## [4.0.0-beta.3](https://github.com/overtrue/wechat/tree/4.0.0-beta.3) (2017-09-23)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.16...4.0.0-beta.3)
+
+**Closed issues:**
+
+- 退款结果通知 [\#858](https://github.com/overtrue/wechat/issues/858)
+
+**Merged pull requests:**
+
+- Update Application.php [\#936](https://github.com/overtrue/wechat/pull/936) ([HanSon](https://github.com/HanSon))
+
+## [3.3.16](https://github.com/overtrue/wechat/tree/3.3.16) (2017-09-20)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.15...3.3.16)
+
+**Closed issues:**
+
+- 希望能增加获取回复数据的方法 [\#929](https://github.com/overtrue/wechat/issues/929)
+- 3.3 版本 数据类型不对导致无法运行 [\#928](https://github.com/overtrue/wechat/issues/928)
+
+**Merged pull requests:**
+
+- 增加退款回调处理 [\#931](https://github.com/overtrue/wechat/pull/931) ([leo108](https://github.com/leo108))
+
+## [3.3.15](https://github.com/overtrue/wechat/tree/3.3.15) (2017-09-13)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.14...3.3.15)
+
+**Closed issues:**
+
+- 微信 for windows 发送文件的时候报错 [\#927](https://github.com/overtrue/wechat/issues/927)
+
+## [3.3.14](https://github.com/overtrue/wechat/tree/3.3.14) (2017-09-13)
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.2...3.3.14)
+
+**Closed issues:**
+
+- 请教授权的时候什么方法拿到用户是否关注了本公众号? [\#926](https://github.com/overtrue/wechat/issues/926)
+
+## [4.0.0-beta.2](https://github.com/overtrue/wechat/tree/4.0.0-beta.2) (2017-09-12)
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.1...4.0.0-beta.2)
+
+**Closed issues:**
+
+- readme.md写错了? [\#923](https://github.com/overtrue/wechat/issues/923)
+- token验证成功,但还是回复暂时不可用,困扰1个星期多了,真心求助!!!有偿都可以!! [\#922](https://github.com/overtrue/wechat/issues/922)
+- 条件判断错了,stripos返回的是“返回在字符串 haystack 中 needle 首次出现的数字位置。”,所以不能直接作为条件判断 [\#915](https://github.com/overtrue/wechat/issues/915)
+- README中的链接是否错误 [\#913](https://github.com/overtrue/wechat/issues/913)
+- 测试公众号无法接受用户信息 [\#911](https://github.com/overtrue/wechat/issues/911)
+- ReadMe文件过期 [\#910](https://github.com/overtrue/wechat/issues/910)
+- 开放平台服务,取消授权会有哪些参数过来? [\#909](https://github.com/overtrue/wechat/issues/909)
+- token无法验证 [\#908](https://github.com/overtrue/wechat/issues/908)
+- laravel 5.4 composer 失败 [\#907](https://github.com/overtrue/wechat/issues/907)
+- 开放平台:组件ticket无法通过 [\#904](https://github.com/overtrue/wechat/issues/904)
+- 官方网站一直登陆不了,浙江丽水地区 [\#903](https://github.com/overtrue/wechat/issues/903)
+- \[4.0\] Pimple\Exception\UnknownIdentifierException [\#901](https://github.com/overtrue/wechat/issues/901)
+- 4.0 报错“Your requirements could not be resolved to an installable set of packages.” [\#898](https://github.com/overtrue/wechat/issues/898)
+
+**Merged pull requests:**
+
+- 修改通过ticket换取二维码图片地址的逻辑 [\#925](https://github.com/overtrue/wechat/pull/925) ([Gwill](https://github.com/Gwill))
+- make domain more flexible [\#924](https://github.com/overtrue/wechat/pull/924) ([HanSon](https://github.com/HanSon))
+- add code & domain comment [\#921](https://github.com/overtrue/wechat/pull/921) ([HanSon](https://github.com/HanSon))
+- Apply fixes from StyleCI [\#919](https://github.com/overtrue/wechat/pull/919) ([overtrue](https://github.com/overtrue))
+- \[3.1\] Custom PreAuthCode Support [\#918](https://github.com/overtrue/wechat/pull/918) ([freyo](https://github.com/freyo))
+- 修改acess\_token无效时微信返回错误码的判断 [\#916](https://github.com/overtrue/wechat/pull/916) ([blackjune](https://github.com/blackjune))
+- \[4.0\] Add optional 'request' parameter to notify handler methods [\#905](https://github.com/overtrue/wechat/pull/905) ([edwardaa](https://github.com/edwardaa))
+- Apply fixes from StyleCI [\#902](https://github.com/overtrue/wechat/pull/902) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#897](https://github.com/overtrue/wechat/pull/897) ([overtrue](https://github.com/overtrue))
+- 增加OAuth中Guzzle\Client的配置项的设置 [\#893](https://github.com/overtrue/wechat/pull/893) ([khsing](https://github.com/khsing))
+- Apply fixes from StyleCI [\#887](https://github.com/overtrue/wechat/pull/887) ([overtrue](https://github.com/overtrue))
+- Scrutinizer Auto-Fixes [\#884](https://github.com/overtrue/wechat/pull/884) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+
+## [4.0.0-beta.1](https://github.com/overtrue/wechat/tree/4.0.0-beta.1) (2017-08-31)
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-alpha.2...4.0.0-beta.1)
+
+**Closed issues:**
+
+- http://easywechat.org/ 网站访问不了了? [\#896](https://github.com/overtrue/wechat/issues/896)
+- 关于缓存,请问为什么key中包含appId \* 2,有什么讲究吗? [\#892](https://github.com/overtrue/wechat/issues/892)
+- 小程序调用解密程序报-41003错误 [\#891](https://github.com/overtrue/wechat/issues/891)
+- 小程序调用加密数据解密时报错,不存在方法 [\#890](https://github.com/overtrue/wechat/issues/890)
+- 有关4.0使用文档的问题 [\#883](https://github.com/overtrue/wechat/issues/883)
+- \[4.0\] PHP最低版本能否降到7.0  [\#880](https://github.com/overtrue/wechat/issues/880)
+
+**Merged pull requests:**
+
+- \[4.0\] Pass proper arguments to the Response constructor [\#895](https://github.com/overtrue/wechat/pull/895) ([edwardaa](https://github.com/edwardaa))
+- Fix baseUrl and json issues. [\#894](https://github.com/overtrue/wechat/pull/894) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#889](https://github.com/overtrue/wechat/pull/889) ([overtrue](https://github.com/overtrue))
+- Scrutinizer Auto-Fixes [\#885](https://github.com/overtrue/wechat/pull/885) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- Apply fixes from StyleCI [\#882](https://github.com/overtrue/wechat/pull/882) ([overtrue](https://github.com/overtrue))
+- 补充通用卡接口 [\#881](https://github.com/overtrue/wechat/pull/881) ([XiaoLer](https://github.com/XiaoLer))
+- Apply fixes from StyleCI [\#879](https://github.com/overtrue/wechat/pull/879) ([overtrue](https://github.com/overtrue))
+- \[3.1\] Payment/API 没有使用全局的 cache [\#878](https://github.com/overtrue/wechat/pull/878) ([edwardaa](https://github.com/edwardaa))
+- Add JSON\_UNESCAPED\_UNICODE option. [\#874](https://github.com/overtrue/wechat/pull/874) ([mingyoung](https://github.com/mingyoung))
+- update \_\_set\_state magic method to static [\#872](https://github.com/overtrue/wechat/pull/872) ([8090Lambert](https://github.com/8090Lambert))
+
+## [4.0.0-alpha.2](https://github.com/overtrue/wechat/tree/4.0.0-alpha.2) (2017-08-20)
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-alpha.1...4.0.0-alpha.2)
+
+**Closed issues:**
+
+- 你好,怎么用的 [\#869](https://github.com/overtrue/wechat/issues/869)
+
+**Merged pull requests:**
+
+- Tweak dir [\#871](https://github.com/overtrue/wechat/pull/871) ([mingyoung](https://github.com/mingyoung))
+- Fix mini-program guard. [\#870](https://github.com/overtrue/wechat/pull/870) ([mingyoung](https://github.com/mingyoung))
+
+## [4.0.0-alpha.1](https://github.com/overtrue/wechat/tree/4.0.0-alpha.1) (2017-08-14)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.13...4.0.0-alpha.1)
+
+**Closed issues:**
+
+- 对doctrine/cache依赖的版本锁定 [\#867](https://github.com/overtrue/wechat/issues/867)
+
+## [3.3.13](https://github.com/overtrue/wechat/tree/3.3.13) (2017-08-13)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.12...3.3.13)
+
+**Closed issues:**
+
+- 文档中网页授权实例写的不明确 [\#850](https://github.com/overtrue/wechat/issues/850)
+- \[意见\]作者能否提供getTokenFromServer方法扩展从外部第三方获取access\_token [\#837](https://github.com/overtrue/wechat/issues/837)
+- invalid credential, access\_token is invalid or not latest [\#808](https://github.com/overtrue/wechat/issues/808)
+- \[4.0\] 重构卡券 [\#806](https://github.com/overtrue/wechat/issues/806)
+- \[4.0\] 重构 Broadcasting [\#805](https://github.com/overtrue/wechat/issues/805)
+- \[4.0\] 变更日志 [\#746](https://github.com/overtrue/wechat/issues/746)
+
+**Merged pull requests:**
+
+- Fixed open-platform authorizer server token. [\#866](https://github.com/overtrue/wechat/pull/866) ([mingyoung](https://github.com/mingyoung))
+- payment\ClientTest 优化 [\#865](https://github.com/overtrue/wechat/pull/865) ([tianyong90](https://github.com/tianyong90))
+- Apply fixes from StyleCI [\#864](https://github.com/overtrue/wechat/pull/864) ([overtrue](https://github.com/overtrue))
+- 退款通知处理及相关单元测试 [\#863](https://github.com/overtrue/wechat/pull/863) ([tianyong90](https://github.com/tianyong90))
+- Apply fixes from StyleCI [\#862](https://github.com/overtrue/wechat/pull/862) ([overtrue](https://github.com/overtrue))
+- Update dependence version. [\#861](https://github.com/overtrue/wechat/pull/861) ([mingyoung](https://github.com/mingyoung))
+- Add tests. [\#859](https://github.com/overtrue/wechat/pull/859) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#857](https://github.com/overtrue/wechat/pull/857) ([overtrue](https://github.com/overtrue))
+- Payment 单元测试优化 [\#856](https://github.com/overtrue/wechat/pull/856) ([tianyong90](https://github.com/tianyong90))
+- Apply fixes from StyleCI [\#855](https://github.com/overtrue/wechat/pull/855) ([overtrue](https://github.com/overtrue))
+- lists 方法重命名为 list,相关单元测试调整 [\#853](https://github.com/overtrue/wechat/pull/853) ([tianyong90](https://github.com/tianyong90))
+- Apply fixes from StyleCI [\#852](https://github.com/overtrue/wechat/pull/852) ([overtrue](https://github.com/overtrue))
+- Payment 单元测试及部分问题修复 [\#851](https://github.com/overtrue/wechat/pull/851) ([tianyong90](https://github.com/tianyong90))
+- Apply fixes from StyleCI [\#848](https://github.com/overtrue/wechat/pull/848) ([overtrue](https://github.com/overtrue))
+- 调整 Payment\BaseClient 注入的 $app 类型 [\#847](https://github.com/overtrue/wechat/pull/847) ([tianyong90](https://github.com/tianyong90))
+- array\_merge 方法参数类型转换, type hints [\#846](https://github.com/overtrue/wechat/pull/846) ([tianyong90](https://github.com/tianyong90))
+- Fix oauth. [\#845](https://github.com/overtrue/wechat/pull/845) ([mingyoung](https://github.com/mingyoung))
+- Text message. [\#844](https://github.com/overtrue/wechat/pull/844) ([mingyoung](https://github.com/mingyoung))
+- Rename BaseService -\> BasicService. [\#843](https://github.com/overtrue/wechat/pull/843) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#842](https://github.com/overtrue/wechat/pull/842) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#841](https://github.com/overtrue/wechat/pull/841) ([overtrue](https://github.com/overtrue))
+- phpdoc types。 [\#840](https://github.com/overtrue/wechat/pull/840) ([tianyong90](https://github.com/tianyong90))
+- Apply fixes from StyleCI [\#839](https://github.com/overtrue/wechat/pull/839) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#836](https://github.com/overtrue/wechat/pull/836) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#835](https://github.com/overtrue/wechat/pull/835) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#833](https://github.com/overtrue/wechat/pull/833) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#831](https://github.com/overtrue/wechat/pull/831) ([overtrue](https://github.com/overtrue))
+
+## [3.3.12](https://github.com/overtrue/wechat/tree/3.3.12) (2017-08-01)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.11...3.3.12)
+
+**Closed issues:**
+
+- 能否整合微信开放平台在给出一套demo [\#816](https://github.com/overtrue/wechat/issues/816)
+- 请教这个项目的支付部分,尤其是签名和结果回调,是否支持小程序? [\#814](https://github.com/overtrue/wechat/issues/814)
+- 微信意图识别接口返回invalid param [\#804](https://github.com/overtrue/wechat/issues/804)
+- 返回param invalid [\#803](https://github.com/overtrue/wechat/issues/803)
+
+**Merged pull requests:**
+
+- change comment word [\#830](https://github.com/overtrue/wechat/pull/830) ([tianyong90](https://github.com/tianyong90))
+- Fix getTicket. [\#829](https://github.com/overtrue/wechat/pull/829) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#827](https://github.com/overtrue/wechat/pull/827) ([overtrue](https://github.com/overtrue))
+- 修正 HasAttributes Trait 引用错误 [\#825](https://github.com/overtrue/wechat/pull/825) ([tianyong90](https://github.com/tianyong90))
+- Apply fixes from StyleCI [\#824](https://github.com/overtrue/wechat/pull/824) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#822](https://github.com/overtrue/wechat/pull/822) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#820](https://github.com/overtrue/wechat/pull/820) ([mingyoung](https://github.com/mingyoung))
+- Add subscribe message. [\#819](https://github.com/overtrue/wechat/pull/819) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#818](https://github.com/overtrue/wechat/pull/818) ([mingyoung](https://github.com/mingyoung))
+- 微信开放平台帐号管理 [\#817](https://github.com/overtrue/wechat/pull/817) ([XiaoLer](https://github.com/XiaoLer))
+- add method in comment [\#813](https://github.com/overtrue/wechat/pull/813) ([HanSon](https://github.com/HanSon))
+- fixed guzzle version [\#812](https://github.com/overtrue/wechat/pull/812) ([HanSon](https://github.com/HanSon))
+- Apply fixes from StyleCI [\#811](https://github.com/overtrue/wechat/pull/811) ([mingyoung](https://github.com/mingyoung))
+- Downgrade to php 7.0 [\#809](https://github.com/overtrue/wechat/pull/809) ([HanSon](https://github.com/HanSon))
+
+## [3.3.11](https://github.com/overtrue/wechat/tree/3.3.11) (2017-07-17)
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-alpha1...3.3.11)
+
+**Closed issues:**
+
+- 请添加 「退款原因」 参数 [\#802](https://github.com/overtrue/wechat/issues/802)
+
+## [4.0.0-alpha1](https://github.com/overtrue/wechat/tree/4.0.0-alpha1) (2017-07-17)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.10...4.0.0-alpha1)
+
+**Closed issues:**
+
+- Overtrue\Wechat\Media not found [\#801](https://github.com/overtrue/wechat/issues/801)
+- 在微信的接口配置时Token 无效,可任意输入 [\#800](https://github.com/overtrue/wechat/issues/800)
+
+## [3.3.10](https://github.com/overtrue/wechat/tree/3.3.10) (2017-07-13)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.9...3.3.10)
+
+**Closed issues:**
+
+- 第三方平台refresh\_token的保存问题 [\#798](https://github.com/overtrue/wechat/issues/798)
+- 网页授权共享session已晚 [\#792](https://github.com/overtrue/wechat/issues/792)
+
+**Merged pull requests:**
+
+- 临时二维码也是支持scene\_str的,这里补充上 [\#797](https://github.com/overtrue/wechat/pull/797) ([lornewang](https://github.com/lornewang))
+- Apply fixes from StyleCI [\#795](https://github.com/overtrue/wechat/pull/795) ([overtrue](https://github.com/overtrue))
+- add card message type [\#794](https://github.com/overtrue/wechat/pull/794) ([IanGely](https://github.com/IanGely))
+- add staff message type wxcard [\#793](https://github.com/overtrue/wechat/pull/793) ([IanGely](https://github.com/IanGely))
+
+## [3.3.9](https://github.com/overtrue/wechat/tree/3.3.9) (2017-07-07)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.8...3.3.9)
+
+**Closed issues:**
+
+- \[4.0\] Http 模块 [\#678](https://github.com/overtrue/wechat/issues/678)
+- \[4.0\] Http 请求类 [\#582](https://github.com/overtrue/wechat/issues/582)
+
+**Merged pull requests:**
+
+- Apply fixes from StyleCI [\#791](https://github.com/overtrue/wechat/pull/791) ([overtrue](https://github.com/overtrue))
+- Add get user portrait method. [\#790](https://github.com/overtrue/wechat/pull/790) ([getive](https://github.com/getive))
+- \[Feature\] Move directories [\#789](https://github.com/overtrue/wechat/pull/789) ([overtrue](https://github.com/overtrue))
+- \[Feature\] Move traits to kernel. [\#788](https://github.com/overtrue/wechat/pull/788) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#787](https://github.com/overtrue/wechat/pull/787) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#786](https://github.com/overtrue/wechat/pull/786) ([overtrue](https://github.com/overtrue))
+
+## [3.3.8](https://github.com/overtrue/wechat/tree/3.3.8) (2017-07-07)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.7...3.3.8)
+
+**Closed issues:**
+
+- $temporary-\>getStream\($media\_id\) 与 file\_get\_contents\(\) 有区别??? [\#742](https://github.com/overtrue/wechat/issues/742)
+
+## [3.3.7](https://github.com/overtrue/wechat/tree/3.3.7) (2017-07-06)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.6...3.3.7)
+
+**Closed issues:**
+
+- 多添加一个$option [\#772](https://github.com/overtrue/wechat/issues/772)
+- 消息群发,指定openid群发视频时,微信报错invalid message type hint: \[JUs0Oa0779ge25\] [\#757](https://github.com/overtrue/wechat/issues/757)
+
+## [3.3.6](https://github.com/overtrue/wechat/tree/3.3.6) (2017-07-06)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.5...3.3.6)
+
+**Fixed bugs:**
+
+- 素材管理,如果media\_id不存在会保存网页返回的错误代码 [\#592](https://github.com/overtrue/wechat/issues/592)
+
+**Closed issues:**
+
+- https://easywechat.org网站证书刚过期了,知会作者一声 [\#781](https://github.com/overtrue/wechat/issues/781)
+- access\_token 是否能不内部主动请求微信 [\#778](https://github.com/overtrue/wechat/issues/778)
+- 门店创建API \($poi-\>create\) 建议返回 poi\_id / exception [\#774](https://github.com/overtrue/wechat/issues/774)
+- 扩展门店小程序错误 [\#762](https://github.com/overtrue/wechat/issues/762)
+- \[4.0\] jssdk 抽出独立模块 [\#754](https://github.com/overtrue/wechat/issues/754)
+- \[4.0\] 消息加密解密模块提取到 Kernel [\#753](https://github.com/overtrue/wechat/issues/753)
+- 网页能授权但无法获取用户信息,代码跟官方文档一样。 [\#713](https://github.com/overtrue/wechat/issues/713)
+
+**Merged pull requests:**
+
+- Feature: BaseService. [\#785](https://github.com/overtrue/wechat/pull/785) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#784](https://github.com/overtrue/wechat/pull/784) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#783](https://github.com/overtrue/wechat/pull/783) ([mingyoung](https://github.com/mingyoung))
+
+## [3.3.5](https://github.com/overtrue/wechat/tree/3.3.5) (2017-07-04)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.4...3.3.5)
+
+**Implemented enhancements:**
+
+- 并发下access\_token存在脏写隐患 [\#696](https://github.com/overtrue/wechat/issues/696)
+
+**Merged pull requests:**
+
+- Apply fixes from StyleCI [\#780](https://github.com/overtrue/wechat/pull/780) ([overtrue](https://github.com/overtrue))
+
+## [3.3.4](https://github.com/overtrue/wechat/tree/3.3.4) (2017-07-04)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.3...3.3.4)
+
+**Closed issues:**
+
+- 网页授权获取用户信息无法打开授权页面 [\#773](https://github.com/overtrue/wechat/issues/773)
+- Class 'EasyWechat\Foundation\Application' not found  [\#769](https://github.com/overtrue/wechat/issues/769)
+- 获取小程序二维码报错 [\#766](https://github.com/overtrue/wechat/issues/766)
+- Call to undefined method EasyWeChat\Server\Guard::setRequest\(\) [\#765](https://github.com/overtrue/wechat/issues/765)
+- 网页授权问题,提示scopes类型错误 [\#764](https://github.com/overtrue/wechat/issues/764)
+- 门店小程序扩展错误问题 [\#763](https://github.com/overtrue/wechat/issues/763)
+- 微信开发者平台,全网发布怎么通过 [\#761](https://github.com/overtrue/wechat/issues/761)
+- 微信网页授权重复请求报code无效 [\#714](https://github.com/overtrue/wechat/issues/714)
+
+**Merged pull requests:**
+
+- 新版客服功能-获取聊天记录 [\#775](https://github.com/overtrue/wechat/pull/775) ([wuwenbao](https://github.com/wuwenbao))
+- Fix mini-program qrcode. [\#768](https://github.com/overtrue/wechat/pull/768) ([mingyoung](https://github.com/mingyoung))
+- Add code comments [\#756](https://github.com/overtrue/wechat/pull/756) ([daxiong123](https://github.com/daxiong123))
+
+## [3.3.3](https://github.com/overtrue/wechat/tree/3.3.3) (2017-06-22)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.2...3.3.3)
+
+**Implemented enhancements:**
+
+- \[4.0\] Trait HasHttpRequests [\#671](https://github.com/overtrue/wechat/issues/671)
+- \[4.0\] 缓存抽象成 trait: InteractsWithCache [\#670](https://github.com/overtrue/wechat/issues/670)
+- \[4.0\] 返回值类型可配置 [\#661](https://github.com/overtrue/wechat/issues/661)
+- \[4.0\] 报错信息可选 [\#596](https://github.com/overtrue/wechat/issues/596)
+- \[4.0\] 简化并完善开发者配置项 [\#584](https://github.com/overtrue/wechat/issues/584)
+
+**Fixed bugs:**
+
+- open\_platform.oauth 过早的获取 access token [\#701](https://github.com/overtrue/wechat/issues/701)
+
+**Closed issues:**
+
+- 微信网页支付配置生成 [\#751](https://github.com/overtrue/wechat/issues/751)
+- configForJSSDKPayment [\#744](https://github.com/overtrue/wechat/issues/744)
+- 发现微信上有管理公众号留言的接口,不知道是不是新出的 [\#721](https://github.com/overtrue/wechat/issues/721)
+- oauth能获取用户信息,再通过access\_token与用户openid去获取信息,部分用户的信息为空 [\#720](https://github.com/overtrue/wechat/issues/720)
+- 接入多个公众号 [\#718](https://github.com/overtrue/wechat/issues/718)
+- guzzle curl error28 - 去哪设置默认timeout ? [\#715](https://github.com/overtrue/wechat/issues/715)
+-  使用$server-\>getMessage\(\);报错 [\#712](https://github.com/overtrue/wechat/issues/712)
+- 怎样从数据库中调取配置 [\#711](https://github.com/overtrue/wechat/issues/711)
+- \[4.0\] 支持企业微信 [\#707](https://github.com/overtrue/wechat/issues/707)
+- defaultColor does not work. [\#703](https://github.com/overtrue/wechat/issues/703)
+- 是否支持H5支付 [\#694](https://github.com/overtrue/wechat/issues/694)
+- 生成AccessToken时,似乎没有调用自定义缓存的delete方法 [\#693](https://github.com/overtrue/wechat/issues/693)
+- \[4.0\] PSR-6 缓存接口 [\#692](https://github.com/overtrue/wechat/issues/692)
+- 微信支付沙盒模式支持配置文件配置 [\#690](https://github.com/overtrue/wechat/issues/690)
+- \[4.0\] 优化服务提供器结构 [\#689](https://github.com/overtrue/wechat/issues/689)
+- 强制项目不要自动获取AccessToken [\#688](https://github.com/overtrue/wechat/issues/688)
+- 小程序解密$encryptedData数据 [\#687](https://github.com/overtrue/wechat/issues/687)
+- 微信坑爹timestamp已经解决不需要configForJSSDKPayment改变timestamp中s大小写 [\#686](https://github.com/overtrue/wechat/issues/686)
+- \[4.0\] 所有 API 改名为 Client. [\#677](https://github.com/overtrue/wechat/issues/677)
+- sandbox\_signkey 过期 [\#675](https://github.com/overtrue/wechat/issues/675)
+- 接口配置失败 [\#672](https://github.com/overtrue/wechat/issues/672)
+- 下载语音文件偶尔报错:ErrorException: is\_readable\(\) expects parameter 1 to be a valid path [\#667](https://github.com/overtrue/wechat/issues/667)
+- 微信支付沙箱地址混乱 [\#665](https://github.com/overtrue/wechat/issues/665)
+- 开放平台自动回复出错,提示“该服务号暂时无法提供服务” [\#654](https://github.com/overtrue/wechat/issues/654)
+- \[4.0\]自定义微信API的区域接入点 [\#636](https://github.com/overtrue/wechat/issues/636)
+- 在命令行使用easywechat如何关闭日志 [\#601](https://github.com/overtrue/wechat/issues/601)
+- \[4.0\] PHP 版本最低要求 7.1 [\#586](https://github.com/overtrue/wechat/issues/586)
+- \[4.0\] 简化微信 API 请求 [\#583](https://github.com/overtrue/wechat/issues/583)
+- \[4.0\] 自定义 endpoint [\#521](https://github.com/overtrue/wechat/issues/521)
+
+**Merged pull requests:**
+
+- Apply fixes from StyleCI [\#750](https://github.com/overtrue/wechat/pull/750) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#749](https://github.com/overtrue/wechat/pull/749) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#747](https://github.com/overtrue/wechat/pull/747) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#745](https://github.com/overtrue/wechat/pull/745) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#740](https://github.com/overtrue/wechat/pull/740) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#737](https://github.com/overtrue/wechat/pull/737) ([mingyoung](https://github.com/mingyoung))
+- 分模块静态调用 [\#734](https://github.com/overtrue/wechat/pull/734) ([mingyoung](https://github.com/mingyoung))
+- Revert "Apply fixes from StyleCI" [\#731](https://github.com/overtrue/wechat/pull/731) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#730](https://github.com/overtrue/wechat/pull/730) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#729](https://github.com/overtrue/wechat/pull/729) ([overtrue](https://github.com/overtrue))
+- Revert "Apply fixes from StyleCI" [\#728](https://github.com/overtrue/wechat/pull/728) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#727](https://github.com/overtrue/wechat/pull/727) ([overtrue](https://github.com/overtrue))
+- 修复Https 请求判断不准 [\#726](https://github.com/overtrue/wechat/pull/726) ([xutl](https://github.com/xutl))
+- Apply fixes from StyleCI [\#725](https://github.com/overtrue/wechat/pull/725) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#724](https://github.com/overtrue/wechat/pull/724) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#723](https://github.com/overtrue/wechat/pull/723) ([mingyoung](https://github.com/mingyoung))
+- Correction notes [\#722](https://github.com/overtrue/wechat/pull/722) ([PersiLiao](https://github.com/PersiLiao))
+- Apply fixes from StyleCI [\#717](https://github.com/overtrue/wechat/pull/717) ([mingyoung](https://github.com/mingyoung))
+- 新增图文消息留言管理接口 [\#716](https://github.com/overtrue/wechat/pull/716) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#710](https://github.com/overtrue/wechat/pull/710) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#709](https://github.com/overtrue/wechat/pull/709) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#708](https://github.com/overtrue/wechat/pull/708) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#706](https://github.com/overtrue/wechat/pull/706) ([overtrue](https://github.com/overtrue))
+- 命令行下不打印日志 [\#705](https://github.com/overtrue/wechat/pull/705) ([mingyoung](https://github.com/mingyoung))
+- add defaultColor [\#704](https://github.com/overtrue/wechat/pull/704) ([damonto](https://github.com/damonto))
+- Fix [\#702](https://github.com/overtrue/wechat/pull/702) ([mingyoung](https://github.com/mingyoung))
+- Add api. [\#700](https://github.com/overtrue/wechat/pull/700) ([mingyoung](https://github.com/mingyoung))
+- Rename method. [\#699](https://github.com/overtrue/wechat/pull/699) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#698](https://github.com/overtrue/wechat/pull/698) ([mingyoung](https://github.com/mingyoung))
+- 修正素材管理中的返回值文档注释,正确的类型应该是集合,而不是字符串。 [\#695](https://github.com/overtrue/wechat/pull/695) ([starlight36](https://github.com/starlight36))
+- Payment sandbox config. [\#691](https://github.com/overtrue/wechat/pull/691) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#684](https://github.com/overtrue/wechat/pull/684) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#683](https://github.com/overtrue/wechat/pull/683) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#682](https://github.com/overtrue/wechat/pull/682) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#681](https://github.com/overtrue/wechat/pull/681) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#680](https://github.com/overtrue/wechat/pull/680) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#679](https://github.com/overtrue/wechat/pull/679) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#676](https://github.com/overtrue/wechat/pull/676) ([mingyoung](https://github.com/mingyoung))
+- checks via composer. [\#673](https://github.com/overtrue/wechat/pull/673) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#668](https://github.com/overtrue/wechat/pull/668) ([overtrue](https://github.com/overtrue))
+- Correct payment sandbox endpoint and add a method to get sandbox sign key [\#666](https://github.com/overtrue/wechat/pull/666) ([skyred](https://github.com/skyred))
+
+## [3.3.2](https://github.com/overtrue/wechat/tree/3.3.2) (2017-04-27)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.1...3.3.2)
+
+**Implemented enhancements:**
+
+- \[4.0\] Open Platform 模块 [\#587](https://github.com/overtrue/wechat/issues/587)
+- \[4.0\] 微信支付 sandbox模式 [\#507](https://github.com/overtrue/wechat/issues/507)
+
+**Closed issues:**
+
+- \[4.0\] staff 模块改名为 customer service [\#585](https://github.com/overtrue/wechat/issues/585)
+
+**Merged pull requests:**
+
+- Module rename. [\#664](https://github.com/overtrue/wechat/pull/664) ([mingyoung](https://github.com/mingyoung))
+- Merge branch master into branch develop. [\#663](https://github.com/overtrue/wechat/pull/663) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#662](https://github.com/overtrue/wechat/pull/662) ([mingyoung](https://github.com/mingyoung))
+- Fix payment tools API [\#660](https://github.com/overtrue/wechat/pull/660) ([mingyoung](https://github.com/mingyoung))
+- Avoid ambiguity [\#659](https://github.com/overtrue/wechat/pull/659) ([mingyoung](https://github.com/mingyoung))
+- Support Payment Sandbox mode [\#658](https://github.com/overtrue/wechat/pull/658) ([skyred](https://github.com/skyred))
+- Apply fixes from StyleCI [\#656](https://github.com/overtrue/wechat/pull/656) ([overtrue](https://github.com/overtrue))
+- Mini program datacube. [\#655](https://github.com/overtrue/wechat/pull/655) ([mingyoung](https://github.com/mingyoung))
+
+## [3.3.1](https://github.com/overtrue/wechat/tree/3.3.1) (2017-04-16)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.0...3.3.1)
+
+**Closed issues:**
+
+- 微信第三方平台缓存位置,是否可以在配置文件中自定义 [\#648](https://github.com/overtrue/wechat/issues/648)
+- 微信开放平台authorizer token缓存问题 [\#644](https://github.com/overtrue/wechat/issues/644)
+- 微信开放平台发起网页授权bug [\#638](https://github.com/overtrue/wechat/issues/638)
+- 微信公众号不能回复接收到的消息,日志无报错 [\#637](https://github.com/overtrue/wechat/issues/637)
+- \[4.0\]黑名单管理 [\#538](https://github.com/overtrue/wechat/issues/538)
+
+**Merged pull requests:**
+
+- optimizes [\#652](https://github.com/overtrue/wechat/pull/652) ([mingyoung](https://github.com/mingyoung))
+
+## [3.3.0](https://github.com/overtrue/wechat/tree/3.3.0) (2017-04-13)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.7...3.3.0)
+
+**Closed issues:**
+
+- 微信接口获取openid是怎么排序的? [\#650](https://github.com/overtrue/wechat/issues/650)
+- 缺少网页扫码支付接口 [\#647](https://github.com/overtrue/wechat/issues/647)
+- 微信下的单的默认过期时间是多少啊 [\#645](https://github.com/overtrue/wechat/issues/645)
+- 在获取用户信息是出错 [\#643](https://github.com/overtrue/wechat/issues/643)
+- 调用$app =app\('wechat'\);时报错Use of undefined constant CURLOPT\_IPRESOLVE - assumed 'CURLOPT\_IPRESOLVE' [\#633](https://github.com/overtrue/wechat/issues/633)
+- 提示找不到EasyWeChat\Server\Guard::setRequest\(\)方法 [\#626](https://github.com/overtrue/wechat/issues/626)
+- 开放平台接收ComponentVerifyTicket,会出现Undefined index: FromUserName [\#623](https://github.com/overtrue/wechat/issues/623)
+- 美国移动网络获取不到accessToken [\#610](https://github.com/overtrue/wechat/issues/610)
+- 开放平台 APP 微信登录 [\#604](https://github.com/overtrue/wechat/issues/604)
+
+**Merged pull requests:**
+
+- Merge from open-platform branch. [\#651](https://github.com/overtrue/wechat/pull/651) ([mingyoung](https://github.com/mingyoung))
+- Update code for open-platform [\#649](https://github.com/overtrue/wechat/pull/649) ([mingyoung](https://github.com/mingyoung))
+- Code cleanup & refactoring. [\#646](https://github.com/overtrue/wechat/pull/646) ([mingyoung](https://github.com/mingyoung))
+- support cash coupon [\#642](https://github.com/overtrue/wechat/pull/642) ([HanSon](https://github.com/HanSon))
+- ♻️ All tests have been namespaced. [\#641](https://github.com/overtrue/wechat/pull/641) ([mingyoung](https://github.com/mingyoung))
+- tweak code. [\#640](https://github.com/overtrue/wechat/pull/640) ([mingyoung](https://github.com/mingyoung))
+- modify oauth property [\#639](https://github.com/overtrue/wechat/pull/639) ([jekst](https://github.com/jekst))
+- Apply fixes from StyleCI [\#635](https://github.com/overtrue/wechat/pull/635) ([overtrue](https://github.com/overtrue))
+- ✨ Blacklist. [\#634](https://github.com/overtrue/wechat/pull/634) ([mingyoung](https://github.com/mingyoung))
+- 🔨 Refactoring for mini-program. [\#632](https://github.com/overtrue/wechat/pull/632) ([mingyoung](https://github.com/mingyoung))
+
+## [3.2.7](https://github.com/overtrue/wechat/tree/3.2.7) (2017-03-31)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.6...3.2.7)
+
+**Closed issues:**
+
+- 不管哪个公众号,只要填写 这个接口地址,都能配置或应用成功,实际上是不成功的,不到怎么找错。。 [\#611](https://github.com/overtrue/wechat/issues/611)
+
+**Merged pull requests:**
+
+- 修复一个创建卡券时的 bug, 添加获取微信门店类目表的api [\#631](https://github.com/overtrue/wechat/pull/631) ([Hexor](https://github.com/Hexor))
+
+## [3.2.6](https://github.com/overtrue/wechat/tree/3.2.6) (2017-03-31)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.5...3.2.6)
+
+**Closed issues:**
+
+- 我想大量发模板消息,但send每次都等待返回太慢,有啥解决办法吗? [\#630](https://github.com/overtrue/wechat/issues/630)
+- 3.2开放平台缺少authorizer\_token和authorization [\#629](https://github.com/overtrue/wechat/issues/629)
+- 微信开发平台接受消息报Invalid request signature bug [\#625](https://github.com/overtrue/wechat/issues/625)
+- 图文上传thumb\_media\_id 返回 {"errcode":40007,"errmsg":"invalid media\_id hint: \[\]"}  [\#622](https://github.com/overtrue/wechat/issues/622)
+- Encryptor基类hack导致小程序的sessionKey base64\_decode失败 [\#614](https://github.com/overtrue/wechat/issues/614)
+- 是否有 2.1 升级到最新版的方案? [\#609](https://github.com/overtrue/wechat/issues/609)
+- laravel5.3 安装 "overtrue/wechat:~3.1 失败 [\#607](https://github.com/overtrue/wechat/issues/607)
+- overtrue/wechat和phpdoc包依赖冲突。 [\#605](https://github.com/overtrue/wechat/issues/605)
+- \[bug\]2个问题 [\#597](https://github.com/overtrue/wechat/issues/597)
+- 微信第三方平台开发是否只做了一部分? [\#594](https://github.com/overtrue/wechat/issues/594)
+- \[4.0\] ServiceProvider 移动到各自模块里 [\#588](https://github.com/overtrue/wechat/issues/588)
+- Cannot use EasyWeChat\OpenPlatform\Traits\VerifyTicket as VerifyTicket because the name is already in use [\#579](https://github.com/overtrue/wechat/issues/579)
+- 授权state值怎么设置 [\#573](https://github.com/overtrue/wechat/issues/573)
+- mini\_app get jscode problem, report appid & secret value is null [\#569](https://github.com/overtrue/wechat/issues/569)
+- 小程序生成二维码问题 [\#568](https://github.com/overtrue/wechat/issues/568)
+
+**Merged pull requests:**
+
+- Update OpenPlatform AppId [\#624](https://github.com/overtrue/wechat/pull/624) ([jeftom](https://github.com/jeftom))
+- Apply fixes from StyleCI [\#621](https://github.com/overtrue/wechat/pull/621) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#618](https://github.com/overtrue/wechat/pull/618) ([overtrue](https://github.com/overtrue))
+- Compatible with php5.5 [\#617](https://github.com/overtrue/wechat/pull/617) ([mingyoung](https://github.com/mingyoung))
+- Make the testcase works. [\#616](https://github.com/overtrue/wechat/pull/616) ([mingyoung](https://github.com/mingyoung))
+- Fix mini-program decryptor [\#615](https://github.com/overtrue/wechat/pull/615) ([mingyoung](https://github.com/mingyoung))
+- Missing message handling [\#613](https://github.com/overtrue/wechat/pull/613) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#612](https://github.com/overtrue/wechat/pull/612) ([overtrue](https://github.com/overtrue))
+- 添加卡券创建二维码接口 [\#608](https://github.com/overtrue/wechat/pull/608) ([forecho](https://github.com/forecho))
+- 开放平台大幅重构并且添加测试 [\#606](https://github.com/overtrue/wechat/pull/606) ([tsunamilx](https://github.com/tsunamilx))
+- Update MessageBuilder.php [\#603](https://github.com/overtrue/wechat/pull/603) ([U2Fsd](https://github.com/U2Fsd))
+- 生成 js添加到卡包接口 增加fixed\_begintimestamp、outer\_str字段 [\#602](https://github.com/overtrue/wechat/pull/602) ([gychg](https://github.com/gychg))
+- tests for speed [\#600](https://github.com/overtrue/wechat/pull/600) ([mingyoung](https://github.com/mingyoung))
+- Update test files [\#599](https://github.com/overtrue/wechat/pull/599) ([mingyoung](https://github.com/mingyoung))
+- 允许自定义ticket缓存key [\#598](https://github.com/overtrue/wechat/pull/598) ([XiaoLer](https://github.com/XiaoLer))
+- delete top color [\#595](https://github.com/overtrue/wechat/pull/595) ([HanSon](https://github.com/HanSon))
+- Add payment scan notify handler [\#593](https://github.com/overtrue/wechat/pull/593) ([acgrid](https://github.com/acgrid))
+- Apply fixes from StyleCI [\#591](https://github.com/overtrue/wechat/pull/591) ([overtrue](https://github.com/overtrue))
+- Upgrade packages version to 4.0 [\#590](https://github.com/overtrue/wechat/pull/590) ([reatang](https://github.com/reatang))
+- Move providers to module dir. \#588 [\#589](https://github.com/overtrue/wechat/pull/589) ([overtrue](https://github.com/overtrue))
+- 把OpenPlatform中的组件依赖解耦 [\#581](https://github.com/overtrue/wechat/pull/581) ([reatang](https://github.com/reatang))
+
+## [3.2.5](https://github.com/overtrue/wechat/tree/3.2.5) (2017-02-04)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.4...3.2.5)
+
+**Merged pull requests:**
+
+- fix naming [\#580](https://github.com/overtrue/wechat/pull/580) ([mingyoung](https://github.com/mingyoung))
+- Allow client code configure its own GuzzleHTTP handler [\#578](https://github.com/overtrue/wechat/pull/578) ([acgrid](https://github.com/acgrid))
+
+## [3.2.4](https://github.com/overtrue/wechat/tree/3.2.4) (2017-01-24)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.3...3.2.4)
+
+**Closed issues:**
+
+- 如何在其他框架下使用$app-\>payment-\>handleNotify [\#574](https://github.com/overtrue/wechat/issues/574)
+- 前后端分离单页下获取的config,认证失败 [\#565](https://github.com/overtrue/wechat/issues/565)
+- 支付签名错误 [\#563](https://github.com/overtrue/wechat/issues/563)
+
+**Merged pull requests:**
+
+- Update Authorizer.php [\#577](https://github.com/overtrue/wechat/pull/577) ([ww380459000](https://github.com/ww380459000))
+- 补全通用卡接口 [\#575](https://github.com/overtrue/wechat/pull/575) ([XiaoLer](https://github.com/XiaoLer))
+- require ext-SimpleXML [\#572](https://github.com/overtrue/wechat/pull/572) ([garveen](https://github.com/garveen))
+- fix README Contribution link [\#571](https://github.com/overtrue/wechat/pull/571) ([zhwei](https://github.com/zhwei))
+- Add user data decryption. [\#570](https://github.com/overtrue/wechat/pull/570) ([mingyoung](https://github.com/mingyoung))
+- change request parameter [\#567](https://github.com/overtrue/wechat/pull/567) ([cloudsthere](https://github.com/cloudsthere))
+- 完善小程序代码 [\#566](https://github.com/overtrue/wechat/pull/566) ([mingyoung](https://github.com/mingyoung))
+- 添加小程序支持 [\#564](https://github.com/overtrue/wechat/pull/564) ([mingyoung](https://github.com/mingyoung))
+
+## [3.2.3](https://github.com/overtrue/wechat/tree/3.2.3) (2017-01-04)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.2...3.2.3)
+
+**Closed issues:**
+
+- 文档里的自定义菜单中,group\_id是否为tag\_id的误写? [\#561](https://github.com/overtrue/wechat/issues/561)
+- Open Platform有简明的使用文档吗?3ks [\#560](https://github.com/overtrue/wechat/issues/560)
+- 刷新access\_token有效期,未发现有相关的封装 [\#540](https://github.com/overtrue/wechat/issues/540)
+
+**Merged pull requests:**
+
+- Update Card.php [\#562](https://github.com/overtrue/wechat/pull/562) ([XiaoLer](https://github.com/XiaoLer))
+- Apply fixes from StyleCI [\#559](https://github.com/overtrue/wechat/pull/559) ([overtrue](https://github.com/overtrue))
+- Update API.php [\#558](https://github.com/overtrue/wechat/pull/558) ([drogjh](https://github.com/drogjh))
+- optimized code [\#557](https://github.com/overtrue/wechat/pull/557) ([mingyoung](https://github.com/mingyoung))
+
+## [3.2.2](https://github.com/overtrue/wechat/tree/3.2.2) (2016-12-27)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.1...3.2.2)
+
+**Closed issues:**
+
+- How to get authorize url? [\#555](https://github.com/overtrue/wechat/issues/555)
+
+**Merged pull requests:**
+
+- fixed downloadBill method result [\#556](https://github.com/overtrue/wechat/pull/556) ([hidehalo](https://github.com/hidehalo))
+- add config:log.permission for monolog [\#554](https://github.com/overtrue/wechat/pull/554) ([woshizoufeng](https://github.com/woshizoufeng))
+- Improve open platform support. [\#553](https://github.com/overtrue/wechat/pull/553) ([mingyoung](https://github.com/mingyoung))
+- Improve. [\#552](https://github.com/overtrue/wechat/pull/552) ([mingyoung](https://github.com/mingyoung))
+- add $forceRefresh param to js-\>ticket\(\) method [\#551](https://github.com/overtrue/wechat/pull/551) ([leo108](https://github.com/leo108))
+
+## [3.2.1](https://github.com/overtrue/wechat/tree/3.2.1) (2016-12-20)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.0...3.2.1)
+
+**Merged pull requests:**
+
+- 增加小程序用jscode获取用户信息的接口 [\#550](https://github.com/overtrue/wechat/pull/550) ([soone](https://github.com/soone))
+
+## [3.2.0](https://github.com/overtrue/wechat/tree/3.2.0) (2016-12-19)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.9...3.2.0)
+
+**Closed issues:**
+
+- 喵喵喵 [\#545](https://github.com/overtrue/wechat/issues/545)
+- HttpException with uploadArticle API [\#544](https://github.com/overtrue/wechat/issues/544)
+- 是否有接入小程序的计划 [\#543](https://github.com/overtrue/wechat/issues/543)
+- "Call to undefined method Overtrue\Socialite\Providers\WeChat Provider::driver\(\) [\#536](https://github.com/overtrue/wechat/issues/536)
+- 服务端Server模块回复音乐消息出错 [\#533](https://github.com/overtrue/wechat/issues/533)
+- 用户授权出现The key "access\_token" could not be empty [\#527](https://github.com/overtrue/wechat/issues/527)
+
+**Merged pull requests:**
+
+- Apply fixes from StyleCI [\#549](https://github.com/overtrue/wechat/pull/549) ([overtrue](https://github.com/overtrue))
+- 添加摇一摇周边模块 [\#548](https://github.com/overtrue/wechat/pull/548) ([allen05ren](https://github.com/allen05ren))
+- Make some compatible. [\#542](https://github.com/overtrue/wechat/pull/542) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#541](https://github.com/overtrue/wechat/pull/541) ([overtrue](https://github.com/overtrue))
+- 改变了http 中 json 方法的接口, 从而支持 添加 添加 query参数 [\#539](https://github.com/overtrue/wechat/pull/539) ([shoaly](https://github.com/shoaly))
+- 提交 [\#537](https://github.com/overtrue/wechat/pull/537) ([shoaly](https://github.com/shoaly))
+- Apply fixes from StyleCI [\#535](https://github.com/overtrue/wechat/pull/535) ([overtrue](https://github.com/overtrue))
+
+## [3.1.9](https://github.com/overtrue/wechat/tree/3.1.9) (2016-12-01)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.8...3.1.9)
+
+**Closed issues:**
+
+- 还是不懂怎么获取unionid [\#531](https://github.com/overtrue/wechat/issues/531)
+- Scope 参数错误或没有 Scope 权限 [\#528](https://github.com/overtrue/wechat/issues/528)
+- $\_SERVER\['SERVER\_ADDR'\] 在mac php7中获取不到 [\#520](https://github.com/overtrue/wechat/issues/520)
+- 能否永久素材其他类型封装个download方法,跟临时一样 [\#505](https://github.com/overtrue/wechat/issues/505)
+- V3.1 JSSDK使用疑惑 [\#503](https://github.com/overtrue/wechat/issues/503)
+- 如何加入QQ群 [\#501](https://github.com/overtrue/wechat/issues/501)
+- 能否在下一个版本把企业的相关接口整合集成进去 [\#496](https://github.com/overtrue/wechat/issues/496)
+- 既然使用了monolog,那么在Application::initializeLogger只使用了文件流的特定形式来记录日志是否合理? [\#494](https://github.com/overtrue/wechat/issues/494)
+- configForShareAddress [\#482](https://github.com/overtrue/wechat/issues/482)
+- 更新微信文章的时候MatialEasyWeChat\Material,如果设置了show\_pic\_cover和content\_source\_url不会生效 [\#470](https://github.com/overtrue/wechat/issues/470)
+- 请问 SDK 是否支持授权接入的公众号接口调用? [\#438](https://github.com/overtrue/wechat/issues/438)
+- 通过unionid发送信息。 [\#411](https://github.com/overtrue/wechat/issues/411)
+- 【新增】设备管理 [\#77](https://github.com/overtrue/wechat/issues/77)
+
+**Merged pull requests:**
+
+- Add support wechat open platform. [\#532](https://github.com/overtrue/wechat/pull/532) ([mingyoung](https://github.com/mingyoung))
+- Applied fixes from StyleCI [\#530](https://github.com/overtrue/wechat/pull/530) ([overtrue](https://github.com/overtrue))
+- 新增硬件设备api [\#529](https://github.com/overtrue/wechat/pull/529) ([soone](https://github.com/soone))
+
+## [3.1.8](https://github.com/overtrue/wechat/tree/3.1.8) (2016-11-23)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.7...3.1.8)
+
+**Closed issues:**
+
+- SCAN 事件会出现无法提供服务 [\#525](https://github.com/overtrue/wechat/issues/525)
+
+## [3.1.7](https://github.com/overtrue/wechat/tree/3.1.7) (2016-10-26)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.6...3.1.7)
+
+**Closed issues:**
+
+- preg\_replace unicode 的兼容问题 [\#515](https://github.com/overtrue/wechat/issues/515)
+
+**Merged pull requests:**
+
+- support psr-http-message-bridge 1.0 [\#524](https://github.com/overtrue/wechat/pull/524) ([wppd](https://github.com/wppd))
+- Applied fixes from StyleCI [\#523](https://github.com/overtrue/wechat/pull/523) ([overtrue](https://github.com/overtrue))
+- for \#520 [\#522](https://github.com/overtrue/wechat/pull/522) ([jinchun](https://github.com/jinchun))
+
+## [3.1.6](https://github.com/overtrue/wechat/tree/3.1.6) (2016-10-19)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.5...3.1.6)
+
+**Closed issues:**
+
+- PHP Fatal error: Uncaught HttpException [\#517](https://github.com/overtrue/wechat/issues/517)
+- 微信支付回调出错 [\#514](https://github.com/overtrue/wechat/issues/514)
+
+**Merged pull requests:**
+
+- Fix xml preg replace [\#519](https://github.com/overtrue/wechat/pull/519) ([springjk](https://github.com/springjk))
+- fix the DOC [\#518](https://github.com/overtrue/wechat/pull/518) ([ac1982](https://github.com/ac1982))
+
+## [3.1.5](https://github.com/overtrue/wechat/tree/3.1.5) (2016-10-13)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.4...3.1.5)
+
+**Closed issues:**
+
+- wechat 在 larave l5.3 使用 passport 包下无法安装 [\#513](https://github.com/overtrue/wechat/issues/513)
+
+**Merged pull requests:**
+
+- Applied fixes from StyleCI [\#512](https://github.com/overtrue/wechat/pull/512) ([overtrue](https://github.com/overtrue))
+
+## [3.1.4](https://github.com/overtrue/wechat/tree/3.1.4) (2016-10-12)
+[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.39...3.1.4)
+
+**Closed issues:**
+
+- 微信卡券特殊票券创建之后为什么无法更新卡券信息一致提示code非法。 [\#511](https://github.com/overtrue/wechat/issues/511)
+- 请添加 「退款方式」 参数 [\#509](https://github.com/overtrue/wechat/issues/509)
+- 2.1.40命名空间巨变引发的重大问题\(疑似提错版本了\) [\#508](https://github.com/overtrue/wechat/issues/508)
+- 卡券核销、查询建议 [\#506](https://github.com/overtrue/wechat/issues/506)
+- 支付重复回调问题 [\#504](https://github.com/overtrue/wechat/issues/504)
+
+**Merged pull requests:**
+
+- Changed method doc to the right accepted param type [\#510](https://github.com/overtrue/wechat/pull/510) ([marianoasselborn](https://github.com/marianoasselborn))
+- 增加判断是否有人工客服帐号,避免出现无账号时候,头像为默认头像的情况 [\#502](https://github.com/overtrue/wechat/pull/502) ([hello2t](https://github.com/hello2t))
+- Applied fixes from StyleCI [\#500](https://github.com/overtrue/wechat/pull/500) ([overtrue](https://github.com/overtrue))
+- 为initializeLogger日志初始话函数添加判断分支 [\#499](https://github.com/overtrue/wechat/pull/499) ([403studio](https://github.com/403studio))
+
+## [2.1.39](https://github.com/overtrue/wechat/tree/2.1.39) (2016-09-05)
+[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.41...2.1.39)
+
+## [2.1.41](https://github.com/overtrue/wechat/tree/2.1.41) (2016-09-05)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.3...2.1.41)
+
+**Closed issues:**
+
+- 调用接口次数超过最大限制问题 [\#493](https://github.com/overtrue/wechat/issues/493)
+- 微信退款证书报错 Unable to set private key file  [\#492](https://github.com/overtrue/wechat/issues/492)
+- 微信支付存在问题 [\#489](https://github.com/overtrue/wechat/issues/489)
+- 预支付下单 response body 为空 [\#488](https://github.com/overtrue/wechat/issues/488)
+- https check issue [\#486](https://github.com/overtrue/wechat/issues/486)
+
+**Merged pull requests:**
+
+- update composer.json [\#498](https://github.com/overtrue/wechat/pull/498) ([ac1982](https://github.com/ac1982))
+- use openssl instead of mcrypt [\#497](https://github.com/overtrue/wechat/pull/497) ([ac1982](https://github.com/ac1982))
+- 修复 with 方法带数据的问题 [\#491](https://github.com/overtrue/wechat/pull/491) ([XiaoLer](https://github.com/XiaoLer))
+
+## [3.1.3](https://github.com/overtrue/wechat/tree/3.1.3) (2016-08-08)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.2...3.1.3)
+
+**Closed issues:**
+
+- Laravel中写的最简单的例子在phpunit出错。 [\#485](https://github.com/overtrue/wechat/issues/485)
+- 微信的消息回复的FromUserName和ToUserName是不是对调了 [\#484](https://github.com/overtrue/wechat/issues/484)
+- 微信红包不能发给别的公众号的用户吗 [\#483](https://github.com/overtrue/wechat/issues/483)
+- 用户授权登录问题 [\#481](https://github.com/overtrue/wechat/issues/481)
+- cURL error 56: SSLRead\(\) return error -9806 [\#473](https://github.com/overtrue/wechat/issues/473)
+- 会员卡开卡字段文档有错误 [\#471](https://github.com/overtrue/wechat/issues/471)
+- Getting more done in GitHub with ZenHub [\#439](https://github.com/overtrue/wechat/issues/439)
+- 微信支付下单错误 [\#376](https://github.com/overtrue/wechat/issues/376)
+
+**Merged pull requests:**
+
+- update the File class to recognize pdf file. [\#480](https://github.com/overtrue/wechat/pull/480) ([ac1982](https://github.com/ac1982))
+- update testActivateUserForm [\#478](https://github.com/overtrue/wechat/pull/478) ([wangniuniu](https://github.com/wangniuniu))
+- Scrutinizer Auto-Fixes [\#477](https://github.com/overtrue/wechat/pull/477) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- Applied fixes from StyleCI [\#476](https://github.com/overtrue/wechat/pull/476) ([overtrue](https://github.com/overtrue))
+- Scrutinizer Auto-Fixes [\#475](https://github.com/overtrue/wechat/pull/475) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- 开放自定义prefix和缓存键值方法 [\#474](https://github.com/overtrue/wechat/pull/474) ([XiaoLer](https://github.com/XiaoLer))
+- Applied fixes from StyleCI [\#469](https://github.com/overtrue/wechat/pull/469) ([overtrue](https://github.com/overtrue))
+- modify stats [\#468](https://github.com/overtrue/wechat/pull/468) ([wangniuniu](https://github.com/wangniuniu))
+
+## [3.1.2](https://github.com/overtrue/wechat/tree/3.1.2) (2016-07-21)
+[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.38...3.1.2)
+
+**Closed issues:**
+
+- 素材管理中,上传图文下的上传图片,关于返回内容的差异 [\#466](https://github.com/overtrue/wechat/issues/466)
+- spbill\_create\_ip参数设置 [\#461](https://github.com/overtrue/wechat/issues/461)
+
+**Merged pull requests:**
+
+- 更新获取标签下粉丝列表方法 [\#467](https://github.com/overtrue/wechat/pull/467) ([dingdayu](https://github.com/dingdayu))
+- Applied fixes from StyleCI [\#465](https://github.com/overtrue/wechat/pull/465) ([overtrue](https://github.com/overtrue))
+- card module. [\#464](https://github.com/overtrue/wechat/pull/464) ([wangniuniu](https://github.com/wangniuniu))
+- Applied fixes from StyleCI [\#463](https://github.com/overtrue/wechat/pull/463) ([overtrue](https://github.com/overtrue))
+- Scrutinizer Auto-Fixes [\#462](https://github.com/overtrue/wechat/pull/462) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+
+## [2.1.38](https://github.com/overtrue/wechat/tree/2.1.38) (2016-07-16)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.1...2.1.38)
+
+**Closed issues:**
+
+- 请问卡券管理功能整合上日程表了吗 [\#454](https://github.com/overtrue/wechat/issues/454)
+
+**Merged pull requests:**
+
+- Typo. [\#460](https://github.com/overtrue/wechat/pull/460) ([tianyong90](https://github.com/tianyong90))
+- Applied fixes from StyleCI [\#459](https://github.com/overtrue/wechat/pull/459) ([overtrue](https://github.com/overtrue))
+- add voice recognition [\#458](https://github.com/overtrue/wechat/pull/458) ([leniy](https://github.com/leniy))
+- Applied fixes from StyleCI [\#457](https://github.com/overtrue/wechat/pull/457) ([overtrue](https://github.com/overtrue))
+- Update API.php [\#456](https://github.com/overtrue/wechat/pull/456) ([marvin8212](https://github.com/marvin8212))
+- Update XML.php [\#455](https://github.com/overtrue/wechat/pull/455) ([canon4ever](https://github.com/canon4ever))
+
+## [3.1.1](https://github.com/overtrue/wechat/tree/3.1.1) (2016-07-12)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.0...3.1.1)
+
+**Closed issues:**
+
+- 拿到code=CODE&state=STATE之后怎么拿到openid? [\#452](https://github.com/overtrue/wechat/issues/452)
+- 安装出错 [\#450](https://github.com/overtrue/wechat/issues/450)
+- 自定义菜单接口\(新版\)出错 [\#448](https://github.com/overtrue/wechat/issues/448)
+- h5上没法打开微信app授权界面 [\#447](https://github.com/overtrue/wechat/issues/447)
+- 重构卡券 [\#76](https://github.com/overtrue/wechat/issues/76)
+
+**Merged pull requests:**
+
+- typos. [\#453](https://github.com/overtrue/wechat/pull/453) ([tianye](https://github.com/tianye))
+- edit readme.md [\#451](https://github.com/overtrue/wechat/pull/451) ([tianyong90](https://github.com/tianyong90))
+- Add cache driver config. [\#449](https://github.com/overtrue/wechat/pull/449) ([dingdayu](https://github.com/dingdayu))
+
+## [3.1.0](https://github.com/overtrue/wechat/tree/3.1.0) (2016-06-28)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.0.21...3.1.0)
+
+**Merged pull requests:**
+
+- Applied fixes from StyleCI [\#446](https://github.com/overtrue/wechat/pull/446) ([overtrue](https://github.com/overtrue))
+- New Staff API. [\#445](https://github.com/overtrue/wechat/pull/445) ([overtrue](https://github.com/overtrue))
+- 2.1 [\#444](https://github.com/overtrue/wechat/pull/444) ([dongnanyanhai](https://github.com/dongnanyanhai))
+- Fix path. [\#443](https://github.com/overtrue/wechat/pull/443) ([overtrue](https://github.com/overtrue))
+
+## [3.0.21](https://github.com/overtrue/wechat/tree/3.0.21) (2016-06-17)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.0.1...3.0.21)
+
+**Closed issues:**
+
+- scan出现公众号暂时无法服务的消息 [\#436](https://github.com/overtrue/wechat/issues/436)
+- scan出现公众号暂时无法服务的消息 [\#435](https://github.com/overtrue/wechat/issues/435)
+- 用户标签接口无法使用 [\#433](https://github.com/overtrue/wechat/issues/433)
+- WeChatProvider下的getAuthUrl个人觉得应该暴露出来 [\#432](https://github.com/overtrue/wechat/issues/432)
+- 支持二维码扫描进入公众号推送的SCAN事件 [\#431](https://github.com/overtrue/wechat/issues/431)
+- \[3.0\] EasyWeChat\Support\XML::parse方法会将空节点解析为空数组,而不是空字符串 [\#426](https://github.com/overtrue/wechat/issues/426)
+- 下载二维码, $qrcode-\>download\($ticket,$paths\);  目录参数不可加入 中文 [\#420](https://github.com/overtrue/wechat/issues/420)
+- \[help want\]Is hard to change default configuration of GuzzleHttp [\#415](https://github.com/overtrue/wechat/issues/415)
+- PHP7.0 curl\_setopt 设置问题 [\#413](https://github.com/overtrue/wechat/issues/413)
+- 无法通知微信支付完成 [\#412](https://github.com/overtrue/wechat/issues/412)
+- 如何获取用户的unionid? [\#407](https://github.com/overtrue/wechat/issues/407)
+- 是否支持多框架 [\#406](https://github.com/overtrue/wechat/issues/406)
+- fuckTheWeChatInvalidJSON [\#405](https://github.com/overtrue/wechat/issues/405)
+- Class 'GuzzleHttp\Middleware' not found [\#404](https://github.com/overtrue/wechat/issues/404)
+- 支付统一下单接口签名错误 [\#402](https://github.com/overtrue/wechat/issues/402)
+- payment里没有configForJSSDKPayment方法 [\#401](https://github.com/overtrue/wechat/issues/401)
+- 查询支付的地址多了一个空格,导致查询失败,去掉最后的那个空格后就好了 [\#393](https://github.com/overtrue/wechat/issues/393)
+- 网页授权过不了 [\#392](https://github.com/overtrue/wechat/issues/392)
+- 微信AccessToken被动更新可能会有并发更新的情况出现 [\#390](https://github.com/overtrue/wechat/issues/390)
+- 临时素材下载,文件名和扩展名之间会有2个\[.\] [\#389](https://github.com/overtrue/wechat/issues/389)
+- 有一个地方变量名对不上 [\#380](https://github.com/overtrue/wechat/issues/380)
+- 自定义缓存 [\#379](https://github.com/overtrue/wechat/issues/379)
+- https://easywechat.org/ 底部 “开始使用” url拼错 [\#378](https://github.com/overtrue/wechat/issues/378)
+- 在server.php里面调用yii的model,一直报错 [\#375](https://github.com/overtrue/wechat/issues/375)
+- overture/wechat 2.1.36\(客服消息转发错误\) [\#374](https://github.com/overtrue/wechat/issues/374)
+- 建议支持开发模式下禁用验证 [\#373](https://github.com/overtrue/wechat/issues/373)
+- https://easywechat.org/ 导航 首页 about:blank [\#370](https://github.com/overtrue/wechat/issues/370)
+- laravel 下session问题 [\#369](https://github.com/overtrue/wechat/issues/369)
+- 关于Access——toekn [\#368](https://github.com/overtrue/wechat/issues/368)
+- 返回支付页面时报错:"access\_token" could not be empty [\#367](https://github.com/overtrue/wechat/issues/367)
+- xampp下js-\>config报错 [\#366](https://github.com/overtrue/wechat/issues/366)
+- 官方文档有误 [\#360](https://github.com/overtrue/wechat/issues/360)
+- \[BUG\] 微信收货地址无法成功 [\#359](https://github.com/overtrue/wechat/issues/359)
+- 无法获取 $message-\>ScanCodeInfo-\>ScanType 对象 [\#358](https://github.com/overtrue/wechat/issues/358)
+- \[Bugs\] 项目文档首页跳转问题 [\#357](https://github.com/overtrue/wechat/issues/357)
+- Business和UnifiedOrder没有定义 [\#356](https://github.com/overtrue/wechat/issues/356)
+- 你的网站访问不了。。。。https://easywechat.org/ [\#352](https://github.com/overtrue/wechat/issues/352)
+- 连续多次执行微信支付退款报错 [\#348](https://github.com/overtrue/wechat/issues/348)
+- 客服操作 都是  -1 错误 [\#344](https://github.com/overtrue/wechat/issues/344)
+- 请使用openssl 而不是不安全的mcrypt来加密 [\#342](https://github.com/overtrue/wechat/issues/342)
+- 文本类型的通知消息 [\#341](https://github.com/overtrue/wechat/issues/341)
+- 服务器配置https 并且 通过阿里云 https cdn之后, 会出现 https 判断语句失效 [\#338](https://github.com/overtrue/wechat/issues/338)
+- 作者请问者个sdk支持企业号吗? [\#336](https://github.com/overtrue/wechat/issues/336)
+- laravel 5.1引入包报错 [\#331](https://github.com/overtrue/wechat/issues/331)
+- 申请退款有问题 [\#328](https://github.com/overtrue/wechat/issues/328)
+- 订单相关接口bug [\#327](https://github.com/overtrue/wechat/issues/327)
+- 临时素材接口无法使用 [\#319](https://github.com/overtrue/wechat/issues/319)
+- 使用sendNormal\(\),sendGroup\(\)发送红包时,报Undefined index: HTTP\_CLIENT\_IP [\#316](https://github.com/overtrue/wechat/issues/316)
+- v3中微信卡券功能缺失? [\#307](https://github.com/overtrue/wechat/issues/307)
+- 测试 [\#305](https://github.com/overtrue/wechat/issues/305)
+- \[3.0\] 永久素材上传视频无法上传问题 [\#304](https://github.com/overtrue/wechat/issues/304)
+- Cannot destroy active lambda function [\#296](https://github.com/overtrue/wechat/issues/296)
+- 微信支付-》企业付款也可以增加个类上去,跟企业红包类似 [\#232](https://github.com/overtrue/wechat/issues/232)
+
+**Merged pull requests:**
+
+- Applied fixes from StyleCI [\#442](https://github.com/overtrue/wechat/pull/442) ([overtrue](https://github.com/overtrue))
+- NGINX HTTPS无法签名 [\#441](https://github.com/overtrue/wechat/pull/441) ([ares333](https://github.com/ares333))
+- Develop [\#440](https://github.com/overtrue/wechat/pull/440) ([overtrue](https://github.com/overtrue))
+- Develop [\#437](https://github.com/overtrue/wechat/pull/437) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#434](https://github.com/overtrue/wechat/pull/434) ([overtrue](https://github.com/overtrue))
+- 修改错误提示信息,方便跟踪错误 [\#430](https://github.com/overtrue/wechat/pull/430) ([zerozh](https://github.com/zerozh))
+- Develop [\#429](https://github.com/overtrue/wechat/pull/429) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#428](https://github.com/overtrue/wechat/pull/428) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#427](https://github.com/overtrue/wechat/pull/427) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#425](https://github.com/overtrue/wechat/pull/425) ([overtrue](https://github.com/overtrue))
+- update annotation [\#424](https://github.com/overtrue/wechat/pull/424) ([lilocon](https://github.com/lilocon))
+- Develop [\#421](https://github.com/overtrue/wechat/pull/421) ([overtrue](https://github.com/overtrue))
+- Set default timeout. [\#419](https://github.com/overtrue/wechat/pull/419) ([overtrue](https://github.com/overtrue))
+- Develop [\#418](https://github.com/overtrue/wechat/pull/418) ([overtrue](https://github.com/overtrue))
+- Develop [\#416](https://github.com/overtrue/wechat/pull/416) ([overtrue](https://github.com/overtrue))
+- better implementation for prepare oauth callback url [\#414](https://github.com/overtrue/wechat/pull/414) ([lichunqiang](https://github.com/lichunqiang))
+- Develop [\#410](https://github.com/overtrue/wechat/pull/410) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#409](https://github.com/overtrue/wechat/pull/409) ([overtrue](https://github.com/overtrue))
+- 增加微信支付服务商支持 [\#408](https://github.com/overtrue/wechat/pull/408) ([takatost](https://github.com/takatost))
+- Develop [\#403](https://github.com/overtrue/wechat/pull/403) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#400](https://github.com/overtrue/wechat/pull/400) ([overtrue](https://github.com/overtrue))
+- Scrutinizer Auto-Fixes [\#399](https://github.com/overtrue/wechat/pull/399) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- Develop [\#398](https://github.com/overtrue/wechat/pull/398) ([overtrue](https://github.com/overtrue))
+- Develop [\#397](https://github.com/overtrue/wechat/pull/397) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#396](https://github.com/overtrue/wechat/pull/396) ([overtrue](https://github.com/overtrue))
+- Typo & Improve code. [\#395](https://github.com/overtrue/wechat/pull/395) ([jinchun](https://github.com/jinchun))
+- Develop [\#394](https://github.com/overtrue/wechat/pull/394) ([overtrue](https://github.com/overtrue))
+- Bugfix close \#389 [\#391](https://github.com/overtrue/wechat/pull/391) ([overtrue](https://github.com/overtrue))
+- Update NoticeNoticeTest.php [\#388](https://github.com/overtrue/wechat/pull/388) ([xiabeifeng](https://github.com/xiabeifeng))
+- Update Notice.php [\#387](https://github.com/overtrue/wechat/pull/387) ([xiabeifeng](https://github.com/xiabeifeng))
+- Tests for \#384 [\#386](https://github.com/overtrue/wechat/pull/386) ([xiabeifeng](https://github.com/xiabeifeng))
+- Improve Notice API. [\#384](https://github.com/overtrue/wechat/pull/384) ([xiabeifeng](https://github.com/xiabeifeng))
+- 对应根 版本依赖 [\#382](https://github.com/overtrue/wechat/pull/382) ([parkshinhye](https://github.com/parkshinhye))
+- Develop [\#381](https://github.com/overtrue/wechat/pull/381) ([overtrue](https://github.com/overtrue))
+- Develop [\#377](https://github.com/overtrue/wechat/pull/377) ([overtrue](https://github.com/overtrue))
+- Fix test for \#371 [\#372](https://github.com/overtrue/wechat/pull/372) ([overtrue](https://github.com/overtrue))
+- 刷卡支付不需要notify\_url参数 [\#371](https://github.com/overtrue/wechat/pull/371) ([lilocon](https://github.com/lilocon))
+- Applied fixes from StyleCI [\#365](https://github.com/overtrue/wechat/pull/365) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#364](https://github.com/overtrue/wechat/pull/364) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#363](https://github.com/overtrue/wechat/pull/363) ([overtrue](https://github.com/overtrue))
+- Update composer.json [\#361](https://github.com/overtrue/wechat/pull/361) ([jaychan](https://github.com/jaychan))
+- Applied fixes from StyleCI [\#355](https://github.com/overtrue/wechat/pull/355) ([overtrue](https://github.com/overtrue))
+- \[ci skip\]fix document typo [\#354](https://github.com/overtrue/wechat/pull/354) ([lichunqiang](https://github.com/lichunqiang))
+- 自定义Logger [\#353](https://github.com/overtrue/wechat/pull/353) ([lilocon](https://github.com/lilocon))
+- Update Refund.php [\#351](https://github.com/overtrue/wechat/pull/351) ([jaring](https://github.com/jaring))
+- Applied fixes from StyleCI [\#350](https://github.com/overtrue/wechat/pull/350) ([overtrue](https://github.com/overtrue))
+- OpenSSL bugfix. [\#349](https://github.com/overtrue/wechat/pull/349) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#347](https://github.com/overtrue/wechat/pull/347) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#346](https://github.com/overtrue/wechat/pull/346) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#345](https://github.com/overtrue/wechat/pull/345) ([overtrue](https://github.com/overtrue))
+- 添加代码提示 [\#343](https://github.com/overtrue/wechat/pull/343) ([lilocon](https://github.com/lilocon))
+- Applied fixes from StyleCI [\#340](https://github.com/overtrue/wechat/pull/340) ([overtrue](https://github.com/overtrue))
+- Fix bug: Payment::downloadBill\(\) response error. [\#339](https://github.com/overtrue/wechat/pull/339) ([overtrue](https://github.com/overtrue))
+- change get\_client\_ip to get\_server\_ip [\#335](https://github.com/overtrue/wechat/pull/335) ([tianyong90](https://github.com/tianyong90))
+- Payment SSL. [\#334](https://github.com/overtrue/wechat/pull/334) ([overtrue](https://github.com/overtrue))
+- Add a helper to get correct client ip address. fixed \#316 [\#333](https://github.com/overtrue/wechat/pull/333) ([tianyong90](https://github.com/tianyong90))
+- Dependency Bugfix. overtrue/laravel-wechat\#24 [\#332](https://github.com/overtrue/wechat/pull/332) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#330](https://github.com/overtrue/wechat/pull/330) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#329](https://github.com/overtrue/wechat/pull/329) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#326](https://github.com/overtrue/wechat/pull/326) ([overtrue](https://github.com/overtrue))
+- Add order default notify\_url. [\#325](https://github.com/overtrue/wechat/pull/325) ([foreverglory](https://github.com/foreverglory))
+- Revert "Applied fixes from StyleCI" [\#323](https://github.com/overtrue/wechat/pull/323) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#322](https://github.com/overtrue/wechat/pull/322) ([overtrue](https://github.com/overtrue))
+- Develop [\#321](https://github.com/overtrue/wechat/pull/321) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#320](https://github.com/overtrue/wechat/pull/320) ([overtrue](https://github.com/overtrue))
+- 模板消息添加【 获取模板列表】和【 删除模板】接口 [\#318](https://github.com/overtrue/wechat/pull/318) ([forecho](https://github.com/forecho))
+- Applied fixes from StyleCI [\#314](https://github.com/overtrue/wechat/pull/314) ([overtrue](https://github.com/overtrue))
+- fix Temporary upload bug [\#313](https://github.com/overtrue/wechat/pull/313) ([mani95lisa](https://github.com/mani95lisa))
+- Applied fixes from StyleCI [\#312](https://github.com/overtrue/wechat/pull/312) ([overtrue](https://github.com/overtrue))
+- MerchantPay Class [\#311](https://github.com/overtrue/wechat/pull/311) ([ac1982](https://github.com/ac1982))
+- Applied fixes from StyleCI [\#309](https://github.com/overtrue/wechat/pull/309) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#308](https://github.com/overtrue/wechat/pull/308) ([overtrue](https://github.com/overtrue))
+- 删除裂变红包接口中的ip参数 [\#306](https://github.com/overtrue/wechat/pull/306) ([xjchengo](https://github.com/xjchengo))
+- fix code style and some spelling mistakes [\#303](https://github.com/overtrue/wechat/pull/303) ([jinchun](https://github.com/jinchun))
+- Merge Develop [\#302](https://github.com/overtrue/wechat/pull/302) ([overtrue](https://github.com/overtrue))
+- Add method for app payment [\#301](https://github.com/overtrue/wechat/pull/301) ([lichunqiang](https://github.com/lichunqiang))
+- Removed the return syntax [\#300](https://github.com/overtrue/wechat/pull/300) ([lichunqiang](https://github.com/lichunqiang))
+- add return tag [\#299](https://github.com/overtrue/wechat/pull/299) ([lichunqiang](https://github.com/lichunqiang))
+- Merge Develop [\#298](https://github.com/overtrue/wechat/pull/298) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#297](https://github.com/overtrue/wechat/pull/297) ([overtrue](https://github.com/overtrue))
+- \[ci skip\]Update .gitattributes [\#295](https://github.com/overtrue/wechat/pull/295) ([lichunqiang](https://github.com/lichunqiang))
+- Merge Develop [\#294](https://github.com/overtrue/wechat/pull/294) ([overtrue](https://github.com/overtrue))
+
+## [3.0.1](https://github.com/overtrue/wechat/tree/3.0.1) (2016-02-19)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.0...3.0.1)
+
+**Closed issues:**
+
+- composer 安装 3.0版本,报错如下: [\#291](https://github.com/overtrue/wechat/issues/291)
+- \[3.0\] 下载永久素材时,微信返回的Content-Type不正确,导致出错。 [\#290](https://github.com/overtrue/wechat/issues/290)
+- 挖个坑,自己跳 [\#147](https://github.com/overtrue/wechat/issues/147)
+
+**Merged pull requests:**
+
+- Applied fixes from StyleCI [\#293](https://github.com/overtrue/wechat/pull/293) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#292](https://github.com/overtrue/wechat/pull/292) ([overtrue](https://github.com/overtrue))
+
+## [3.0](https://github.com/overtrue/wechat/tree/3.0) (2016-02-17)
+[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.0...3.0)
+
+**Implemented enhancements:**
+
+- MIME json 格式检查优化 [\#49](https://github.com/overtrue/wechat/issues/49)
+- 获取 refresh\_token,access\_token [\#43](https://github.com/overtrue/wechat/issues/43)
+- 关于API\_TOKEN\_REFRESH [\#20](https://github.com/overtrue/wechat/issues/20)
+
+**Closed issues:**
+
+- \[3.0\] 无法获取用户分组信息 [\#285](https://github.com/overtrue/wechat/issues/285)
+- 新的laravel 5.2 不能兼容了 [\#284](https://github.com/overtrue/wechat/issues/284)
+- \[3.0\]Message/Article类的$properties内的source\_url没有正常转换为content\_source\_url. [\#281](https://github.com/overtrue/wechat/issues/281)
+- 3.0删除个性菜单失败 [\#280](https://github.com/overtrue/wechat/issues/280)
+- 也许你该给一个代码贡献规范 [\#277](https://github.com/overtrue/wechat/issues/277)
+- 3.0网页授权时scope为snsapi\_base得不到openid [\#276](https://github.com/overtrue/wechat/issues/276)
+- wechat3.0中 有2个地方的js调用参数不一样,超哥没有提供 [\#272](https://github.com/overtrue/wechat/issues/272)
+- 我想知道2.X和3.0有什么大的区别! [\#270](https://github.com/overtrue/wechat/issues/270)
+- 2.1: Link 消息类型没有实现 [\#269](https://github.com/overtrue/wechat/issues/269)
+- 关于模板消息换行的问题 [\#266](https://github.com/overtrue/wechat/issues/266)
+- easywechat Invalid request [\#265](https://github.com/overtrue/wechat/issues/265)
+- 40029不合法的oauth\_code [\#264](https://github.com/overtrue/wechat/issues/264)
+- 下载素材的一个小问题 [\#263](https://github.com/overtrue/wechat/issues/263)
+- \[2.1\] 微信自定义菜单结构变更导致`Menu::get\(\)` 无法读取个性化菜单 [\#262](https://github.com/overtrue/wechat/issues/262)
+- payment中是不是不包含H5和JS的生成配置文件的方法了? [\#261](https://github.com/overtrue/wechat/issues/261)
+- payment下prepare方法bug [\#260](https://github.com/overtrue/wechat/issues/260)
+- UserServiceProvider中似乎忘记注册user.group了 [\#256](https://github.com/overtrue/wechat/issues/256)
+- 2.1.X版媒体下载没有扩展名 [\#252](https://github.com/overtrue/wechat/issues/252)
+- 为什么所有的子模块在自己的库都是develop分支 [\#247](https://github.com/overtrue/wechat/issues/247)
+- 网页授权使用跳转的bug [\#246](https://github.com/overtrue/wechat/issues/246)
+- typo of variable [\#245](https://github.com/overtrue/wechat/issues/245)
+- The implementation class of ServerServiceProvider missing an important  [\#244](https://github.com/overtrue/wechat/issues/244)
+- \[3.0\]\[payment\] 两个可能的bug [\#235](https://github.com/overtrue/wechat/issues/235)
+- 发送多图文 [\#233](https://github.com/overtrue/wechat/issues/233)
+- 自定义菜单返回应该把个性化自定义菜单也一起返回 [\#231](https://github.com/overtrue/wechat/issues/231)
+- 发送模板消息 CRUL 错误 [\#223](https://github.com/overtrue/wechat/issues/223)
+- 客服接口暂时测到有3个bug,麻烦修复 [\#222](https://github.com/overtrue/wechat/issues/222)
+- JSSDK access\_token missing [\#211](https://github.com/overtrue/wechat/issues/211)
+- Js.php/ticket [\#210](https://github.com/overtrue/wechat/issues/210)
+- 微信支付里有一个收货地址共享 ,超哥你这里没有,可以加一下不? [\#204](https://github.com/overtrue/wechat/issues/204)
+- 小问题 [\#203](https://github.com/overtrue/wechat/issues/203)
+- 网页授权 跳转 [\#202](https://github.com/overtrue/wechat/issues/202)
+- access token 重复添加的问题 [\#201](https://github.com/overtrue/wechat/issues/201)
+- authorize snsapi\_base 下可以获取unionid [\#198](https://github.com/overtrue/wechat/issues/198)
+- 网页授权 [\#189](https://github.com/overtrue/wechat/issues/189)
+- 一点建议 [\#188](https://github.com/overtrue/wechat/issues/188)
+- 接口更新-新增临时素材接口变动 [\#186](https://github.com/overtrue/wechat/issues/186)
+- 接入多个公众号不用id [\#185](https://github.com/overtrue/wechat/issues/185)
+- \[Insight\] Files should not be executable [\#184](https://github.com/overtrue/wechat/issues/184)
+- 建议不要写死Http [\#183](https://github.com/overtrue/wechat/issues/183)
+- laravel4.2安装不成功 [\#182](https://github.com/overtrue/wechat/issues/182)
+- 是否支持laravel4.2 [\#181](https://github.com/overtrue/wechat/issues/181)
+- 微信出个性化菜单了,希望支持 [\#180](https://github.com/overtrue/wechat/issues/180)
+- 3.0 composer依赖Symfony2.7。能不能支持Symfony3.0? [\#179](https://github.com/overtrue/wechat/issues/179)
+- 发送链接类消息错误 [\#175](https://github.com/overtrue/wechat/issues/175)
+- Throw Exception的时候 Intel server status 设置为200是不是好一些 [\#174](https://github.com/overtrue/wechat/issues/174)
+- 生成临时二维码时,返回EventKey不是传递的值 [\#173](https://github.com/overtrue/wechat/issues/173)
+- 关于素材获取的一个建议 [\#172](https://github.com/overtrue/wechat/issues/172)
+- 能否增加微信APP支付相关方法 [\#171](https://github.com/overtrue/wechat/issues/171)
+- 微信回调URL回调不到 [\#170](https://github.com/overtrue/wechat/issues/170)
+- 素材管理添加永久素材返回JSON/XML内容错误 [\#169](https://github.com/overtrue/wechat/issues/169)
+- \[消息的使用\] 中 \[上传素材文件\] 的文档示例貌似有误 [\#168](https://github.com/overtrue/wechat/issues/168)
+- 素材管理里的download方法不是很符合sdk一站式的解决. [\#165](https://github.com/overtrue/wechat/issues/165)
+- \[Wechat\]不合法的oauth\_code' in /src/Wechat/Http.php:124 [\#164](https://github.com/overtrue/wechat/issues/164)
+- AccessToken Expired Error Code [\#163](https://github.com/overtrue/wechat/issues/163)
+- 素材管理接口出错 [\#162](https://github.com/overtrue/wechat/issues/162)
+- 两处代码php5.4才能运行 [\#158](https://github.com/overtrue/wechat/issues/158)
+- extension is null when calling `download video` in wechat.media [\#157](https://github.com/overtrue/wechat/issues/157)
+- Payment/UnifiedOrder does not support serialize or create by array [\#155](https://github.com/overtrue/wechat/issues/155)
+- 没有找到"微信支付-\>查询订单"相关功能 [\#150](https://github.com/overtrue/wechat/issues/150)
+- 请教,Cache::setter中your\_custom\_set\_cache怎么使用 [\#149](https://github.com/overtrue/wechat/issues/149)
+- 发生异常时, 希望能把发送和接收的原始数据记录下来. [\#148](https://github.com/overtrue/wechat/issues/148)
+- 发送红包,证书错误 [\#144](https://github.com/overtrue/wechat/issues/144)
+- 发视频消息总返回 -1 [\#143](https://github.com/overtrue/wechat/issues/143)
+- 关于PHP版本 [\#141](https://github.com/overtrue/wechat/issues/141)
+- Server消息回复必须以事件方式吗? [\#140](https://github.com/overtrue/wechat/issues/140)
+- 微信支付相关文档细化 [\#138](https://github.com/overtrue/wechat/issues/138)
+- 好奇地问个问题,这项目的测试用例放在哪? [\#135](https://github.com/overtrue/wechat/issues/135)
+- 试了两次,真的不会用 [\#134](https://github.com/overtrue/wechat/issues/134)
+- 不知道这算不算是个BUG [\#133](https://github.com/overtrue/wechat/issues/133)
+- 微信小店 [\#130](https://github.com/overtrue/wechat/issues/130)
+- 多次遇到 accesstoken 无效的问题 [\#129](https://github.com/overtrue/wechat/issues/129)
+- MCH\_KEY 微信支付 [\#128](https://github.com/overtrue/wechat/issues/128)
+- 使用flightphp框架,验证URL的时候,在Apache下接入成功,在Nginx接入失败 [\#126](https://github.com/overtrue/wechat/issues/126)
+- 好东西!可惜没有我需要的微信红包 [\#125](https://github.com/overtrue/wechat/issues/125)
+- Cache存储部件可定制 [\#120](https://github.com/overtrue/wechat/issues/120)
+- 关于Bag [\#119](https://github.com/overtrue/wechat/issues/119)
+- 将代码部署到负载均衡上如何管理access token  [\#118](https://github.com/overtrue/wechat/issues/118)
+- 消息接受和回复时,如果不对消息做回复,该如何做? [\#117](https://github.com/overtrue/wechat/issues/117)
+- 请教一个问题 [\#116](https://github.com/overtrue/wechat/issues/116)
+- 关于 Cache [\#115](https://github.com/overtrue/wechat/issues/115)
+- 如何才能获取普通的access\_token [\#113](https://github.com/overtrue/wechat/issues/113)
+- $HTTP\_RAW\_POST\_DATA DEPRECATED [\#111](https://github.com/overtrue/wechat/issues/111)
+- App支付缺少错误码 [\#109](https://github.com/overtrue/wechat/issues/109)
+- 当用户信息有 " 字符时系统出错 \(用户与用户组管理接口\) [\#107](https://github.com/overtrue/wechat/issues/107)
+- 提示错误 [\#106](https://github.com/overtrue/wechat/issues/106)
+- 使用企业号的时候 接入失败啊,在验证url的时候 [\#104](https://github.com/overtrue/wechat/issues/104)
+- 支付签名错误 [\#101](https://github.com/overtrue/wechat/issues/101)
+- 微信支付.$payment-\>getConfig\(\)调用时候\[Wechat\]系统繁忙,此时请开发者稍候再试. [\#96](https://github.com/overtrue/wechat/issues/96)
+- wechat/src/Wechat/Payment/UnifiedOrder.php 小问题 [\#94](https://github.com/overtrue/wechat/issues/94)
+- 请教laravel中如何在微信支付中 catch  UnifiedOrder 抛出的异常? [\#93](https://github.com/overtrue/wechat/issues/93)
+- 是否可以增加一个第三方接口融合功能 [\#91](https://github.com/overtrue/wechat/issues/91)
+- 订单查询 [\#90](https://github.com/overtrue/wechat/issues/90)
+- 如何不下载图片,通过mediaId获取图片存储的URL [\#89](https://github.com/overtrue/wechat/issues/89)
+- 'Undefined index: HTTP\_HOST'  [\#88](https://github.com/overtrue/wechat/issues/88)
+- Undefined index: HTTP\_HOST [\#87](https://github.com/overtrue/wechat/issues/87)
+- 不能上传gif格式的图片素材 [\#84](https://github.com/overtrue/wechat/issues/84)
+- OAuth重构 [\#74](https://github.com/overtrue/wechat/issues/74)
+- \[3.0\] Tasks [\#50](https://github.com/overtrue/wechat/issues/50)
+- appId 和 appSecret不要作为各个类的构造参数 [\#114](https://github.com/overtrue/wechat/issues/114)
+- 增加debug相关的选项 [\#112](https://github.com/overtrue/wechat/issues/112)
+- 好像没有获取自动回复数据接口 [\#108](https://github.com/overtrue/wechat/issues/108)
+- js端查看微信卡券接口 chooseCard [\#79](https://github.com/overtrue/wechat/issues/79)
+- 【新增】支付 [\#78](https://github.com/overtrue/wechat/issues/78)
+- 模板消息重构 [\#75](https://github.com/overtrue/wechat/issues/75)
+- 素材下载自动识别MIME生成后缀 [\#54](https://github.com/overtrue/wechat/issues/54)
+- \[建议\] 深度结合微信多图文与素材管理 [\#46](https://github.com/overtrue/wechat/issues/46)
+- 群发功能 [\#18](https://github.com/overtrue/wechat/issues/18)
+
+**Merged pull requests:**
+
+- 3.0 [\#289](https://github.com/overtrue/wechat/pull/289) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#288](https://github.com/overtrue/wechat/pull/288) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#287](https://github.com/overtrue/wechat/pull/287) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#286](https://github.com/overtrue/wechat/pull/286) ([overtrue](https://github.com/overtrue))
+- Fix bug in batchGet method. [\#283](https://github.com/overtrue/wechat/pull/283) ([tianyong90](https://github.com/tianyong90))
+- Typo. [\#279](https://github.com/overtrue/wechat/pull/279) ([overtrue](https://github.com/overtrue))
+- Add contribution guide. resolves \#277 [\#278](https://github.com/overtrue/wechat/pull/278) ([overtrue](https://github.com/overtrue))
+- Develop [\#274](https://github.com/overtrue/wechat/pull/274) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#273](https://github.com/overtrue/wechat/pull/273) ([overtrue](https://github.com/overtrue))
+- Develop [\#271](https://github.com/overtrue/wechat/pull/271) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#268](https://github.com/overtrue/wechat/pull/268) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#267](https://github.com/overtrue/wechat/pull/267) ([overtrue](https://github.com/overtrue))
+- Update QRCode.php [\#258](https://github.com/overtrue/wechat/pull/258) ([webshiyue](https://github.com/webshiyue))
+- Add tests for LuckyMoney. [\#255](https://github.com/overtrue/wechat/pull/255) ([tianyong90](https://github.com/tianyong90))
+- CS. [\#254](https://github.com/overtrue/wechat/pull/254) ([overtrue](https://github.com/overtrue))
+- Scrutinizer Auto-Fixes [\#253](https://github.com/overtrue/wechat/pull/253) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- Applied fixes from StyleCI [\#251](https://github.com/overtrue/wechat/pull/251) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#250](https://github.com/overtrue/wechat/pull/250) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#249](https://github.com/overtrue/wechat/pull/249) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#248](https://github.com/overtrue/wechat/pull/248) ([overtrue](https://github.com/overtrue))
+- Merge from Develop [\#243](https://github.com/overtrue/wechat/pull/243) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#242](https://github.com/overtrue/wechat/pull/242) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#241](https://github.com/overtrue/wechat/pull/241) ([overtrue](https://github.com/overtrue))
+- Add Luckymoney. [\#240](https://github.com/overtrue/wechat/pull/240) ([tianyong90](https://github.com/tianyong90))
+- Applied fixes from StyleCI [\#237](https://github.com/overtrue/wechat/pull/237) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#234](https://github.com/overtrue/wechat/pull/234) ([overtrue](https://github.com/overtrue))
+- Multiple News Items Support [\#230](https://github.com/overtrue/wechat/pull/230) ([fanglinks](https://github.com/fanglinks))
+- Applied fixes from StyleCI [\#221](https://github.com/overtrue/wechat/pull/221) ([overtrue](https://github.com/overtrue))
+- \[3.0\]\[Bugfix\]发送图文消息缺少type [\#217](https://github.com/overtrue/wechat/pull/217) ([sunbiao0526](https://github.com/sunbiao0526))
+- fix Js.php 获取自定义cache对象 [\#215](https://github.com/overtrue/wechat/pull/215) ([sunbiao0526](https://github.com/sunbiao0526))
+- Applied fixes from StyleCI [\#197](https://github.com/overtrue/wechat/pull/197) ([overtrue](https://github.com/overtrue))
+- Add alias [\#196](https://github.com/overtrue/wechat/pull/196) ([ruchengtang](https://github.com/ruchengtang))
+- Applied fixes from StyleCI [\#195](https://github.com/overtrue/wechat/pull/195) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#194](https://github.com/overtrue/wechat/pull/194) ([overtrue](https://github.com/overtrue))
+- Add Broadcast. [\#193](https://github.com/overtrue/wechat/pull/193) ([ruchengtang](https://github.com/ruchengtang))
+- 微信红包类优化 [\#190](https://github.com/overtrue/wechat/pull/190) ([tianyong90](https://github.com/tianyong90))
+- Update ServerServiceProvider.php [\#187](https://github.com/overtrue/wechat/pull/187) ([ghost](https://github.com/ghost))
+- Update README\_EN.md [\#178](https://github.com/overtrue/wechat/pull/178) ([spekulatius](https://github.com/spekulatius))
+- 添加群发消息文档 [\#177](https://github.com/overtrue/wechat/pull/177) ([ruchengtang](https://github.com/ruchengtang))
+- 群发消息 [\#176](https://github.com/overtrue/wechat/pull/176) ([ruchengtang](https://github.com/ruchengtang))
+- Master [\#167](https://github.com/overtrue/wechat/pull/167) ([xiaohome](https://github.com/xiaohome))
+- 微信小店 [\#166](https://github.com/overtrue/wechat/pull/166) ([xiaohome](https://github.com/xiaohome))
+- 红包类更新 [\#161](https://github.com/overtrue/wechat/pull/161) ([overtrue](https://github.com/overtrue))
+- 加入摇一摇红包类,红包类提升至Overtrue命名空间 [\#160](https://github.com/overtrue/wechat/pull/160) ([tianyong90](https://github.com/tianyong90))
+- 2.1 [\#159](https://github.com/overtrue/wechat/pull/159) ([overtrue](https://github.com/overtrue))
+- Update QRCode.php [\#156](https://github.com/overtrue/wechat/pull/156) ([ruchengtang](https://github.com/ruchengtang))
+- 修复使用!=,来判断0 != null 的时候的一个bug [\#154](https://github.com/overtrue/wechat/pull/154) ([Liv1020](https://github.com/Liv1020))
+- 调整多客服类删除客服方法 [\#151](https://github.com/overtrue/wechat/pull/151) ([tianyong90](https://github.com/tianyong90))
+- 修复个bug [\#146](https://github.com/overtrue/wechat/pull/146) ([xiaohome](https://github.com/xiaohome))
+- Update README.md [\#142](https://github.com/overtrue/wechat/pull/142) ([parkshinhye](https://github.com/parkshinhye))
+- Fix code style to PSR-2 [\#139](https://github.com/overtrue/wechat/pull/139) ([tianyong90](https://github.com/tianyong90))
+- 加入红包工具类,支持现金和裂变红包的发送及查询 [\#137](https://github.com/overtrue/wechat/pull/137) ([tianyong90](https://github.com/tianyong90))
+- 卡券类批量获取卡券ID方法支持仅获取指定状态卡券 [\#132](https://github.com/overtrue/wechat/pull/132) ([tianyong90](https://github.com/tianyong90))
+- 添加客服 卡券回复!!! [\#124](https://github.com/overtrue/wechat/pull/124) ([parkshinhye](https://github.com/parkshinhye))
+- 调整退款类中一处异常抛出逻辑并修正单词拼写错误 [\#122](https://github.com/overtrue/wechat/pull/122) ([tianyong90](https://github.com/tianyong90))
+- 加入创建卡券货架接口 [\#121](https://github.com/overtrue/wechat/pull/121) ([tianyong90](https://github.com/tianyong90))
+- 增加退款类 [\#105](https://github.com/overtrue/wechat/pull/105) ([jaring](https://github.com/jaring))
+- 增加获取用户已领取卡券方法 [\#103](https://github.com/overtrue/wechat/pull/103) ([tenstone](https://github.com/tenstone))
+- Scrutinizer Auto-Fixes [\#100](https://github.com/overtrue/wechat/pull/100) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- 修正二维码类中生成卡券二维码方法 [\#99](https://github.com/overtrue/wechat/pull/99) ([tianyong90](https://github.com/tianyong90))
+- 卡券接口加入添加测试白名单方法 [\#98](https://github.com/overtrue/wechat/pull/98) ([tianyong90](https://github.com/tianyong90))
+- 依样画葫芦写了一个查询订单,更改了UnifiedOrder中Http初始化 [\#95](https://github.com/overtrue/wechat/pull/95) ([jaring](https://github.com/jaring))
+- accessToken根据appId变化 [\#92](https://github.com/overtrue/wechat/pull/92) ([keepeye](https://github.com/keepeye))
+- Fix payment sign bug. [\#82](https://github.com/overtrue/wechat/pull/82) ([0i](https://github.com/0i))
+- \[wiki\] wechat payment [\#81](https://github.com/overtrue/wechat/pull/81) ([0i](https://github.com/0i))
+
+## [2.1.0](https://github.com/overtrue/wechat/tree/2.1.0) (2015-08-18)
+[Full Changelog](https://github.com/overtrue/wechat/compare/2.0.35...2.1.0)
+
+**Merged pull requests:**
+
+- Wechat Payment [\#80](https://github.com/overtrue/wechat/pull/80) ([0i](https://github.com/0i))
+
+## [2.0.35](https://github.com/overtrue/wechat/tree/2.0.35) (2015-08-11)
+[Full Changelog](https://github.com/overtrue/wechat/compare/2.0.1...2.0.35)
+
+**Implemented enhancements:**
+
+- Overtrue\Wechat\Http识别JSON的问题 [\#47](https://github.com/overtrue/wechat/issues/47)
+
+**Fixed bugs:**
+
+- 模板消息简单格式无效 [\#34](https://github.com/overtrue/wechat/issues/34)
+
+**Closed issues:**
+
+- $data是数组,title输出不了内容 [\#73](https://github.com/overtrue/wechat/issues/73)
+- 回调是如何传递外部参数的? [\#72](https://github.com/overtrue/wechat/issues/72)
+- 【建议】可以添加微信js的功能吗? [\#71](https://github.com/overtrue/wechat/issues/71)
+- Message::make\('link'\) 无效 [\#70](https://github.com/overtrue/wechat/issues/70)
+- 监听消息 返回Bad Request [\#65](https://github.com/overtrue/wechat/issues/65)
+- 微信素材管理小改版,求跟上~ [\#64](https://github.com/overtrue/wechat/issues/64)
+- 在新浪SAE平台上的部署问题 [\#63](https://github.com/overtrue/wechat/issues/63)
+- $xmlInput = file\_get\_contents\('php://input'\);貌似在某些版本的PHP有问题还是怎的 [\#57](https://github.com/overtrue/wechat/issues/57)
+- 卡券的 attachExtension 方法  [\#56](https://github.com/overtrue/wechat/issues/56)
+- 网页授权$auth-\>authorize\(\) 后还需要保存access\_token吗? [\#53](https://github.com/overtrue/wechat/issues/53)
+- php 5.6版本下出现错误(5.6以下版本正常) [\#51](https://github.com/overtrue/wechat/issues/51)
+- 消息发送后服务器无法正确返回响应 [\#48](https://github.com/overtrue/wechat/issues/48)
+- token验证失败 [\#45](https://github.com/overtrue/wechat/issues/45)
+- 微信关注自动回复问题 [\#44](https://github.com/overtrue/wechat/issues/44)
+- js sdk config 建议增加 beta 字段 [\#35](https://github.com/overtrue/wechat/issues/35)
+- 关于Util\HTTP::encode\(\)中的urlencode\(\)/urldecode\(\)成组操作的疑问 [\#31](https://github.com/overtrue/wechat/issues/31)
+- Media::updateNews\(\) 方法与微信API不一致 [\#29](https://github.com/overtrue/wechat/issues/29)
+- 希望能有一个ThinkPHP的使用示例 [\#28](https://github.com/overtrue/wechat/issues/28)
+- 事件消息 [\#22](https://github.com/overtrue/wechat/issues/22)
+- 模板消息notice [\#21](https://github.com/overtrue/wechat/issues/21)
+- 关于获取(接收)用户发送消息 [\#19](https://github.com/overtrue/wechat/issues/19)
+- 微信公众号绑定的一点问题,请教。 [\#16](https://github.com/overtrue/wechat/issues/16)
+- 获取素材列表错误 [\#15](https://github.com/overtrue/wechat/issues/15)
+
+**Merged pull requests:**
+
+- Scrutinizer Auto-Fixes [\#69](https://github.com/overtrue/wechat/pull/69) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- Scrutinizer Auto-Fixes [\#68](https://github.com/overtrue/wechat/pull/68) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- Scrutinizer Auto-Fixes [\#67](https://github.com/overtrue/wechat/pull/67) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- Fixed StyleCI config [\#66](https://github.com/overtrue/wechat/pull/66) ([GrahamCampbell](https://github.com/GrahamCampbell))
+- 洁癖爆发了。。。 [\#62](https://github.com/overtrue/wechat/pull/62) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- fix: js getUrl use Url::current\(\) [\#61](https://github.com/overtrue/wechat/pull/61) ([wdjwxh](https://github.com/wdjwxh))
+- bug-fix: add x-forwarded-host for Url::current [\#60](https://github.com/overtrue/wechat/pull/60) ([wdjwxh](https://github.com/wdjwxh))
+- Fix request method for User::batchGet\(\), should be POST with JSON. [\#59](https://github.com/overtrue/wechat/pull/59) ([acgrid](https://github.com/acgrid))
+- optimize some code [\#58](https://github.com/overtrue/wechat/pull/58) ([tabalt](https://github.com/tabalt))
+- 增加使用media id发送图文消息的功能 [\#52](https://github.com/overtrue/wechat/pull/52) ([zengohm](https://github.com/zengohm))
+- fix Staff::delete, let it works [\#42](https://github.com/overtrue/wechat/pull/42) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- 支持自定义菜单类型:下发消息media\_id、跳转图文消息view\_limited [\#40](https://github.com/overtrue/wechat/pull/40) ([acgrid](https://github.com/acgrid))
+- docline comments & fix AccessToken parameter typos [\#39](https://github.com/overtrue/wechat/pull/39) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- Merge from master [\#38](https://github.com/overtrue/wechat/pull/38) ([overtrue](https://github.com/overtrue))
+- 客服接口Bugfix [\#37](https://github.com/overtrue/wechat/pull/37) ([overtrue](https://github.com/overtrue))
+- fix Staff and AccessToken typos [\#36](https://github.com/overtrue/wechat/pull/36) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- Update QRCode.php [\#33](https://github.com/overtrue/wechat/pull/33) ([refear99](https://github.com/refear99))
+- English Readme [\#32](https://github.com/overtrue/wechat/pull/32) ([hareluya](https://github.com/hareluya))
+- 更新图文消息方法Media::updateNews\(\)与微信API不一致 [\#30](https://github.com/overtrue/wechat/pull/30) ([acgrid](https://github.com/acgrid))
+- 代码之美在于不断修正 :\) [\#27](https://github.com/overtrue/wechat/pull/27) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- the json\_encode $depth parameter was added@5.5.0 [\#26](https://github.com/overtrue/wechat/pull/26) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- fix \#4 for PHP5.3 [\#25](https://github.com/overtrue/wechat/pull/25) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- fix \#4 for PHP5.3 [\#23](https://github.com/overtrue/wechat/pull/23) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- Update QRCode.php [\#17](https://github.com/overtrue/wechat/pull/17) ([gundanx10](https://github.com/gundanx10))
+
+## [2.0.1](https://github.com/overtrue/wechat/tree/2.0.1) (2015-05-08)
+[Full Changelog](https://github.com/overtrue/wechat/compare/2.0.0...2.0.1)
+
+**Closed issues:**
+
+- 2.0版本使用问题 [\#14](https://github.com/overtrue/wechat/issues/14)
+
+## [2.0.0](https://github.com/overtrue/wechat/tree/2.0.0) (2015-05-07)
+[Full Changelog](https://github.com/overtrue/wechat/compare/1.0.1...2.0.0)
+
+**Closed issues:**
+
+-  素材管理 -- 部分图片下载失败 [\#13](https://github.com/overtrue/wechat/issues/13)
+-  素材管理 -- 图片下载失败 [\#12](https://github.com/overtrue/wechat/issues/12)
+- 请问这样判断Mcrypt到底准不准? [\#11](https://github.com/overtrue/wechat/issues/11)
+- 好奇怪啊,开发者中心的服务器配置已经提交并验证成功了,可是message不起作用 [\#10](https://github.com/overtrue/wechat/issues/10)
+- 网页授权一刷新页面就出现40029 不合法的oauth\_code [\#8](https://github.com/overtrue/wechat/issues/8)
+- mcrypt\_module\_open error [\#7](https://github.com/overtrue/wechat/issues/7)
+- composer update 之后报错 [\#6](https://github.com/overtrue/wechat/issues/6)
+- 今天开始,授权时候一直报40029,invalid  code的错误 [\#5](https://github.com/overtrue/wechat/issues/5)
+- Using $this when not in object context [\#4](https://github.com/overtrue/wechat/issues/4)
+- 监听事件时不区分 $target(监听所有event和message) [\#3](https://github.com/overtrue/wechat/issues/3)
+- Does this support Oauth already? [\#1](https://github.com/overtrue/wechat/issues/1)
+
+**Merged pull requests:**
+
+- Fix wiki url error [\#9](https://github.com/overtrue/wechat/pull/9) ([sinoon](https://github.com/sinoon))
+- Update Bag.php [\#2](https://github.com/overtrue/wechat/pull/2) ([zerozh](https://github.com/zerozh))
+
+## [1.0.1](https://github.com/overtrue/wechat/tree/1.0.1) (2015-03-19)
+[Full Changelog](https://github.com/overtrue/wechat/compare/1.0...1.0.1)
+
+## [1.0](https://github.com/overtrue/wechat/tree/1.0) (2015-03-13)
+
+
+\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
\ No newline at end of file
diff --git a/vendor/overtrue/wechat/CONTRIBUTING.md b/vendor/overtrue/wechat/CONTRIBUTING.md
new file mode 100644
index 0000000..134204f
--- /dev/null
+++ b/vendor/overtrue/wechat/CONTRIBUTING.md
@@ -0,0 +1,67 @@
+# Contribute
+
+## Introduction
+
+First, thank you for considering contributing to wechat! It's people like you that make the open source community such a great community! 😊
+
+We welcome any type of contribution, not only code. You can help with 
+- **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)
+- **Marketing**: writing blog posts, howto's, printing stickers, ...
+- **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ...
+- **Code**: take a look at the [open issues](issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them.
+- **Money**: we welcome financial contributions in full transparency on our [open collective](https://opencollective.com/wechat).
+
+## Your First Contribution
+
+Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
+
+## Submitting code
+
+Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests.
+
+## Code review process
+
+The bigger the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge.
+It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you?
+
+## Financial contributions
+
+We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/wechat).
+Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
+
+## Questions
+
+If you have any questions, create an [issue](issue) (protip: do a quick search first to see if someone else didn't ask the same question before!).
+You can also reach us at hello@wechat.opencollective.com.
+
+## Credits
+
+### Contributors
+
+Thank you to all the people who have already contributed to wechat!
+<a href="graphs/contributors"><img src="https://opencollective.com/wechat/contributors.svg?width=890" /></a>
+
+
+### Backers
+
+Thank you to all our backers! [[Become a backer](https://opencollective.com/wechat#backer)]
+
+<a href="https://opencollective.com/wechat#backers" target="_blank"><img src="https://opencollective.com/wechat/backers.svg?width=890"></a>
+
+
+### Sponsors
+
+Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/wechat#sponsor))
+
+<a href="https://opencollective.com/wechat/sponsor/0/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/0/avatar.svg"></a>
+<a href="https://opencollective.com/wechat/sponsor/1/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/1/avatar.svg"></a>
+<a href="https://opencollective.com/wechat/sponsor/2/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/2/avatar.svg"></a>
+<a href="https://opencollective.com/wechat/sponsor/3/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/3/avatar.svg"></a>
+<a href="https://opencollective.com/wechat/sponsor/4/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/4/avatar.svg"></a>
+<a href="https://opencollective.com/wechat/sponsor/5/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/5/avatar.svg"></a>
+<a href="https://opencollective.com/wechat/sponsor/6/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/6/avatar.svg"></a>
+<a href="https://opencollective.com/wechat/sponsor/7/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/7/avatar.svg"></a>
+<a href="https://opencollective.com/wechat/sponsor/8/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/8/avatar.svg"></a>
+<a href="https://opencollective.com/wechat/sponsor/9/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/9/avatar.svg"></a>
+
+<!-- This `CONTRIBUTING.md` is based on @nayafia's template https://github.com/nayafia/contributing-template -->
\ No newline at end of file
diff --git a/vendor/overtrue/wechat/src/BasicService/Application.php b/vendor/overtrue/wechat/src/BasicService/Application.php
new file mode 100644
index 0000000..e4b1e8a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/Application.php
@@ -0,0 +1,39 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService;
+
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Class Application.
+ *
+ * @author overtrue <i@overtrue.me>
+ *
+ * @property \EasyWeChat\BasicService\Jssdk\Client           $jssdk
+ * @property \EasyWeChat\BasicService\Media\Client           $media
+ * @property \EasyWeChat\BasicService\QrCode\Client          $qrcode
+ * @property \EasyWeChat\BasicService\Url\Client             $url
+ * @property \EasyWeChat\BasicService\ContentSecurity\Client $content_security
+ */
+class Application extends ServiceContainer
+{
+    /**
+     * @var array
+     */
+    protected $providers = [
+        Jssdk\ServiceProvider::class,
+        QrCode\ServiceProvider::class,
+        Media\ServiceProvider::class,
+        Url\ServiceProvider::class,
+        ContentSecurity\ServiceProvider::class,
+    ];
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/ContentSecurity/Client.php b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/Client.php
new file mode 100644
index 0000000..7bc11bd
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/Client.php
@@ -0,0 +1,123 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\ContentSecurity;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class Client.
+ *
+ * @author tianyong90 <412039588@qq.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @var string
+     */
+    protected $baseUri = 'https://api.weixin.qq.com/wxa/';
+
+    /**
+     * Text content security check.
+     *
+     * @param string $text
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function checkText(string $text)
+    {
+        $params = [
+            'content' => $text,
+        ];
+
+        return $this->httpPostJson('msg_sec_check', $params);
+    }
+
+    /**
+     * Image security check.
+     *
+     * @param string $path
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function checkImage(string $path)
+    {
+        return $this->httpUpload('img_sec_check', ['media' => $path]);
+    }
+
+    /**
+     * Media security check.
+     *
+     * @param string $mediaUrl
+     * @param int    $mediaType
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function checkMediaAsync(string $mediaUrl, int $mediaType)
+    {
+        /*
+         * 1:音频;2:图片
+         */
+        $mediaTypes = [1, 2];
+
+        if (!in_array($mediaType, $mediaTypes, true)) {
+            throw new InvalidArgumentException('media type must be 1 or 2');
+        }
+
+        $params = [
+            'media_url' => $mediaUrl,
+            'media_type' => $mediaType,
+        ];
+
+        return $this->httpPostJson('media_check_async', $params);
+    }
+
+    /**
+     * Image security check async.
+     *
+     * @param string $mediaUrl
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function checkImageAsync(string $mediaUrl)
+    {
+        return $this->checkMediaAsync($mediaUrl, 2);
+    }
+
+    /**
+     * Audio security check async.
+     *
+     * @param string $mediaUrl
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function checkAudioAsync(string $mediaUrl)
+    {
+        return $this->checkMediaAsync($mediaUrl, 1);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/ContentSecurity/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/ServiceProvider.php
new file mode 100644
index 0000000..3645de9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/ServiceProvider.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\ContentSecurity;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['content_security'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/Jssdk/Client.php b/vendor/overtrue/wechat/src/BasicService/Jssdk/Client.php
new file mode 100644
index 0000000..33367fc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/Jssdk/Client.php
@@ -0,0 +1,207 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\Jssdk;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\Kernel\Traits\InteractsWithCache;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    use InteractsWithCache;
+
+    /**
+     * @var string
+     */
+    protected $ticketEndpoint = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket';
+
+    /**
+     * Current URI.
+     *
+     * @var string
+     */
+    protected $url;
+
+    /**
+     * Get config json for jsapi.
+     *
+     * @param array $jsApiList
+     * @param bool  $debug
+     * @param bool  $beta
+     * @param bool  $json
+     *
+     * @return array|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function buildConfig(array $jsApiList, bool $debug = false, bool $beta = false, bool $json = true)
+    {
+        $config = array_merge(compact('debug', 'beta', 'jsApiList'), $this->configSignature());
+
+        return $json ? json_encode($config) : $config;
+    }
+
+    /**
+     * Return jsapi config as a PHP array.
+     *
+     * @param array $apis
+     * @param bool  $debug
+     * @param bool  $beta
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function getConfigArray(array $apis, bool $debug = false, bool $beta = false)
+    {
+        return $this->buildConfig($apis, $debug, $beta, false);
+    }
+
+    /**
+     * Get js ticket.
+     *
+     * @param bool   $refresh
+     * @param string $type
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function getTicket(bool $refresh = false, string $type = 'jsapi'): array
+    {
+        $cacheKey = sprintf('easywechat.basic_service.jssdk.ticket.%s.%s', $type, $this->getAppId());
+
+        if (!$refresh && $this->getCache()->has($cacheKey)) {
+            return $this->getCache()->get($cacheKey);
+        }
+
+        /** @var array<string, mixed> $result */
+        $result = $this->castResponseToType(
+            $this->requestRaw($this->ticketEndpoint, 'GET', ['query' => ['type' => $type]]),
+            'array'
+        );
+
+        $this->getCache()->set($cacheKey, $result, $result['expires_in'] - 500);
+
+        if (!$this->getCache()->has($cacheKey)) {
+            throw new RuntimeException('Failed to cache jssdk ticket.');
+        }
+
+        return $result;
+    }
+
+    /**
+     * Build signature.
+     *
+     * @param string|null $url
+     * @param string|null $nonce
+     * @param int|null    $timestamp
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    protected function configSignature(string $url = null, string $nonce = null, $timestamp = null): array
+    {
+        $url = $url ?: $this->getUrl();
+        $nonce = $nonce ?: Support\Str::quickRandom(10);
+        $timestamp = $timestamp ?: time();
+
+        return [
+            'appId' => $this->getAppId(),
+            'nonceStr' => $nonce,
+            'timestamp' => $timestamp,
+            'url' => $url,
+            'signature' => $this->getTicketSignature($this->getTicket()['ticket'], $nonce, $timestamp, $url),
+        ];
+    }
+
+    /**
+     * Sign the params.
+     *
+     * @param string $ticket
+     * @param string $nonce
+     * @param int    $timestamp
+     * @param string $url
+     *
+     * @return string
+     */
+    public function getTicketSignature($ticket, $nonce, $timestamp, $url): string
+    {
+        return sha1(sprintf('jsapi_ticket=%s&noncestr=%s&timestamp=%s&url=%s', $ticket, $nonce, $timestamp, $url));
+    }
+
+    /**
+     * @return string
+     */
+    public function dictionaryOrderSignature()
+    {
+        $params = func_get_args();
+
+        sort($params, SORT_STRING);
+
+        return sha1(implode('', $params));
+    }
+
+    /**
+     * Set current url.
+     *
+     * @param string $url
+     *
+     * @return $this
+     */
+    public function setUrl(string $url)
+    {
+        $this->url = $url;
+
+        return $this;
+    }
+
+    /**
+     * Get current url.
+     *
+     * @return string
+     */
+    public function getUrl(): string
+    {
+        if ($this->url) {
+            return $this->url;
+        }
+
+        return Support\current_url();
+    }
+
+    /**
+     * @return string
+     */
+    protected function getAppId()
+    {
+        return $this->app['config']->get('app_id');
+    }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/Jssdk/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/Jssdk/ServiceProvider.php
new file mode 100644
index 0000000..5581a1e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/Jssdk/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\Jssdk;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['jssdk'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/Media/Client.php b/vendor/overtrue/wechat/src/BasicService/Media/Client.php
new file mode 100644
index 0000000..13f1afa
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/Media/Client.php
@@ -0,0 +1,212 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\Media;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Http\StreamResponse;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @var string
+     */
+    protected $baseUri = 'https://api.weixin.qq.com/cgi-bin/';
+
+    /**
+     * Allow media type.
+     *
+     * @var array
+     */
+    protected $allowTypes = ['image', 'voice', 'video', 'thumb'];
+
+    /**
+     * Upload image.
+     *
+     * @param string $path
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function uploadImage($path)
+    {
+        return $this->upload('image', $path);
+    }
+
+    /**
+     * Upload video.
+     *
+     * @param string $path
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function uploadVideo($path)
+    {
+        return $this->upload('video', $path);
+    }
+
+    /**
+     * @param string $path
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function uploadVoice($path)
+    {
+        return $this->upload('voice', $path);
+    }
+
+    /**
+     * @param string $path
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function uploadThumb($path)
+    {
+        return $this->upload('thumb', $path);
+    }
+
+    /**
+     * Upload temporary material.
+     *
+     * @param string $type
+     * @param string $path
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function upload(string $type, string $path)
+    {
+        if (!file_exists($path) || !is_readable($path)) {
+            throw new InvalidArgumentException(sprintf("File does not exist, or the file is unreadable: '%s'", $path));
+        }
+
+        if (!in_array($type, $this->allowTypes, true)) {
+            throw new InvalidArgumentException(sprintf("Unsupported media type: '%s'", $type));
+        }
+
+        return $this->httpUpload('media/upload', ['media' => $path], ['type' => $type]);
+    }
+
+    /**
+     * @param string $path
+     * @param string $title
+     * @param string $description
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function uploadVideoForBroadcasting(string $path, string $title, string $description)
+    {
+        $response = $this->uploadVideo($path);
+        /** @var array $arrayResponse */
+        $arrayResponse = $this->detectAndCastResponseToType($response, 'array');
+
+        if (!empty($arrayResponse['media_id'])) {
+            return $this->createVideoForBroadcasting($arrayResponse['media_id'], $title, $description);
+        }
+
+        return $response;
+    }
+
+    /**
+     * @param string $mediaId
+     * @param string $title
+     * @param string $description
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function createVideoForBroadcasting(string $mediaId, string $title, string $description)
+    {
+        return $this->httpPostJson('media/uploadvideo', [
+            'media_id' => $mediaId,
+            'title' => $title,
+            'description' => $description,
+        ]);
+    }
+
+    /**
+     * Fetch item from WeChat server.
+     *
+     * @param string $mediaId
+     *
+     * @return \EasyWeChat\Kernel\Http\StreamResponse|\Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(string $mediaId)
+    {
+        $response = $this->requestRaw('media/get', 'GET', [
+            'query' => [
+                'media_id' => $mediaId,
+            ],
+        ]);
+
+        if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) {
+            return StreamResponse::buildFromPsrResponse($response);
+        }
+
+        return $this->castResponseToType($response, $this->app['config']->get('response_type'));
+    }
+
+    /**
+     * @param string $mediaId
+     *
+     * @return array|\EasyWeChat\Kernel\Http\Response|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getJssdkMedia(string $mediaId)
+    {
+        $response = $this->requestRaw('media/get/jssdk', 'GET', [
+            'query' => [
+                'media_id' => $mediaId,
+            ],
+        ]);
+
+        if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) {
+            return StreamResponse::buildFromPsrResponse($response);
+        }
+
+        return $this->castResponseToType($response, $this->app['config']->get('response_type'));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/Media/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/Media/ServiceProvider.php
new file mode 100644
index 0000000..45de142
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/Media/ServiceProvider.php
@@ -0,0 +1,44 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * ServiceProvider.php.
+ *
+ * This file is part of the wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\Media;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['media'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/QrCode/Client.php b/vendor/overtrue/wechat/src/BasicService/QrCode/Client.php
new file mode 100644
index 0000000..ac606a3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/QrCode/Client.php
@@ -0,0 +1,120 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\QrCode;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @var string
+     */
+    protected $baseUri = 'https://api.weixin.qq.com/cgi-bin/';
+
+    const DAY = 86400;
+    const SCENE_MAX_VALUE = 100000;
+    const SCENE_QR_CARD = 'QR_CARD';
+    const SCENE_QR_TEMPORARY = 'QR_SCENE';
+    const SCENE_QR_TEMPORARY_STR = 'QR_STR_SCENE';
+    const SCENE_QR_FOREVER = 'QR_LIMIT_SCENE';
+    const SCENE_QR_FOREVER_STR = 'QR_LIMIT_STR_SCENE';
+
+    /**
+     * Create forever QR code.
+     *
+     * @param string|int $sceneValue
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function forever($sceneValue)
+    {
+        if (is_int($sceneValue) && $sceneValue > 0 && $sceneValue < self::SCENE_MAX_VALUE) {
+            $type = self::SCENE_QR_FOREVER;
+            $sceneKey = 'scene_id';
+        } else {
+            $type = self::SCENE_QR_FOREVER_STR;
+            $sceneKey = 'scene_str';
+        }
+        $scene = [$sceneKey => $sceneValue];
+
+        return $this->create($type, $scene, false);
+    }
+
+    /**
+     * Create temporary QR code.
+     *
+     * @param string|int $sceneValue
+     * @param int|null   $expireSeconds
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function temporary($sceneValue, $expireSeconds = null)
+    {
+        if (is_int($sceneValue) && $sceneValue > 0) {
+            $type = self::SCENE_QR_TEMPORARY;
+            $sceneKey = 'scene_id';
+        } else {
+            $type = self::SCENE_QR_TEMPORARY_STR;
+            $sceneKey = 'scene_str';
+        }
+        $scene = [$sceneKey => $sceneValue];
+
+        return $this->create($type, $scene, true, $expireSeconds);
+    }
+
+    /**
+     * Return url for ticket.
+     * Detail: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1443433542 .
+     *
+     * @param string $ticket
+     *
+     * @return string
+     */
+    public function url($ticket)
+    {
+        return sprintf('https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s', urlencode($ticket));
+    }
+
+    /**
+     * Create a QrCode.
+     *
+     * @param string $actionName
+     * @param array  $actionInfo
+     * @param bool   $temporary
+     * @param int    $expireSeconds
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function create($actionName, $actionInfo, $temporary = true, $expireSeconds = null)
+    {
+        null !== $expireSeconds || $expireSeconds = 7 * self::DAY;
+
+        $params = [
+            'action_name' => $actionName,
+            'action_info' => ['scene' => $actionInfo],
+        ];
+
+        if ($temporary) {
+            $params['expire_seconds'] = min($expireSeconds, 30 * self::DAY);
+        }
+
+        return $this->httpPostJson('qrcode/create', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/QrCode/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/QrCode/ServiceProvider.php
new file mode 100644
index 0000000..1638c66
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/QrCode/ServiceProvider.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\QrCode;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['qrcode'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/Url/Client.php b/vendor/overtrue/wechat/src/BasicService/Url/Client.php
new file mode 100644
index 0000000..19a5fb1
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/Url/Client.php
@@ -0,0 +1,47 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\Url;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @var string
+     */
+    protected $baseUri = 'https://api.weixin.qq.com/';
+
+    /**
+     * Shorten the url.
+     *
+     * @param string $url
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function shorten(string $url)
+    {
+        $params = [
+            'action' => 'long2short',
+            'long_url' => $url,
+        ];
+
+        return $this->httpPostJson('cgi-bin/shorturl', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/Url/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/Url/ServiceProvider.php
new file mode 100644
index 0000000..6740bb8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/Url/ServiceProvider.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\Url;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['url'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Factory.php b/vendor/overtrue/wechat/src/Factory.php
new file mode 100644
index 0000000..295b4ff
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Factory.php
@@ -0,0 +1,54 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat;
+
+/**
+ * Class Factory.
+ *
+ * @method static \EasyWeChat\Payment\Application            payment(array $config)
+ * @method static \EasyWeChat\MiniProgram\Application        miniProgram(array $config)
+ * @method static \EasyWeChat\OpenPlatform\Application       openPlatform(array $config)
+ * @method static \EasyWeChat\OfficialAccount\Application    officialAccount(array $config)
+ * @method static \EasyWeChat\BasicService\Application       basicService(array $config)
+ * @method static \EasyWeChat\Work\Application               work(array $config)
+ * @method static \EasyWeChat\OpenWork\Application           openWork(array $config)
+ * @method static \EasyWeChat\MicroMerchant\Application      microMerchant(array $config)
+ */
+class Factory
+{
+    /**
+     * @param string $name
+     * @param array  $config
+     *
+     * @return \EasyWeChat\Kernel\ServiceContainer
+     */
+    public static function make($name, array $config)
+    {
+        $namespace = Kernel\Support\Str::studly($name);
+        $application = "\\EasyWeChat\\{$namespace}\\Application";
+
+        return new $application($config);
+    }
+
+    /**
+     * Dynamically pass methods to the application.
+     *
+     * @param string $name
+     * @param array  $arguments
+     *
+     * @return mixed
+     */
+    public static function __callStatic($name, $arguments)
+    {
+        return self::make($name, ...$arguments);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/AccessToken.php b/vendor/overtrue/wechat/src/Kernel/AccessToken.php
new file mode 100644
index 0000000..77f23c0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/AccessToken.php
@@ -0,0 +1,282 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel;
+
+use EasyWeChat\Kernel\Contracts\AccessTokenInterface;
+use EasyWeChat\Kernel\Exceptions\HttpException;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Traits\HasHttpRequests;
+use EasyWeChat\Kernel\Traits\InteractsWithCache;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Class AccessToken.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+abstract class AccessToken implements AccessTokenInterface
+{
+    use HasHttpRequests;
+    use InteractsWithCache;
+
+    /**
+     * @var \EasyWeChat\Kernel\ServiceContainer
+     */
+    protected $app;
+
+    /**
+     * @var string
+     */
+    protected $requestMethod = 'GET';
+
+    /**
+     * @var string
+     */
+    protected $endpointToGetToken;
+
+    /**
+     * @var string
+     */
+    protected $queryName;
+
+    /**
+     * @var array
+     */
+    protected $token;
+
+    /**
+     * @var int
+     */
+    protected $safeSeconds = 500;
+
+    /**
+     * @var string
+     */
+    protected $tokenKey = 'access_token';
+
+    /**
+     * @var string
+     */
+    protected $cachePrefix = 'easywechat.kernel.access_token.';
+
+    /**
+     * AccessToken constructor.
+     *
+     * @param \EasyWeChat\Kernel\ServiceContainer $app
+     */
+    public function __construct(ServiceContainer $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\HttpException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function getRefreshedToken(): array
+    {
+        return $this->getToken(true);
+    }
+
+    /**
+     * @param bool $refresh
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\HttpException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function getToken(bool $refresh = false): array
+    {
+        $cacheKey = $this->getCacheKey();
+        $cache = $this->getCache();
+
+        if (!$refresh && $cache->has($cacheKey)) {
+            return $cache->get($cacheKey);
+        }
+
+        /** @var array $token */
+        $token = $this->requestToken($this->getCredentials(), true);
+
+        $this->setToken($token[$this->tokenKey], $token['expires_in'] ?? 7200);
+
+        $this->app->events->dispatch(new Events\AccessTokenRefreshed($this));
+
+        return $token;
+    }
+
+    /**
+     * @param string $token
+     * @param int    $lifetime
+     *
+     * @return \EasyWeChat\Kernel\Contracts\AccessTokenInterface
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function setToken(string $token, int $lifetime = 7200): AccessTokenInterface
+    {
+        $this->getCache()->set($this->getCacheKey(), [
+            $this->tokenKey => $token,
+            'expires_in' => $lifetime,
+        ], $lifetime - $this->safeSeconds);
+
+        if (!$this->getCache()->has($this->getCacheKey())) {
+            throw new RuntimeException('Failed to cache access token.');
+        }
+
+        return $this;
+    }
+
+    /**
+     * @return \EasyWeChat\Kernel\Contracts\AccessTokenInterface
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\HttpException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function refresh(): AccessTokenInterface
+    {
+        $this->getToken(true);
+
+        return $this;
+    }
+
+    /**
+     * @param array $credentials
+     * @param bool  $toArray
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\HttpException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function requestToken(array $credentials, $toArray = false)
+    {
+        $response = $this->sendRequest($credentials);
+        $result = json_decode($response->getBody()->getContents(), true);
+        $formatted = $this->castResponseToType($response, $this->app['config']->get('response_type'));
+
+        if (empty($result[$this->tokenKey])) {
+            throw new HttpException('Request access_token fail: '.json_encode($result, JSON_UNESCAPED_UNICODE), $response, $formatted);
+        }
+
+        return $toArray ? $result : $formatted;
+    }
+
+    /**
+     * @param \Psr\Http\Message\RequestInterface $request
+     * @param array                              $requestOptions
+     *
+     * @return \Psr\Http\Message\RequestInterface
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\HttpException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface
+    {
+        parse_str($request->getUri()->getQuery(), $query);
+
+        $query = http_build_query(array_merge($this->getQuery(), $query));
+
+        return $request->withUri($request->getUri()->withQuery($query));
+    }
+
+    /**
+     * Send http request.
+     *
+     * @param array $credentials
+     *
+     * @return ResponseInterface
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function sendRequest(array $credentials): ResponseInterface
+    {
+        $options = [
+            ('GET' === $this->requestMethod) ? 'query' : 'json' => $credentials,
+        ];
+
+        return $this->setHttpClient($this->app['http_client'])->request($this->getEndpoint(), $this->requestMethod, $options);
+    }
+
+    /**
+     * @return string
+     */
+    protected function getCacheKey()
+    {
+        return $this->cachePrefix.md5(json_encode($this->getCredentials()));
+    }
+
+    /**
+     * The request query will be used to add to the request.
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\HttpException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    protected function getQuery(): array
+    {
+        return [$this->queryName ?? $this->tokenKey => $this->getToken()[$this->tokenKey]];
+    }
+
+    /**
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function getEndpoint(): string
+    {
+        if (empty($this->endpointToGetToken)) {
+            throw new InvalidArgumentException('No endpoint for access token request.');
+        }
+
+        return $this->endpointToGetToken;
+    }
+
+    /**
+     * @return string
+     */
+    public function getTokenKey()
+    {
+        return $this->tokenKey;
+    }
+
+    /**
+     * Credential for get token.
+     *
+     * @return array
+     */
+    abstract protected function getCredentials(): array;
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/BaseClient.php b/vendor/overtrue/wechat/src/Kernel/BaseClient.php
new file mode 100644
index 0000000..75e79e8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/BaseClient.php
@@ -0,0 +1,271 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel;
+
+use EasyWeChat\Kernel\Contracts\AccessTokenInterface;
+use EasyWeChat\Kernel\Http\Response;
+use EasyWeChat\Kernel\Traits\HasHttpRequests;
+use GuzzleHttp\MessageFormatter;
+use GuzzleHttp\Middleware;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Log\LogLevel;
+
+/**
+ * Class BaseClient.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class BaseClient
+{
+    use HasHttpRequests { request as performRequest; }
+
+    /**
+     * @var \EasyWeChat\Kernel\ServiceContainer
+     */
+    protected $app;
+
+    /**
+     * @var \EasyWeChat\Kernel\Contracts\AccessTokenInterface
+     */
+    protected $accessToken;
+
+    /**
+     * @var string
+     */
+    protected $baseUri;
+
+    /**
+     * BaseClient constructor.
+     *
+     * @param \EasyWeChat\Kernel\ServiceContainer                    $app
+     * @param \EasyWeChat\Kernel\Contracts\AccessTokenInterface|null $accessToken
+     */
+    public function __construct(ServiceContainer $app, AccessTokenInterface $accessToken = null)
+    {
+        $this->app = $app;
+        $this->accessToken = $accessToken ?? $this->app['access_token'];
+    }
+
+    /**
+     * GET request.
+     *
+     * @param string $url
+     * @param array  $query
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function httpGet(string $url, array $query = [])
+    {
+        return $this->request($url, 'GET', ['query' => $query]);
+    }
+
+    /**
+     * POST request.
+     *
+     * @param string $url
+     * @param array  $data
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function httpPost(string $url, array $data = [])
+    {
+        return $this->request($url, 'POST', ['form_params' => $data]);
+    }
+
+    /**
+     * JSON request.
+     *
+     * @param string $url
+     * @param array  $data
+     * @param array  $query
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function httpPostJson(string $url, array $data = [], array $query = [])
+    {
+        return $this->request($url, 'POST', ['query' => $query, 'json' => $data]);
+    }
+
+    /**
+     * Upload file.
+     *
+     * @param string $url
+     * @param array  $files
+     * @param array  $form
+     * @param array  $query
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function httpUpload(string $url, array $files = [], array $form = [], array $query = [])
+    {
+        $multipart = [];
+
+        foreach ($files as $name => $path) {
+            $multipart[] = [
+                'name' => $name,
+                'contents' => fopen($path, 'r'),
+            ];
+        }
+
+        foreach ($form as $name => $contents) {
+            $multipart[] = compact('name', 'contents');
+        }
+
+        return $this->request($url, 'POST', ['query' => $query, 'multipart' => $multipart, 'connect_timeout' => 30, 'timeout' => 30, 'read_timeout' => 30]);
+    }
+
+    /**
+     * @return AccessTokenInterface
+     */
+    public function getAccessToken(): AccessTokenInterface
+    {
+        return $this->accessToken;
+    }
+
+    /**
+     * @param \EasyWeChat\Kernel\Contracts\AccessTokenInterface $accessToken
+     *
+     * @return $this
+     */
+    public function setAccessToken(AccessTokenInterface $accessToken)
+    {
+        $this->accessToken = $accessToken;
+
+        return $this;
+    }
+
+    /**
+     * @param string $url
+     * @param string $method
+     * @param array  $options
+     * @param bool   $returnRaw
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function request(string $url, string $method = 'GET', array $options = [], $returnRaw = false)
+    {
+        if (empty($this->middlewares)) {
+            $this->registerHttpMiddlewares();
+        }
+
+        $response = $this->performRequest($url, $method, $options);
+
+        $this->app->events->dispatch(new Events\HttpResponseCreated($response));
+
+        return $returnRaw ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
+    }
+
+    /**
+     * @param string $url
+     * @param string $method
+     * @param array  $options
+     *
+     * @return \EasyWeChat\Kernel\Http\Response
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function requestRaw(string $url, string $method = 'GET', array $options = [])
+    {
+        return Response::buildFromPsrResponse($this->request($url, $method, $options, true));
+    }
+
+    /**
+     * Register Guzzle middlewares.
+     */
+    protected function registerHttpMiddlewares()
+    {
+        // retry
+        $this->pushMiddleware($this->retryMiddleware(), 'retry');
+        // access token
+        $this->pushMiddleware($this->accessTokenMiddleware(), 'access_token');
+        // log
+        $this->pushMiddleware($this->logMiddleware(), 'log');
+    }
+
+    /**
+     * Attache access token to request query.
+     *
+     * @return \Closure
+     */
+    protected function accessTokenMiddleware()
+    {
+        return function (callable $handler) {
+            return function (RequestInterface $request, array $options) use ($handler) {
+                if ($this->accessToken) {
+                    $request = $this->accessToken->applyToRequest($request, $options);
+                }
+
+                return $handler($request, $options);
+            };
+        };
+    }
+
+    /**
+     * Log the request.
+     *
+     * @return \Closure
+     */
+    protected function logMiddleware()
+    {
+        $formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG);
+
+        return Middleware::log($this->app['logger'], $formatter, LogLevel::DEBUG);
+    }
+
+    /**
+     * Return retry middleware.
+     *
+     * @return \Closure
+     */
+    protected function retryMiddleware()
+    {
+        return Middleware::retry(function (
+            $retries,
+            RequestInterface $request,
+            ResponseInterface $response = null
+        ) {
+            // Limit the number of retries to 2
+            if ($retries < $this->app->config->get('http.max_retries', 1) && $response && $body = $response->getBody()) {
+                // Retry on server errors
+                $response = json_decode($body, true);
+
+                if (!empty($response['errcode']) && in_array(abs($response['errcode']), [40001, 40014, 42001], true)) {
+                    $this->accessToken->refresh();
+                    $this->app['logger']->debug('Retrying with refreshed access token.');
+
+                    return true;
+                }
+            }
+
+            return false;
+        }, function () {
+            return abs($this->app->config->get('http.retry_delay', 500));
+        });
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Clauses/Clause.php b/vendor/overtrue/wechat/src/Kernel/Clauses/Clause.php
new file mode 100644
index 0000000..49bbb22
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Clauses/Clause.php
@@ -0,0 +1,64 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Clauses;
+
+/**
+ * Class Clause.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Clause
+{
+    /**
+     * @var array
+     */
+    protected $clauses = [
+        'where' => [],
+    ];
+
+    /**
+     * @param mixed ...$args
+     *
+     * @return $this
+     */
+    public function where(...$args)
+    {
+        array_push($this->clauses['where'], $args);
+
+        return $this;
+    }
+
+    /**
+     * @param mixed $payload
+     *
+     * @return bool
+     */
+    public function intercepted($payload)
+    {
+        return (bool) $this->interceptWhereClause($payload);
+    }
+
+    /**
+     * @param mixed $payload
+     *
+     * @return bool
+     */
+    protected function interceptWhereClause($payload)
+    {
+        foreach ($this->clauses['where'] as $item) {
+            list($key, $value) = $item;
+            if (isset($payload[$key]) && $payload[$key] !== $value) {
+                return true;
+            }
+        }
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Config.php b/vendor/overtrue/wechat/src/Kernel/Config.php
new file mode 100644
index 0000000..081f6fd
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Config.php
@@ -0,0 +1,23 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel;
+
+use EasyWeChat\Kernel\Support\Collection;
+
+/**
+ * Class Config.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Config extends Collection
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/AccessTokenInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/AccessTokenInterface.php
new file mode 100644
index 0000000..56f4a4d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Contracts/AccessTokenInterface.php
@@ -0,0 +1,40 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Contracts;
+
+use Psr\Http\Message\RequestInterface;
+
+/**
+ * Interface AuthorizerAccessToken.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+interface AccessTokenInterface
+{
+    /**
+     * @return array
+     */
+    public function getToken(): array;
+
+    /**
+     * @return \EasyWeChat\Kernel\Contracts\AccessTokenInterface
+     */
+    public function refresh(): self;
+
+    /**
+     * @param \Psr\Http\Message\RequestInterface $request
+     * @param array                              $requestOptions
+     *
+     * @return \Psr\Http\Message\RequestInterface
+     */
+    public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface;
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/Arrayable.php b/vendor/overtrue/wechat/src/Kernel/Contracts/Arrayable.php
new file mode 100644
index 0000000..d947f8f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Contracts/Arrayable.php
@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Contracts;
+
+use ArrayAccess;
+
+/**
+ * Interface Arrayable.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+interface Arrayable extends ArrayAccess
+{
+    /**
+     * Get the instance as an array.
+     *
+     * @return array
+     */
+    public function toArray();
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/EventHandlerInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/EventHandlerInterface.php
new file mode 100644
index 0000000..c9d116c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Contracts/EventHandlerInterface.php
@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Contracts;
+
+/**
+ * Interface EventHandlerInterface.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+interface EventHandlerInterface
+{
+    /**
+     * @param mixed $payload
+     */
+    public function handle($payload = null);
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/MediaInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/MediaInterface.php
new file mode 100644
index 0000000..63768cf
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Contracts/MediaInterface.php
@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Contracts;
+
+/**
+ * Interface MediaInterface.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+interface MediaInterface extends MessageInterface
+{
+    /**
+     * @return string
+     */
+    public function getMediaId(): string;
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/MessageInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/MessageInterface.php
new file mode 100644
index 0000000..29ddb57
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Contracts/MessageInterface.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Contracts;
+
+/**
+ * Interface MessageInterface.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+interface MessageInterface
+{
+    /**
+     * @return string
+     */
+    public function getType(): string;
+
+    /**
+     * @return array
+     */
+    public function transformForJsonRequest(): array;
+
+    /**
+     * @return string
+     */
+    public function transformToXml(): string;
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Decorators/FinallyResult.php b/vendor/overtrue/wechat/src/Kernel/Decorators/FinallyResult.php
new file mode 100644
index 0000000..e698bbf
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Decorators/FinallyResult.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Decorators;
+
+/**
+ * Class FinallyResult.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class FinallyResult
+{
+    /**
+     * @var mixed
+     */
+    public $content;
+
+    /**
+     * FinallyResult constructor.
+     *
+     * @param mixed $content
+     */
+    public function __construct($content)
+    {
+        $this->content = $content;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Decorators/TerminateResult.php b/vendor/overtrue/wechat/src/Kernel/Decorators/TerminateResult.php
new file mode 100644
index 0000000..cf1042d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Decorators/TerminateResult.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Decorators;
+
+/**
+ * Class TerminateResult.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class TerminateResult
+{
+    /**
+     * @var mixed
+     */
+    public $content;
+
+    /**
+     * FinallyResult constructor.
+     *
+     * @param mixed $content
+     */
+    public function __construct($content)
+    {
+        $this->content = $content;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Encryptor.php b/vendor/overtrue/wechat/src/Kernel/Encryptor.php
new file mode 100644
index 0000000..150ad21
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Encryptor.php
@@ -0,0 +1,219 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel;
+
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Support\AES;
+use function EasyWeChat\Kernel\Support\str_random;
+use EasyWeChat\Kernel\Support\XML;
+use Throwable;
+
+/**
+ * Class Encryptor.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Encryptor
+{
+    const ERROR_INVALID_SIGNATURE = -40001; // Signature verification failed
+    const ERROR_PARSE_XML = -40002; // Parse XML failed
+    const ERROR_CALC_SIGNATURE = -40003; // Calculating the signature failed
+    const ERROR_INVALID_AES_KEY = -40004; // Invalid AESKey
+    const ERROR_INVALID_APP_ID = -40005; // Check AppID failed
+    const ERROR_ENCRYPT_AES = -40006; // AES EncryptionInterface failed
+    const ERROR_DECRYPT_AES = -40007; // AES decryption failed
+    const ERROR_INVALID_XML = -40008; // Invalid XML
+    const ERROR_BASE64_ENCODE = -40009; // Base64 encoding failed
+    const ERROR_BASE64_DECODE = -40010; // Base64 decoding failed
+    const ERROR_XML_BUILD = -40011; // XML build failed
+    const ILLEGAL_BUFFER = -41003; // Illegal buffer
+
+    /**
+     * App id.
+     *
+     * @var string
+     */
+    protected $appId;
+
+    /**
+     * App token.
+     *
+     * @var string
+     */
+    protected $token;
+
+    /**
+     * @var string
+     */
+    protected $aesKey;
+
+    /**
+     * Block size.
+     *
+     * @var int
+     */
+    protected $blockSize = 32;
+
+    /**
+     * Constructor.
+     *
+     * @param string      $appId
+     * @param string|null $token
+     * @param string|null $aesKey
+     */
+    public function __construct(string $appId, string $token = null, string $aesKey = null)
+    {
+        $this->appId = $appId;
+        $this->token = $token;
+        $this->aesKey = base64_decode($aesKey.'=', true);
+    }
+
+    /**
+     * Get the app token.
+     *
+     * @return string
+     */
+    public function getToken(): string
+    {
+        return $this->token;
+    }
+
+    /**
+     * Encrypt the message and return XML.
+     *
+     * @param string $xml
+     * @param string $nonce
+     * @param int    $timestamp
+     *
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function encrypt($xml, $nonce = null, $timestamp = null): string
+    {
+        try {
+            $xml = $this->pkcs7Pad(str_random(16).pack('N', strlen($xml)).$xml.$this->appId, $this->blockSize);
+
+            $encrypted = base64_encode(AES::encrypt(
+                $xml,
+                $this->aesKey,
+                substr($this->aesKey, 0, 16),
+                OPENSSL_NO_PADDING
+            ));
+            // @codeCoverageIgnoreStart
+        } catch (Throwable $e) {
+            throw new RuntimeException($e->getMessage(), self::ERROR_ENCRYPT_AES);
+        }
+        // @codeCoverageIgnoreEnd
+
+        !is_null($nonce) || $nonce = substr($this->appId, 0, 10);
+        !is_null($timestamp) || $timestamp = time();
+
+        $response = [
+            'Encrypt' => $encrypted,
+            'MsgSignature' => $this->signature($this->token, $timestamp, $nonce, $encrypted),
+            'TimeStamp' => $timestamp,
+            'Nonce' => $nonce,
+        ];
+
+        //生成响应xml
+        return XML::build($response);
+    }
+
+    /**
+     * Decrypt message.
+     *
+     * @param string $content
+     * @param string $msgSignature
+     * @param string $nonce
+     * @param string $timestamp
+     *
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function decrypt($content, $msgSignature, $nonce, $timestamp): string
+    {
+        $signature = $this->signature($this->token, $timestamp, $nonce, $content);
+
+        if ($signature !== $msgSignature) {
+            throw new RuntimeException('Invalid Signature.', self::ERROR_INVALID_SIGNATURE);
+        }
+
+        $decrypted = AES::decrypt(
+            base64_decode($content, true),
+            $this->aesKey,
+            substr($this->aesKey, 0, 16),
+            OPENSSL_NO_PADDING
+        );
+        $result = $this->pkcs7Unpad($decrypted);
+        $content = substr($result, 16, strlen($result));
+        $contentLen = unpack('N', substr($content, 0, 4))[1];
+
+        if (trim(substr($content, $contentLen + 4)) !== $this->appId) {
+            throw new RuntimeException('Invalid appId.', self::ERROR_INVALID_APP_ID);
+        }
+
+        return substr($content, 4, $contentLen);
+    }
+
+    /**
+     * Get SHA1.
+     *
+     * @return string
+     */
+    public function signature(): string
+    {
+        $array = func_get_args();
+        sort($array, SORT_STRING);
+
+        return sha1(implode($array));
+    }
+
+    /**
+     * PKCS#7 pad.
+     *
+     * @param string $text
+     * @param int    $blockSize
+     *
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function pkcs7Pad(string $text, int $blockSize): string
+    {
+        if ($blockSize > 256) {
+            throw new RuntimeException('$blockSize may not be more than 256');
+        }
+        $padding = $blockSize - (strlen($text) % $blockSize);
+        $pattern = chr($padding);
+
+        return $text.str_repeat($pattern, $padding);
+    }
+
+    /**
+     * PKCS#7 unpad.
+     *
+     * @param string $text
+     *
+     * @return string
+     */
+    public function pkcs7Unpad(string $text): string
+    {
+        $pad = ord(substr($text, -1));
+        if ($pad < 1 || $pad > $this->blockSize) {
+            $pad = 0;
+        }
+
+        return substr($text, 0, (strlen($text) - $pad));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Events/AccessTokenRefreshed.php b/vendor/overtrue/wechat/src/Kernel/Events/AccessTokenRefreshed.php
new file mode 100644
index 0000000..a829901
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Events/AccessTokenRefreshed.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Events;
+
+use EasyWeChat\Kernel\AccessToken;
+
+/**
+ * Class AccessTokenRefreshed.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class AccessTokenRefreshed
+{
+    /**
+     * @var \EasyWeChat\Kernel\AccessToken
+     */
+    public $accessToken;
+
+    /**
+     * @param \EasyWeChat\Kernel\AccessToken $accessToken
+     */
+    public function __construct(AccessToken $accessToken)
+    {
+        $this->accessToken = $accessToken;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Events/ApplicationInitialized.php b/vendor/overtrue/wechat/src/Kernel/Events/ApplicationInitialized.php
new file mode 100644
index 0000000..89d67fe
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Events/ApplicationInitialized.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Events;
+
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Class ApplicationInitialized.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ApplicationInitialized
+{
+    /**
+     * @var \EasyWeChat\Kernel\ServiceContainer
+     */
+    public $app;
+
+    /**
+     * @param \EasyWeChat\Kernel\ServiceContainer $app
+     */
+    public function __construct(ServiceContainer $app)
+    {
+        $this->app = $app;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Events/HttpResponseCreated.php b/vendor/overtrue/wechat/src/Kernel/Events/HttpResponseCreated.php
new file mode 100644
index 0000000..2257260
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Events/HttpResponseCreated.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Events;
+
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Class HttpResponseCreated.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class HttpResponseCreated
+{
+    /**
+     * @var \Psr\Http\Message\ResponseInterface
+     */
+    public $response;
+
+    /**
+     * @param \Psr\Http\Message\ResponseInterface $response
+     */
+    public function __construct(ResponseInterface $response)
+    {
+        $this->response = $response;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Events/ServerGuardResponseCreated.php b/vendor/overtrue/wechat/src/Kernel/Events/ServerGuardResponseCreated.php
new file mode 100644
index 0000000..3ab9925
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Events/ServerGuardResponseCreated.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Events;
+
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Class ServerGuardResponseCreated.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServerGuardResponseCreated
+{
+    /**
+     * @var \Symfony\Component\HttpFoundation\Response
+     */
+    public $response;
+
+    /**
+     * @param \Symfony\Component\HttpFoundation\Response $response
+     */
+    public function __construct(Response $response)
+    {
+        $this->response = $response;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/BadRequestException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/BadRequestException.php
new file mode 100644
index 0000000..a0b4f91
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/BadRequestException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+/**
+ * Class BadRequestException.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class BadRequestException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/DecryptException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/DecryptException.php
new file mode 100644
index 0000000..f29acca
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/DecryptException.php
@@ -0,0 +1,16 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+class DecryptException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/Exception.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/Exception.php
new file mode 100644
index 0000000..9eba298
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/Exception.php
@@ -0,0 +1,23 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+use Exception as BaseException;
+
+/**
+ * Class Exception.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Exception extends BaseException
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/HttpException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/HttpException.php
new file mode 100644
index 0000000..8ab130d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/HttpException.php
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Class HttpException.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class HttpException extends Exception
+{
+    /**
+     * @var \Psr\Http\Message\ResponseInterface|null
+     */
+    public $response;
+
+    /**
+     * @var \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string|null
+     */
+    public $formattedResponse;
+
+    /**
+     * HttpException constructor.
+     *
+     * @param string                                   $message
+     * @param \Psr\Http\Message\ResponseInterface|null $response
+     * @param null                                     $formattedResponse
+     * @param int|null                                 $code
+     */
+    public function __construct($message, ResponseInterface $response = null, $formattedResponse = null, $code = null)
+    {
+        parent::__construct($message, $code);
+
+        $this->response = $response;
+        $this->formattedResponse = $formattedResponse;
+
+        if ($response) {
+            $response->getBody()->rewind();
+        }
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidArgumentException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidArgumentException.php
new file mode 100644
index 0000000..386a144
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidArgumentException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+/**
+ * Class InvalidArgumentException.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class InvalidArgumentException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidConfigException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidConfigException.php
new file mode 100644
index 0000000..1e4ae2a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidConfigException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+/**
+ * Class InvalidConfigException.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class InvalidConfigException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/RuntimeException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/RuntimeException.php
new file mode 100644
index 0000000..25f2f5b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/RuntimeException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+/**
+ * Class RuntimeException.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class RuntimeException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/UnboundServiceException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/UnboundServiceException.php
new file mode 100644
index 0000000..78ea85c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/UnboundServiceException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+/**
+ * Class InvalidConfigException.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class UnboundServiceException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Helpers.php b/vendor/overtrue/wechat/src/Kernel/Helpers.php
new file mode 100644
index 0000000..ac9861c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Helpers.php
@@ -0,0 +1,55 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel;
+
+use EasyWeChat\Kernel\Contracts\Arrayable;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Support\Arr;
+use EasyWeChat\Kernel\Support\Collection;
+
+function data_get($data, $key, $default = null)
+{
+    switch (true) {
+        case is_array($data):
+            return Arr::get($data, $key, $default);
+        case $data instanceof Collection:
+            return $data->get($key, $default);
+        case $data instanceof Arrayable:
+            return Arr::get($data->toArray(), $key, $default);
+        case $data instanceof \ArrayIterator:
+            return $data->getArrayCopy()[$key] ?? $default;
+        case $data instanceof \ArrayAccess:
+            return $data[$key] ?? $default;
+        case $data instanceof \IteratorAggregate && $data->getIterator() instanceof \ArrayIterator:
+            return $data->getIterator()->getArrayCopy()[$key] ?? $default;
+        default:
+            throw new RuntimeException(sprintf('Can\'t access data with key "%s"', $key));
+    }
+}
+
+function data_to_array($data)
+{
+    switch (true) {
+        case is_array($data):
+            return $data;
+        case $data instanceof Collection:
+            return $data->all();
+        case $data instanceof Arrayable:
+            return $data->toArray();
+        case $data instanceof \IteratorAggregate && $data->getIterator() instanceof \ArrayIterator:
+            return $data->getIterator()->getArrayCopy();
+        case $data instanceof \ArrayIterator:
+            return $data->getArrayCopy();
+        default:
+            throw new RuntimeException(sprintf('Can\'t transform data to array'));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Http/Response.php b/vendor/overtrue/wechat/src/Kernel/Http/Response.php
new file mode 100644
index 0000000..adcd416
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Http/Response.php
@@ -0,0 +1,121 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Http;
+
+use EasyWeChat\Kernel\Support\Collection;
+use EasyWeChat\Kernel\Support\XML;
+use GuzzleHttp\Psr7\Response as GuzzleResponse;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Class Response.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Response extends GuzzleResponse
+{
+    /**
+     * @return string
+     */
+    public function getBodyContents()
+    {
+        $this->getBody()->rewind();
+        $contents = $this->getBody()->getContents();
+        $this->getBody()->rewind();
+
+        return $contents;
+    }
+
+    /**
+     * @param \Psr\Http\Message\ResponseInterface $response
+     *
+     * @return \EasyWeChat\Kernel\Http\Response
+     */
+    public static function buildFromPsrResponse(ResponseInterface $response)
+    {
+        return new static(
+            $response->getStatusCode(),
+            $response->getHeaders(),
+            $response->getBody(),
+            $response->getProtocolVersion(),
+            $response->getReasonPhrase()
+        );
+    }
+
+    /**
+     * Build to json.
+     *
+     * @return string
+     */
+    public function toJson()
+    {
+        return json_encode($this->toArray());
+    }
+
+    /**
+     * Build to array.
+     *
+     * @return array
+     */
+    public function toArray()
+    {
+        $content = $this->removeControlCharacters($this->getBodyContents());
+
+        if (false !== stripos($this->getHeaderLine('Content-Type'), 'xml') || 0 === stripos($content, '<xml')) {
+            return XML::parse($content);
+        }
+
+        $array = json_decode($content, true, 512, JSON_BIGINT_AS_STRING);
+
+        if (JSON_ERROR_NONE === json_last_error()) {
+            return (array) $array;
+        }
+
+        return [];
+    }
+
+    /**
+     * Get collection data.
+     *
+     * @return \EasyWeChat\Kernel\Support\Collection
+     */
+    public function toCollection()
+    {
+        return new Collection($this->toArray());
+    }
+
+    /**
+     * @return object
+     */
+    public function toObject()
+    {
+        return json_decode($this->toJson());
+    }
+
+    /**
+     * @return bool|string
+     */
+    public function __toString()
+    {
+        return $this->getBodyContents();
+    }
+
+    /**
+     * @param string $content
+     *
+     * @return string
+     */
+    protected function removeControlCharacters(string $content)
+    {
+        return \preg_replace('/[\x00-\x1F\x80-\x9F]/u', '', $content);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Http/StreamResponse.php b/vendor/overtrue/wechat/src/Kernel/Http/StreamResponse.php
new file mode 100644
index 0000000..104e8c3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Http/StreamResponse.php
@@ -0,0 +1,86 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Http;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Support\File;
+
+/**
+ * Class StreamResponse.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class StreamResponse extends Response
+{
+    /**
+     * @param string $directory
+     * @param string $filename
+     * @param bool   $appendSuffix
+     *
+     * @return bool|int
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function save(string $directory, string $filename = '', bool $appendSuffix = true)
+    {
+        $this->getBody()->rewind();
+
+        $directory = rtrim($directory, '/');
+
+        if (!is_dir($directory)) {
+            mkdir($directory, 0755, true); // @codeCoverageIgnore
+        }
+
+        if (!is_writable($directory)) {
+            throw new InvalidArgumentException(sprintf("'%s' is not writable.", $directory));
+        }
+
+        $contents = $this->getBody()->getContents();
+
+        if (empty($contents) || '{' === $contents[0]) {
+            throw new RuntimeException('Invalid media response content.');
+        }
+
+        if (empty($filename)) {
+            if (preg_match('/filename="(?<filename>.*?)"/', $this->getHeaderLine('Content-Disposition'), $match)) {
+                $filename = $match['filename'];
+            } else {
+                $filename = md5($contents);
+            }
+        }
+
+        if ($appendSuffix && empty(pathinfo($filename, PATHINFO_EXTENSION))) {
+            $filename .= File::getStreamExt($contents);
+        }
+
+        file_put_contents($directory.'/'.$filename, $contents);
+
+        return $filename;
+    }
+
+    /**
+     * @param string $directory
+     * @param string $filename
+     * @param bool   $appendSuffix
+     *
+     * @return bool|int
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function saveAs(string $directory, string $filename, bool $appendSuffix = true)
+    {
+        return $this->save($directory, $filename, $appendSuffix);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Log/LogManager.php b/vendor/overtrue/wechat/src/Kernel/Log/LogManager.php
new file mode 100644
index 0000000..3d356ef
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Log/LogManager.php
@@ -0,0 +1,583 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Log;
+
+use EasyWeChat\Kernel\ServiceContainer;
+use Monolog\Formatter\LineFormatter;
+use Monolog\Handler\ErrorLogHandler;
+use Monolog\Handler\FormattableHandlerInterface;
+use Monolog\Handler\HandlerInterface;
+use Monolog\Handler\RotatingFileHandler;
+use Monolog\Handler\SlackWebhookHandler;
+use Monolog\Handler\StreamHandler;
+use Monolog\Handler\SyslogHandler;
+use Monolog\Logger as Monolog;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Class LogManager.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class LogManager implements LoggerInterface
+{
+    /**
+     * @var \EasyWeChat\Kernel\ServiceContainer
+     */
+    protected $app;
+
+    /**
+     * The array of resolved channels.
+     *
+     * @var array
+     */
+    protected $channels = [];
+
+    /**
+     * The registered custom driver creators.
+     *
+     * @var array
+     */
+    protected $customCreators = [];
+
+    /**
+     * The Log levels.
+     *
+     * @var array
+     */
+    protected $levels = [
+        'debug' => Monolog::DEBUG,
+        'info' => Monolog::INFO,
+        'notice' => Monolog::NOTICE,
+        'warning' => Monolog::WARNING,
+        'error' => Monolog::ERROR,
+        'critical' => Monolog::CRITICAL,
+        'alert' => Monolog::ALERT,
+        'emergency' => Monolog::EMERGENCY,
+    ];
+
+    /**
+     * LogManager constructor.
+     *
+     * @param \EasyWeChat\Kernel\ServiceContainer $app
+     */
+    public function __construct(ServiceContainer $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * Create a new, on-demand aggregate logger instance.
+     *
+     * @param array       $channels
+     * @param string|null $channel
+     *
+     * @return \Psr\Log\LoggerInterface
+     *
+     * @throws \Exception
+     */
+    public function stack(array $channels, $channel = null)
+    {
+        return $this->createStackDriver(compact('channels', 'channel'));
+    }
+
+    /**
+     * Get a log channel instance.
+     *
+     * @param string|null $channel
+     *
+     * @return mixed
+     *
+     * @throws \Exception
+     */
+    public function channel($channel = null)
+    {
+        return $this->get($channel);
+    }
+
+    /**
+     * Get a log driver instance.
+     *
+     * @param string|null $driver
+     *
+     * @return mixed
+     *
+     * @throws \Exception
+     */
+    public function driver($driver = null)
+    {
+        return $this->get($driver ?? $this->getDefaultDriver());
+    }
+
+    /**
+     * Attempt to get the log from the local cache.
+     *
+     * @param string $name
+     *
+     * @return \Psr\Log\LoggerInterface
+     *
+     * @throws \Exception
+     */
+    protected function get($name)
+    {
+        try {
+            return $this->channels[$name] ?? ($this->channels[$name] = $this->resolve($name));
+        } catch (\Throwable $e) {
+            $logger = $this->createEmergencyLogger();
+
+            $logger->emergency('Unable to create configured logger. Using emergency logger.', [
+                    'exception' => $e,
+                ]);
+
+            return $logger;
+        }
+    }
+
+    /**
+     * Resolve the given log instance by name.
+     *
+     * @param string $name
+     *
+     * @return \Psr\Log\LoggerInterface
+     *
+     * @throws \InvalidArgumentException
+     */
+    protected function resolve($name)
+    {
+        $config = $this->app['config']->get(\sprintf('log.channels.%s', $name));
+
+        if (is_null($config)) {
+            throw new \InvalidArgumentException(\sprintf('Log [%s] is not defined.', $name));
+        }
+
+        if (isset($this->customCreators[$config['driver']])) {
+            return $this->callCustomCreator($config);
+        }
+
+        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';
+
+        if (method_exists($this, $driverMethod)) {
+            return $this->{$driverMethod}($config);
+        }
+
+        throw new \InvalidArgumentException(\sprintf('Driver [%s] is not supported.', $config['driver']));
+    }
+
+    /**
+     * Create an emergency log handler to avoid white screens of death.
+     *
+     * @return \Monolog\Logger
+     *
+     * @throws \Exception
+     */
+    protected function createEmergencyLogger()
+    {
+        return new Monolog('EasyWeChat', $this->prepareHandlers([new StreamHandler(
+            \sys_get_temp_dir().'/easywechat/easywechat.log', $this->level(['level' => 'debug'])
+        )]));
+    }
+
+    /**
+     * Call a custom driver creator.
+     *
+     * @param array $config
+     *
+     * @return mixed
+     */
+    protected function callCustomCreator(array $config)
+    {
+        return $this->customCreators[$config['driver']]($this->app, $config);
+    }
+
+    /**
+     * Create an aggregate log driver instance.
+     *
+     * @param array $config
+     *
+     * @return \Monolog\Logger
+     *
+     * @throws \Exception
+     */
+    protected function createStackDriver(array $config)
+    {
+        $handlers = [];
+
+        foreach ($config['channels'] ?? [] as $channel) {
+            $handlers = \array_merge($handlers, $this->channel($channel)->getHandlers());
+        }
+
+        return new Monolog($this->parseChannel($config), $handlers);
+    }
+
+    /**
+     * Create an instance of the single file log driver.
+     *
+     * @param array $config
+     *
+     * @return \Psr\Log\LoggerInterface
+     *
+     * @throws \Exception
+     */
+    protected function createSingleDriver(array $config)
+    {
+        return new Monolog($this->parseChannel($config), [
+            $this->prepareHandler(
+                new StreamHandler($config['path'], $this->level($config))
+            ),
+        ]);
+    }
+
+    /**
+     * Create an instance of the daily file log driver.
+     *
+     * @param array $config
+     *
+     * @return \Psr\Log\LoggerInterface
+     */
+    protected function createDailyDriver(array $config)
+    {
+        return new Monolog($this->parseChannel($config), [
+            $this->prepareHandler(new RotatingFileHandler(
+                $config['path'], $config['days'] ?? 7, $this->level($config)
+            )),
+        ]);
+    }
+
+    /**
+     * Create an instance of the Slack log driver.
+     *
+     * @param array $config
+     *
+     * @return \Psr\Log\LoggerInterface
+     */
+    protected function createSlackDriver(array $config)
+    {
+        return new Monolog($this->parseChannel($config), [
+            $this->prepareHandler(new SlackWebhookHandler(
+                $config['url'],
+                $config['channel'] ?? null,
+                $config['username'] ?? 'EasyWeChat',
+                $config['attachment'] ?? true,
+                $config['emoji'] ?? ':boom:',
+                $config['short'] ?? false,
+                $config['context'] ?? true,
+                $this->level($config)
+            )),
+        ]);
+    }
+
+    /**
+     * Create an instance of the syslog log driver.
+     *
+     * @param array $config
+     *
+     * @return \Psr\Log\LoggerInterface
+     */
+    protected function createSyslogDriver(array $config)
+    {
+        return new Monolog($this->parseChannel($config), [
+            $this->prepareHandler(new SyslogHandler(
+                    'EasyWeChat', $config['facility'] ?? LOG_USER, $this->level($config))
+            ),
+        ]);
+    }
+
+    /**
+     * Create an instance of the "error log" log driver.
+     *
+     * @param array $config
+     *
+     * @return \Psr\Log\LoggerInterface
+     */
+    protected function createErrorlogDriver(array $config)
+    {
+        return new Monolog($this->parseChannel($config), [
+            $this->prepareHandler(new ErrorLogHandler(
+                    $config['type'] ?? ErrorLogHandler::OPERATING_SYSTEM, $this->level($config))
+            ),
+        ]);
+    }
+
+    /**
+     * Prepare the handlers for usage by Monolog.
+     *
+     * @param array $handlers
+     *
+     * @return array
+     */
+    protected function prepareHandlers(array $handlers)
+    {
+        foreach ($handlers as $key => $handler) {
+            $handlers[$key] = $this->prepareHandler($handler);
+        }
+
+        return $handlers;
+    }
+
+    /**
+     * Prepare the handler for usage by Monolog.
+     *
+     * @param \Monolog\Handler\HandlerInterface $handler
+     *
+     * @return \Monolog\Handler\HandlerInterface
+     */
+    protected function prepareHandler(HandlerInterface $handler)
+    {
+        if ($handler instanceof FormattableHandlerInterface) {
+            $handler->setFormatter($this->formatter());
+        }
+
+        return $handler;
+    }
+
+    /**
+     * Get a Monolog formatter instance.
+     *
+     * @return \Monolog\Formatter\FormatterInterface
+     */
+    protected function formatter()
+    {
+        $formatter = new LineFormatter(null, null, true, true);
+        $formatter->includeStacktraces();
+
+        return $formatter;
+    }
+
+    /**
+     * Extract the log channel from the given configuration.
+     *
+     * @param array $config
+     *
+     * @return string
+     */
+    protected function parseChannel(array $config)
+    {
+        return $config['name'] ?? 'EasyWeChat';
+    }
+
+    /**
+     * Parse the string level into a Monolog constant.
+     *
+     * @param array $config
+     *
+     * @return int
+     *
+     * @throws \InvalidArgumentException
+     */
+    protected function level(array $config)
+    {
+        $level = $config['level'] ?? 'debug';
+
+        if (isset($this->levels[$level])) {
+            return $this->levels[$level];
+        }
+
+        throw new \InvalidArgumentException('Invalid log level.');
+    }
+
+    /**
+     * Get the default log driver name.
+     *
+     * @return string
+     */
+    public function getDefaultDriver()
+    {
+        return $this->app['config']['log.default'];
+    }
+
+    /**
+     * Set the default log driver name.
+     *
+     * @param string $name
+     */
+    public function setDefaultDriver($name)
+    {
+        $this->app['config']['log.default'] = $name;
+    }
+
+    /**
+     * Register a custom driver creator Closure.
+     *
+     * @param string   $driver
+     * @param \Closure $callback
+     *
+     * @return $this
+     */
+    public function extend($driver, \Closure $callback)
+    {
+        $this->customCreators[$driver] = $callback->bindTo($this, $this);
+
+        return $this;
+    }
+
+    /**
+     * System is unusable.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return mixed
+     *
+     * @throws \Exception
+     */
+    public function emergency($message, array $context = [])
+    {
+        return $this->driver()->emergency($message, $context);
+    }
+
+    /**
+     * Action must be taken immediately.
+     *
+     * Example: Entire website down, database unavailable, etc. This should
+     * trigger the SMS alerts and wake you up.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return mixed
+     *
+     * @throws \Exception
+     */
+    public function alert($message, array $context = [])
+    {
+        return $this->driver()->alert($message, $context);
+    }
+
+    /**
+     * Critical conditions.
+     *
+     * Example: Application component unavailable, unexpected exception.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return mixed
+     *
+     * @throws \Exception
+     */
+    public function critical($message, array $context = [])
+    {
+        return $this->driver()->critical($message, $context);
+    }
+
+    /**
+     * Runtime errors that do not require immediate action but should typically
+     * be logged and monitored.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return mixed
+     *
+     * @throws \Exception
+     */
+    public function error($message, array $context = [])
+    {
+        return $this->driver()->error($message, $context);
+    }
+
+    /**
+     * Exceptional occurrences that are not errors.
+     *
+     * Example: Use of deprecated APIs, poor use of an API, undesirable things
+     * that are not necessarily wrong.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return mixed
+     *
+     * @throws \Exception
+     */
+    public function warning($message, array $context = [])
+    {
+        return $this->driver()->warning($message, $context);
+    }
+
+    /**
+     * Normal but significant events.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return mixed
+     *
+     * @throws \Exception
+     */
+    public function notice($message, array $context = [])
+    {
+        return $this->driver()->notice($message, $context);
+    }
+
+    /**
+     * Interesting events.
+     *
+     * Example: User logs in, SQL logs.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return mixed
+     *
+     * @throws \Exception
+     */
+    public function info($message, array $context = [])
+    {
+        return $this->driver()->info($message, $context);
+    }
+
+    /**
+     * Detailed debug information.
+     *
+     * @param string $message
+     * @param array  $context
+     *
+     * @return mixed
+     *
+     * @throws \Exception
+     */
+    public function debug($message, array $context = [])
+    {
+        return $this->driver()->debug($message, $context);
+    }
+
+    /**
+     * Logs with an arbitrary level.
+     *
+     * @param mixed  $level
+     * @param string $message
+     * @param array  $context
+     *
+     * @return mixed
+     *
+     * @throws \Exception
+     */
+    public function log($level, $message, array $context = [])
+    {
+        return $this->driver()->log($level, $message, $context);
+    }
+
+    /**
+     * Dynamically call the default driver instance.
+     *
+     * @param string $method
+     * @param array  $parameters
+     *
+     * @return mixed
+     *
+     * @throws \Exception
+     */
+    public function __call($method, $parameters)
+    {
+        return $this->driver()->$method(...$parameters);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Article.php b/vendor/overtrue/wechat/src/Kernel/Messages/Article.php
new file mode 100644
index 0000000..4792a1f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Article.php
@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Article.
+ */
+class Article extends Message
+{
+    /**
+     * @var string
+     */
+    protected $type = 'mpnews';
+
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = [
+        'thumb_media_id',
+        'author',
+        'title',
+        'content',
+        'digest',
+        'source_url',
+        'show_cover',
+    ];
+
+    /**
+     * Aliases of attribute.
+     *
+     * @var array
+     */
+    protected $jsonAliases = [
+        'content_source_url' => 'source_url',
+        'show_cover_pic' => 'show_cover',
+    ];
+
+    /**
+     * @var array
+     */
+    protected $required = [
+        'thumb_media_id',
+        'title',
+        'content',
+        'show_cover',
+    ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Card.php b/vendor/overtrue/wechat/src/Kernel/Messages/Card.php
new file mode 100644
index 0000000..0e21231
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Card.php
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * Card.php.
+ *
+ * @author    overtrue <i@overtrue.me>
+ * @copyright 2015 overtrue <i@overtrue.me>
+ *
+ * @see      https://github.com/overtrue
+ * @see      http://overtrue.me
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Card.
+ */
+class Card extends Message
+{
+    /**
+     * Message type.
+     *
+     * @var string
+     */
+    protected $type = 'wxcard';
+
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = ['card_id'];
+
+    /**
+     * Media constructor.
+     *
+     * @param string $cardId
+     */
+    public function __construct(string $cardId)
+    {
+        parent::__construct(['card_id' => $cardId]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/DeviceEvent.php b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceEvent.php
new file mode 100644
index 0000000..ae57910
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceEvent.php
@@ -0,0 +1,40 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class DeviceEvent.
+ *
+ * @property string $media_id
+ */
+class DeviceEvent extends Message
+{
+    /**
+     * Messages type.
+     *
+     * @var string
+     */
+    protected $type = 'device_event';
+
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = [
+        'device_type',
+        'device_id',
+        'content',
+        'session_id',
+        'open_id',
+    ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/DeviceText.php b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceText.php
new file mode 100644
index 0000000..87e627a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceText.php
@@ -0,0 +1,50 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class DeviceText.
+ *
+ * @property string $content
+ */
+class DeviceText extends Message
+{
+    /**
+     * Messages type.
+     *
+     * @var string
+     */
+    protected $type = 'device_text';
+
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = [
+        'device_type',
+        'device_id',
+        'content',
+        'session_id',
+        'open_id',
+    ];
+
+    public function toXmlArray()
+    {
+        return [
+            'DeviceType' => $this->get('device_type'),
+            'DeviceID' => $this->get('device_id'),
+            'SessionID' => $this->get('session_id'),
+            'Content' => base64_encode($this->get('content')),
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/File.php b/vendor/overtrue/wechat/src/Kernel/Messages/File.php
new file mode 100644
index 0000000..a14c42f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/File.php
@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Image.
+ *
+ * @property string $media_id
+ */
+class File extends Media
+{
+    /**
+     * @var string
+     */
+    protected $type = 'file';
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Image.php b/vendor/overtrue/wechat/src/Kernel/Messages/Image.php
new file mode 100644
index 0000000..982033b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Image.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Image.
+ *
+ * @property string $media_id
+ */
+class Image extends Media
+{
+    /**
+     * Messages type.
+     *
+     * @var string
+     */
+    protected $type = 'image';
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Link.php b/vendor/overtrue/wechat/src/Kernel/Messages/Link.php
new file mode 100644
index 0000000..9c126b5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Link.php
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Link.
+ */
+class Link extends Message
+{
+    /**
+     * Messages type.
+     *
+     * @var string
+     */
+    protected $type = 'link';
+
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = [
+        'title',
+        'description',
+        'url',
+    ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Location.php b/vendor/overtrue/wechat/src/Kernel/Messages/Location.php
new file mode 100644
index 0000000..f10351b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Location.php
@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Location.
+ */
+class Location extends Message
+{
+    /**
+     * Messages type.
+     *
+     * @var string
+     */
+    protected $type = 'location';
+
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = [
+        'latitude',
+        'longitude',
+        'scale',
+        'label',
+        'precision',
+    ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Media.php b/vendor/overtrue/wechat/src/Kernel/Messages/Media.php
new file mode 100644
index 0000000..d8706fe
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Media.php
@@ -0,0 +1,70 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+use EasyWeChat\Kernel\Contracts\MediaInterface;
+use EasyWeChat\Kernel\Support\Str;
+
+/**
+ * Class Media.
+ */
+class Media extends Message implements MediaInterface
+{
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = ['media_id'];
+
+    /**
+     * @var array
+     */
+    protected $required = [
+        'media_id',
+    ];
+
+    /**
+     * MaterialClient constructor.
+     *
+     * @param string $mediaId
+     * @param string $type
+     * @param array  $attributes
+     */
+    public function __construct(string $mediaId, $type = null, array $attributes = [])
+    {
+        parent::__construct(array_merge(['media_id' => $mediaId], $attributes));
+
+        !empty($type) && $this->setType($type);
+    }
+
+    /**
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function getMediaId(): string
+    {
+        $this->checkRequiredAttributes();
+
+        return $this->get('media_id');
+    }
+
+    public function toXmlArray()
+    {
+        return [
+            Str::studly($this->getType()) => [
+                'MediaId' => $this->get('media_id'),
+            ],
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Message.php b/vendor/overtrue/wechat/src/Kernel/Messages/Message.php
new file mode 100644
index 0000000..0c22aac
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Message.php
@@ -0,0 +1,208 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+use EasyWeChat\Kernel\Contracts\MessageInterface;
+use EasyWeChat\Kernel\Support\XML;
+use EasyWeChat\Kernel\Traits\HasAttributes;
+use Mockery\Exception\BadMethodCallException;
+
+/**
+ * Class Messages.
+ */
+abstract class Message implements MessageInterface
+{
+    use HasAttributes;
+
+    const TEXT = 2;
+    const IMAGE = 4;
+    const VOICE = 8;
+    const VIDEO = 16;
+    const SHORT_VIDEO = 32;
+    const LOCATION = 64;
+    const LINK = 128;
+    const DEVICE_EVENT = 256;
+    const DEVICE_TEXT = 512;
+    const FILE = 1024;
+    const TEXT_CARD = 2048;
+    const TRANSFER = 4096;
+    const EVENT = 1048576;
+    const MINIPROGRAM_PAGE = 2097152;
+    const ALL = self::TEXT | self::IMAGE | self::VOICE | self::VIDEO | self::SHORT_VIDEO | self::LOCATION | self::LINK
+                | self::DEVICE_EVENT | self::DEVICE_TEXT | self::FILE | self::TEXT_CARD | self::TRANSFER | self::EVENT | self::MINIPROGRAM_PAGE;
+
+    /**
+     * @var string
+     */
+    protected $type;
+
+    /**
+     * @var int
+     */
+    protected $id;
+
+    /**
+     * @var string
+     */
+    protected $to;
+
+    /**
+     * @var string
+     */
+    protected $from;
+
+    /**
+     * @var array
+     */
+    protected $properties = [];
+
+    /**
+     * @var array
+     */
+    protected $jsonAliases = [];
+
+    /**
+     * Message constructor.
+     *
+     * @param array $attributes
+     */
+    public function __construct(array $attributes = [])
+    {
+        $this->setAttributes($attributes);
+    }
+
+    /**
+     * Return type name message.
+     *
+     * @return string
+     */
+    public function getType(): string
+    {
+        return $this->type;
+    }
+
+    /**
+     * @param string $type
+     */
+    public function setType(string $type)
+    {
+        $this->type = $type;
+    }
+
+    /**
+     * Magic getter.
+     *
+     * @param string $property
+     *
+     * @return mixed
+     */
+    public function __get($property)
+    {
+        if (property_exists($this, $property)) {
+            return $this->$property;
+        }
+
+        return $this->getAttribute($property);
+    }
+
+    /**
+     * Magic setter.
+     *
+     * @param string $property
+     * @param mixed  $value
+     *
+     * @return Message
+     */
+    public function __set($property, $value)
+    {
+        if (property_exists($this, $property)) {
+            $this->$property = $value;
+        } else {
+            $this->setAttribute($property, $value);
+        }
+
+        return $this;
+    }
+
+    /**
+     * @param array $appends
+     *
+     * @return array
+     */
+    public function transformForJsonRequestWithoutType(array $appends = [])
+    {
+        return $this->transformForJsonRequest($appends, false);
+    }
+
+    /**
+     * @param array $appends
+     * @param bool  $withType
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function transformForJsonRequest(array $appends = [], $withType = true): array
+    {
+        if (!$withType) {
+            return $this->propertiesToArray([], $this->jsonAliases);
+        }
+        $messageType = $this->getType();
+        $data = array_merge(['msgtype' => $messageType], $appends);
+
+        $data[$messageType] = array_merge($data[$messageType] ?? [], $this->propertiesToArray([], $this->jsonAliases));
+
+        return $data;
+    }
+
+    /**
+     * @param array $appends
+     * @param bool  $returnAsArray
+     *
+     * @return string
+     */
+    public function transformToXml(array $appends = [], bool $returnAsArray = false): string
+    {
+        $data = array_merge(['MsgType' => $this->getType()], $this->toXmlArray(), $appends);
+
+        return $returnAsArray ? $data : XML::build($data);
+    }
+
+    /**
+     * @param array $data
+     * @param array $aliases
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    protected function propertiesToArray(array $data, array $aliases = []): array
+    {
+        $this->checkRequiredAttributes();
+
+        foreach ($this->attributes as $property => $value) {
+            if (is_null($value) && !$this->isRequired($property)) {
+                continue;
+            }
+            $alias = array_search($property, $aliases, true);
+
+            $data[$alias ?: $property] = $this->get($property);
+        }
+
+        return $data;
+    }
+
+    public function toXmlArray()
+    {
+        throw new BadMethodCallException(sprintf('Class "%s" cannot support transform to XML message.', __CLASS__));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/MiniProgramPage.php b/vendor/overtrue/wechat/src/Kernel/Messages/MiniProgramPage.php
new file mode 100644
index 0000000..e4973b1
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/MiniProgramPage.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class MiniProgramPage.
+ */
+class MiniProgramPage extends Message
+{
+    protected $type = 'miniprogrampage';
+
+    protected $properties = [
+        'title',
+        'appid',
+        'pagepath',
+        'thumb_media_id',
+    ];
+
+    protected $required = [
+        'thumb_media_id', 'appid', 'pagepath',
+    ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Music.php b/vendor/overtrue/wechat/src/Kernel/Messages/Music.php
new file mode 100644
index 0000000..85feb76
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Music.php
@@ -0,0 +1,73 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Music.
+ *
+ * @property string $url
+ * @property string $hq_url
+ * @property string $title
+ * @property string $description
+ * @property string $thumb_media_id
+ * @property string $format
+ */
+class Music extends Message
+{
+    /**
+     * Messages type.
+     *
+     * @var string
+     */
+    protected $type = 'music';
+
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = [
+        'title',
+        'description',
+        'url',
+        'hq_url',
+        'thumb_media_id',
+        'format',
+    ];
+
+    /**
+     * Aliases of attribute.
+     *
+     * @var array
+     */
+    protected $jsonAliases = [
+        'musicurl' => 'url',
+        'hqmusicurl' => 'hq_url',
+    ];
+
+    public function toXmlArray()
+    {
+        $music = [
+            'Music' => [
+                'Title' => $this->get('title'),
+                'Description' => $this->get('description'),
+                'MusicUrl' => $this->get('url'),
+                'HQMusicUrl' => $this->get('hq_url'),
+            ],
+        ];
+        if ($thumbMediaId = $this->get('thumb_media_id')) {
+            $music['Music']['ThumbMediaId'] = $thumbMediaId;
+        }
+
+        return $music;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/News.php b/vendor/overtrue/wechat/src/Kernel/Messages/News.php
new file mode 100644
index 0000000..2ad46e8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/News.php
@@ -0,0 +1,73 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class News.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class News extends Message
+{
+    /**
+     * @var string
+     */
+    protected $type = 'news';
+
+    /**
+     * @var array
+     */
+    protected $properties = [
+        'items',
+    ];
+
+    /**
+     * News constructor.
+     *
+     * @param array $items
+     */
+    public function __construct(array $items = [])
+    {
+        parent::__construct(compact('items'));
+    }
+
+    /**
+     * @param array $data
+     * @param array $aliases
+     *
+     * @return array
+     */
+    public function propertiesToArray(array $data, array $aliases = []): array
+    {
+        return ['articles' => array_map(function ($item) {
+            if ($item instanceof NewsItem) {
+                return $item->toJsonArray();
+            }
+        }, $this->get('items'))];
+    }
+
+    public function toXmlArray()
+    {
+        $items = [];
+
+        foreach ($this->get('items') as $item) {
+            if ($item instanceof NewsItem) {
+                $items[] = $item->toXmlArray();
+            }
+        }
+
+        return [
+            'ArticleCount' => count($items),
+            'Articles' => $items,
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/NewsItem.php b/vendor/overtrue/wechat/src/Kernel/Messages/NewsItem.php
new file mode 100644
index 0000000..50bf336
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/NewsItem.php
@@ -0,0 +1,57 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class NewsItem.
+ */
+class NewsItem extends Message
+{
+    /**
+     * Messages type.
+     *
+     * @var string
+     */
+    protected $type = 'news';
+
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = [
+        'title',
+        'description',
+        'url',
+        'image',
+    ];
+
+    public function toJsonArray()
+    {
+        return [
+            'title' => $this->get('title'),
+            'description' => $this->get('description'),
+            'url' => $this->get('url'),
+            'picurl' => $this->get('image'),
+        ];
+    }
+
+    public function toXmlArray()
+    {
+        return [
+            'Title' => $this->get('title'),
+            'Description' => $this->get('description'),
+            'Url' => $this->get('url'),
+            'PicUrl' => $this->get('image'),
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Raw.php b/vendor/overtrue/wechat/src/Kernel/Messages/Raw.php
new file mode 100644
index 0000000..53f2a78
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Raw.php
@@ -0,0 +1,56 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Raw.
+ */
+class Raw extends Message
+{
+    /**
+     * @var string
+     */
+    protected $type = 'raw';
+
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = ['content'];
+
+    /**
+     * Constructor.
+     *
+     * @param string $content
+     */
+    public function __construct(string $content)
+    {
+        parent::__construct(['content' => strval($content)]);
+    }
+
+    /**
+     * @param array $appends
+     * @param bool  $withType
+     *
+     * @return array
+     */
+    public function transformForJsonRequest(array $appends = [], $withType = true): array
+    {
+        return json_decode($this->content, true) ?? [];
+    }
+
+    public function __toString()
+    {
+        return $this->get('content') ?? '';
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/ShortVideo.php b/vendor/overtrue/wechat/src/Kernel/Messages/ShortVideo.php
new file mode 100644
index 0000000..1dc1db9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/ShortVideo.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class ShortVideo.
+ *
+ * @property string $title
+ * @property string $media_id
+ * @property string $description
+ * @property string $thumb_media_id
+ */
+class ShortVideo extends Video
+{
+    /**
+     * Messages type.
+     *
+     * @var string
+     */
+    protected $type = 'shortvideo';
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/TaskCard.php b/vendor/overtrue/wechat/src/Kernel/Messages/TaskCard.php
new file mode 100644
index 0000000..d02c411
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/TaskCard.php
@@ -0,0 +1,44 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class TaskCard.
+ *
+ * @property string $title
+ * @property string $description
+ * @property string $url
+ * @property string $task_id
+ * @property array  $btn
+ */
+class TaskCard extends Message
+{
+    /**
+     * Messages type.
+     *
+     * @var string
+     */
+    protected $type = 'taskcard';
+
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = [
+        'title',
+        'description',
+        'url',
+        'task_id',
+        'btn',
+    ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Text.php b/vendor/overtrue/wechat/src/Kernel/Messages/Text.php
new file mode 100644
index 0000000..e355743
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Text.php
@@ -0,0 +1,54 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Text.
+ *
+ * @property string $content
+ */
+class Text extends Message
+{
+    /**
+     * Message type.
+     *
+     * @var string
+     */
+    protected $type = 'text';
+
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = ['content'];
+
+    /**
+     * Text constructor.
+     *
+     * @param string $content
+     */
+    public function __construct(string $content)
+    {
+        parent::__construct(compact('content'));
+    }
+
+    /**
+     * @return array
+     */
+    public function toXmlArray()
+    {
+        return [
+            'Content' => $this->get('content'),
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/TextCard.php b/vendor/overtrue/wechat/src/Kernel/Messages/TextCard.php
new file mode 100644
index 0000000..edfb7c5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/TextCard.php
@@ -0,0 +1,40 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Text.
+ *
+ * @property string $title
+ * @property string $description
+ * @property string $url
+ */
+class TextCard extends Message
+{
+    /**
+     * Messages type.
+     *
+     * @var string
+     */
+    protected $type = 'textcard';
+
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = [
+        'title',
+        'description',
+        'url',
+    ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Transfer.php b/vendor/overtrue/wechat/src/Kernel/Messages/Transfer.php
new file mode 100644
index 0000000..ff27d1a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Transfer.php
@@ -0,0 +1,56 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Transfer.
+ *
+ * @property string $to
+ * @property string $account
+ */
+class Transfer extends Message
+{
+    /**
+     * Messages type.
+     *
+     * @var string
+     */
+    protected $type = 'transfer_customer_service';
+
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = [
+        'account',
+    ];
+
+    /**
+     * Transfer constructor.
+     *
+     * @param string|null $account
+     */
+    public function __construct(string $account = null)
+    {
+        parent::__construct(compact('account'));
+    }
+
+    public function toXmlArray()
+    {
+        return empty($this->get('account')) ? [] : [
+            'TransInfo' => [
+                'KfAccount' => $this->get('account'),
+            ],
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Video.php b/vendor/overtrue/wechat/src/Kernel/Messages/Video.php
new file mode 100644
index 0000000..90f72a0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Video.php
@@ -0,0 +1,65 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Video.
+ *
+ * @property string $video
+ * @property string $title
+ * @property string $media_id
+ * @property string $description
+ * @property string $thumb_media_id
+ */
+class Video extends Media
+{
+    /**
+     * Messages type.
+     *
+     * @var string
+     */
+    protected $type = 'video';
+
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = [
+        'title',
+        'description',
+        'media_id',
+        'thumb_media_id',
+    ];
+
+    /**
+     * Video constructor.
+     *
+     * @param string $mediaId
+     * @param array  $attributes
+     */
+    public function __construct(string $mediaId, array $attributes = [])
+    {
+        parent::__construct($mediaId, 'video', $attributes);
+    }
+
+    public function toXmlArray()
+    {
+        return [
+            'Video' => [
+                'MediaId' => $this->get('media_id'),
+                'Title' => $this->get('title'),
+                'Description' => $this->get('description'),
+            ],
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Voice.php b/vendor/overtrue/wechat/src/Kernel/Messages/Voice.php
new file mode 100644
index 0000000..ff723ea
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Voice.php
@@ -0,0 +1,37 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Voice.
+ *
+ * @property string $media_id
+ */
+class Voice extends Media
+{
+    /**
+     * Messages type.
+     *
+     * @var string
+     */
+    protected $type = 'voice';
+
+    /**
+     * Properties.
+     *
+     * @var array
+     */
+    protected $properties = [
+        'media_id',
+        'recognition',
+    ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/ConfigServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/ConfigServiceProvider.php
new file mode 100644
index 0000000..b33f4a1
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Providers/ConfigServiceProvider.php
@@ -0,0 +1,39 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Providers;
+
+use EasyWeChat\Kernel\Config;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ConfigServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ConfigServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * Registers services on the given container.
+     *
+     * This method should only be used to configure services and parameters.
+     * It should not get services.
+     *
+     * @param Container $pimple A container instance
+     */
+    public function register(Container $pimple)
+    {
+        $pimple['config'] = function ($app) {
+            return new Config($app->getConfig());
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/EventDispatcherServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/EventDispatcherServiceProvider.php
new file mode 100644
index 0000000..9095cef
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Providers/EventDispatcherServiceProvider.php
@@ -0,0 +1,47 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Providers;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+
+/**
+ * Class EventDispatcherServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class EventDispatcherServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * Registers services on the given container.
+     *
+     * This method should only be used to configure services and parameters.
+     * It should not get services.
+     *
+     * @param Container $pimple A container instance
+     */
+    public function register(Container $pimple)
+    {
+        $pimple['events'] = function ($app) {
+            $dispatcher = new EventDispatcher();
+
+            foreach ($app->config->get('events.listen', []) as $event => $listeners) {
+                foreach ($listeners as $listener) {
+                    $dispatcher->addListener($event, $listener);
+                }
+            }
+
+            return $dispatcher;
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/ExtensionServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/ExtensionServiceProvider.php
new file mode 100644
index 0000000..23af832
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Providers/ExtensionServiceProvider.php
@@ -0,0 +1,39 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Providers;
+
+use EasyWeChatComposer\Extension;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ExtensionServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ExtensionServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * Registers services on the given container.
+     *
+     * This method should only be used to configure services and parameters.
+     * It should not get services.
+     *
+     * @param Container $pimple A container instance
+     */
+    public function register(Container $pimple)
+    {
+        $pimple['extension'] = function ($app) {
+            return new Extension($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/HttpClientServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/HttpClientServiceProvider.php
new file mode 100644
index 0000000..aca7fa9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Providers/HttpClientServiceProvider.php
@@ -0,0 +1,39 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Providers;
+
+use GuzzleHttp\Client;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class HttpClientServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class HttpClientServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * Registers services on the given container.
+     *
+     * This method should only be used to configure services and parameters.
+     * It should not get services.
+     *
+     * @param Container $pimple A container instance
+     */
+    public function register(Container $pimple)
+    {
+        $pimple['http_client'] = function ($app) {
+            return new Client($app['config']->get('http', []));
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php
new file mode 100644
index 0000000..83498ac
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php
@@ -0,0 +1,79 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Providers;
+
+use EasyWeChat\Kernel\Log\LogManager;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class LoggingServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class LogServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * Registers services on the given container.
+     *
+     * This method should only be used to configure services and parameters.
+     * It should not get services.
+     *
+     * @param Container $pimple A container instance
+     */
+    public function register(Container $pimple)
+    {
+        $pimple['logger'] = $pimple['log'] = function ($app) {
+            $config = $this->formatLogConfig($app);
+
+            if (!empty($config)) {
+                $app->rebind('config', $app['config']->merge($config));
+            }
+
+            return new LogManager($app);
+        };
+    }
+
+    public function formatLogConfig($app)
+    {
+        if (!empty($app['config']->get('log.channels'))) {
+            return $app['config']->get('log');
+        }
+
+        if (empty($app['config']->get('log'))) {
+            return [
+                'log' => [
+                    'default' => 'errorlog',
+                    'channels' => [
+                        'errorlog' => [
+                            'driver' => 'errorlog',
+                            'level' => 'debug',
+                        ],
+                    ],
+                ],
+            ];
+        }
+
+        return [
+            'log' => [
+                'default' => 'single',
+                'channels' => [
+                    'single' => [
+                        'driver' => 'single',
+                        'path' => $app['config']->get('log.file') ?: \sys_get_temp_dir().'/logs/easywechat.log',
+                        'level' => $app['config']->get('log.level', 'debug'),
+                    ],
+                ],
+            ],
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/RequestServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/RequestServiceProvider.php
new file mode 100644
index 0000000..a0b111d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Providers/RequestServiceProvider.php
@@ -0,0 +1,39 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Providers;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class RequestServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class RequestServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * Registers services on the given container.
+     *
+     * This method should only be used to configure services and parameters.
+     * It should not get services.
+     *
+     * @param Container $pimple A container instance
+     */
+    public function register(Container $pimple)
+    {
+        $pimple['request'] = function () {
+            return Request::createFromGlobals();
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/ServerGuard.php b/vendor/overtrue/wechat/src/Kernel/ServerGuard.php
new file mode 100644
index 0000000..c135e12
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/ServerGuard.php
@@ -0,0 +1,375 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel;
+
+use EasyWeChat\Kernel\Contracts\MessageInterface;
+use EasyWeChat\Kernel\Exceptions\BadRequestException;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Messages\Message;
+use EasyWeChat\Kernel\Messages\News;
+use EasyWeChat\Kernel\Messages\NewsItem;
+use EasyWeChat\Kernel\Messages\Raw as RawMessage;
+use EasyWeChat\Kernel\Messages\Text;
+use EasyWeChat\Kernel\Support\XML;
+use EasyWeChat\Kernel\Traits\Observable;
+use EasyWeChat\Kernel\Traits\ResponseCastable;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Class ServerGuard.
+ *
+ * 1. url 里的 signature 只是将 token+nonce+timestamp 得到的签名,只是用于验证当前请求的,在公众号环境下一直有
+ * 2. 企业号消息发送时是没有的,因为固定为完全模式,所以 url 里不会存在 signature, 只有 msg_signature 用于解密消息的
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServerGuard
+{
+    use Observable;
+    use ResponseCastable;
+
+    /**
+     * @var bool
+     */
+    protected $alwaysValidate = false;
+
+    /**
+     * Empty string.
+     */
+    const SUCCESS_EMPTY_RESPONSE = 'success';
+
+    /**
+     * @var array
+     */
+    const MESSAGE_TYPE_MAPPING = [
+        'text' => Message::TEXT,
+        'image' => Message::IMAGE,
+        'voice' => Message::VOICE,
+        'video' => Message::VIDEO,
+        'shortvideo' => Message::SHORT_VIDEO,
+        'location' => Message::LOCATION,
+        'link' => Message::LINK,
+        'device_event' => Message::DEVICE_EVENT,
+        'device_text' => Message::DEVICE_TEXT,
+        'event' => Message::EVENT,
+        'file' => Message::FILE,
+        'miniprogrampage' => Message::MINIPROGRAM_PAGE,
+    ];
+
+    /**
+     * @var \EasyWeChat\Kernel\ServiceContainer
+     */
+    protected $app;
+
+    /**
+     * Constructor.
+     *
+     * @codeCoverageIgnore
+     *
+     * @param \EasyWeChat\Kernel\ServiceContainer $app
+     */
+    public function __construct(ServiceContainer $app)
+    {
+        $this->app = $app;
+
+        foreach ($this->app->extension->observers() as $observer) {
+            call_user_func_array([$this, 'push'], $observer);
+        }
+    }
+
+    /**
+     * Handle and return response.
+     *
+     * @return Response
+     *
+     * @throws BadRequestException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function serve(): Response
+    {
+        $this->app['logger']->debug('Request received:', [
+            'method' => $this->app['request']->getMethod(),
+            'uri' => $this->app['request']->getUri(),
+            'content-type' => $this->app['request']->getContentType(),
+            'content' => $this->app['request']->getContent(),
+        ]);
+
+        $response = $this->validate()->resolve();
+
+        $this->app['logger']->debug('Server response created:', ['content' => $response->getContent()]);
+
+        return $response;
+    }
+
+    /**
+     * @return $this
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException
+     */
+    public function validate()
+    {
+        if (!$this->alwaysValidate && !$this->isSafeMode()) {
+            return $this;
+        }
+
+        if ($this->app['request']->get('signature') !== $this->signature([
+                $this->getToken(),
+                $this->app['request']->get('timestamp'),
+                $this->app['request']->get('nonce'),
+            ])) {
+            throw new BadRequestException('Invalid request signature.', 400);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Force validate request.
+     *
+     * @return $this
+     */
+    public function forceValidate()
+    {
+        $this->alwaysValidate = true;
+
+        return $this;
+    }
+
+    /**
+     * Get request message.
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|string
+     *
+     * @throws BadRequestException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function getMessage()
+    {
+        $message = $this->parseMessage($this->app['request']->getContent(false));
+
+        if (!is_array($message) || empty($message)) {
+            throw new BadRequestException('No message received.');
+        }
+
+        if ($this->isSafeMode() && !empty($message['Encrypt'])) {
+            $message = $this->decryptMessage($message);
+
+            // Handle JSON format.
+            $dataSet = json_decode($message, true);
+
+            if ($dataSet && (JSON_ERROR_NONE === json_last_error())) {
+                return $dataSet;
+            }
+
+            $message = XML::parse($message);
+        }
+
+        return $this->detectAndCastResponseToType($message, $this->app->config->get('response_type'));
+    }
+
+    /**
+     * Resolve server request and return the response.
+     *
+     * @return \Symfony\Component\HttpFoundation\Response
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    protected function resolve(): Response
+    {
+        $result = $this->handleRequest();
+
+        if ($this->shouldReturnRawResponse()) {
+            $response = new Response($result['response']);
+        } else {
+            $response = new Response(
+                $this->buildResponse($result['to'], $result['from'], $result['response']),
+                200,
+                ['Content-Type' => 'application/xml']
+            );
+        }
+
+        $this->app->events->dispatch(new Events\ServerGuardResponseCreated($response));
+
+        return $response;
+    }
+
+    /**
+     * @return string|null
+     */
+    protected function getToken()
+    {
+        return $this->app['config']['token'];
+    }
+
+    /**
+     * @param string                                                   $to
+     * @param string                                                   $from
+     * @param \EasyWeChat\Kernel\Contracts\MessageInterface|string|int $message
+     *
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function buildResponse(string $to, string $from, $message)
+    {
+        if (empty($message) || self::SUCCESS_EMPTY_RESPONSE === $message) {
+            return self::SUCCESS_EMPTY_RESPONSE;
+        }
+
+        if ($message instanceof RawMessage) {
+            return $message->get('content', self::SUCCESS_EMPTY_RESPONSE);
+        }
+
+        if (is_string($message) || is_numeric($message)) {
+            $message = new Text((string) $message);
+        }
+
+        if (is_array($message) && reset($message) instanceof NewsItem) {
+            $message = new News($message);
+        }
+
+        if (!($message instanceof Message)) {
+            throw new InvalidArgumentException(sprintf('Invalid Messages type "%s".', gettype($message)));
+        }
+
+        return $this->buildReply($to, $from, $message);
+    }
+
+    /**
+     * Handle request.
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    protected function handleRequest(): array
+    {
+        $castedMessage = $this->getMessage();
+
+        $messageArray = $this->detectAndCastResponseToType($castedMessage, 'array');
+
+        $response = $this->dispatch(self::MESSAGE_TYPE_MAPPING[$messageArray['MsgType'] ?? $messageArray['msg_type'] ?? 'text'], $castedMessage);
+
+        return [
+            'to' => $messageArray['FromUserName'] ?? '',
+            'from' => $messageArray['ToUserName'] ?? '',
+            'response' => $response,
+        ];
+    }
+
+    /**
+     * Build reply XML.
+     *
+     * @param string                                        $to
+     * @param string                                        $from
+     * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message
+     *
+     * @return string
+     */
+    protected function buildReply(string $to, string $from, MessageInterface $message): string
+    {
+        $prepends = [
+            'ToUserName' => $to,
+            'FromUserName' => $from,
+            'CreateTime' => time(),
+            'MsgType' => $message->getType(),
+        ];
+
+        $response = $message->transformToXml($prepends);
+
+        if ($this->isSafeMode()) {
+            $this->app['logger']->debug('Messages safe mode is enabled.');
+            $response = $this->app['encryptor']->encrypt($response);
+        }
+
+        return $response;
+    }
+
+    /**
+     * @param array $params
+     *
+     * @return string
+     */
+    protected function signature(array $params)
+    {
+        sort($params, SORT_STRING);
+
+        return sha1(implode($params));
+    }
+
+    /**
+     * Parse message array from raw php input.
+     *
+     * @param string $content
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException
+     */
+    protected function parseMessage($content)
+    {
+        try {
+            if (0 === stripos($content, '<')) {
+                $content = XML::parse($content);
+            } else {
+                // Handle JSON format.
+                $dataSet = json_decode($content, true);
+                if ($dataSet && (JSON_ERROR_NONE === json_last_error())) {
+                    $content = $dataSet;
+                }
+            }
+
+            return (array) $content;
+        } catch (\Exception $e) {
+            throw new BadRequestException(sprintf('Invalid message content:(%s) %s', $e->getCode(), $e->getMessage()), $e->getCode());
+        }
+    }
+
+    /**
+     * Check the request message safe mode.
+     *
+     * @return bool
+     */
+    protected function isSafeMode(): bool
+    {
+        return $this->app['request']->get('signature') && 'aes' === $this->app['request']->get('encrypt_type');
+    }
+
+    /**
+     * @return bool
+     */
+    protected function shouldReturnRawResponse(): bool
+    {
+        return false;
+    }
+
+    /**
+     * @param array $message
+     *
+     * @return mixed
+     */
+    protected function decryptMessage(array $message)
+    {
+        return $message = $this->app['encryptor']->decrypt(
+            $message['Encrypt'],
+            $this->app['request']->get('msg_signature'),
+            $this->app['request']->get('nonce'),
+            $this->app['request']->get('timestamp')
+        );
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/ServiceContainer.php b/vendor/overtrue/wechat/src/Kernel/ServiceContainer.php
new file mode 100644
index 0000000..af1f35b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/ServiceContainer.php
@@ -0,0 +1,167 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel;
+
+use EasyWeChat\Kernel\Providers\ConfigServiceProvider;
+use EasyWeChat\Kernel\Providers\EventDispatcherServiceProvider;
+use EasyWeChat\Kernel\Providers\ExtensionServiceProvider;
+use EasyWeChat\Kernel\Providers\HttpClientServiceProvider;
+use EasyWeChat\Kernel\Providers\LogServiceProvider;
+use EasyWeChat\Kernel\Providers\RequestServiceProvider;
+use EasyWeChatComposer\Traits\WithAggregator;
+use Pimple\Container;
+
+/**
+ * Class ServiceContainer.
+ *
+ * @author overtrue <i@overtrue.me>
+ *
+ * @property \EasyWeChat\Kernel\Config                          $config
+ * @property \Symfony\Component\HttpFoundation\Request          $request
+ * @property \GuzzleHttp\Client                                 $http_client
+ * @property \Monolog\Logger                                    $logger
+ * @property \Symfony\Component\EventDispatcher\EventDispatcher $events
+ */
+class ServiceContainer extends Container
+{
+    use WithAggregator;
+
+    /**
+     * @var string
+     */
+    protected $id;
+
+    /**
+     * @var array
+     */
+    protected $providers = [];
+
+    /**
+     * @var array
+     */
+    protected $defaultConfig = [];
+
+    /**
+     * @var array
+     */
+    protected $userConfig = [];
+
+    /**
+     * Constructor.
+     *
+     * @param array       $config
+     * @param array       $prepends
+     * @param string|null $id
+     */
+    public function __construct(array $config = [], array $prepends = [], string $id = null)
+    {
+        $this->registerProviders($this->getProviders());
+
+        parent::__construct($prepends);
+
+        $this->userConfig = $config;
+
+        $this->id = $id;
+
+        $this->aggregate();
+
+        $this->events->dispatch(new Events\ApplicationInitialized($this));
+    }
+
+    /**
+     * @return string
+     */
+    public function getId()
+    {
+        return $this->id ?? $this->id = md5(json_encode($this->userConfig));
+    }
+
+    /**
+     * @return array
+     */
+    public function getConfig()
+    {
+        $base = [
+            // http://docs.guzzlephp.org/en/stable/request-options.html
+            'http' => [
+                'timeout' => 30.0,
+                'base_uri' => 'https://api.weixin.qq.com/',
+            ],
+        ];
+
+        return array_replace_recursive($base, $this->defaultConfig, $this->userConfig);
+    }
+
+    /**
+     * Return all providers.
+     *
+     * @return array
+     */
+    public function getProviders()
+    {
+        return array_merge([
+            ConfigServiceProvider::class,
+            LogServiceProvider::class,
+            RequestServiceProvider::class,
+            HttpClientServiceProvider::class,
+            ExtensionServiceProvider::class,
+            EventDispatcherServiceProvider::class,
+        ], $this->providers);
+    }
+
+    /**
+     * @param string $id
+     * @param mixed  $value
+     */
+    public function rebind($id, $value)
+    {
+        $this->offsetUnset($id);
+        $this->offsetSet($id, $value);
+    }
+
+    /**
+     * Magic get access.
+     *
+     * @param string $id
+     *
+     * @return mixed
+     */
+    public function __get($id)
+    {
+        if ($this->shouldDelegate($id)) {
+            return $this->delegateTo($id);
+        }
+
+        return $this->offsetGet($id);
+    }
+
+    /**
+     * Magic set access.
+     *
+     * @param string $id
+     * @param mixed  $value
+     */
+    public function __set($id, $value)
+    {
+        $this->offsetSet($id, $value);
+    }
+
+    /**
+     * @param array $providers
+     */
+    public function registerProviders(array $providers)
+    {
+        foreach ($providers as $provider) {
+            parent::register(new $provider());
+        }
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/AES.php b/vendor/overtrue/wechat/src/Kernel/Support/AES.php
new file mode 100644
index 0000000..73b1b24
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/AES.php
@@ -0,0 +1,85 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+/**
+ * Class AES.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class AES
+{
+    /**
+     * @param string $text
+     * @param string $key
+     * @param string $iv
+     * @param int    $option
+     *
+     * @return string
+     */
+    public static function encrypt(string $text, string $key, string $iv, int $option = OPENSSL_RAW_DATA): string
+    {
+        self::validateKey($key);
+        self::validateIv($iv);
+
+        return openssl_encrypt($text, self::getMode($key), $key, $option, $iv);
+    }
+
+    /**
+     * @param string      $cipherText
+     * @param string      $key
+     * @param string      $iv
+     * @param int         $option
+     * @param string|null $method
+     *
+     * @return string
+     */
+    public static function decrypt(string $cipherText, string $key, string $iv, int $option = OPENSSL_RAW_DATA, $method = null): string
+    {
+        self::validateKey($key);
+        self::validateIv($iv);
+
+        return openssl_decrypt($cipherText, $method ?: self::getMode($key), $key, $option, $iv);
+    }
+
+    /**
+     * @param string $key
+     *
+     * @return string
+     */
+    public static function getMode($key)
+    {
+        return 'aes-'.(8 * strlen($key)).'-cbc';
+    }
+
+    /**
+     * @param string $key
+     */
+    public static function validateKey(string $key)
+    {
+        if (!in_array(strlen($key), [16, 24, 32], true)) {
+            throw new \InvalidArgumentException(sprintf('Key length must be 16, 24, or 32 bytes; got key len (%s).', strlen($key)));
+        }
+    }
+
+    /**
+     * @param string $iv
+     *
+     * @throws \InvalidArgumentException
+     */
+    public static function validateIv(string $iv)
+    {
+        if (!empty($iv) && 16 !== strlen($iv)) {
+            throw new \InvalidArgumentException('IV length must be 16 bytes.');
+        }
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Arr.php b/vendor/overtrue/wechat/src/Kernel/Support/Arr.php
new file mode 100644
index 0000000..ac34038
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/Arr.php
@@ -0,0 +1,466 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+/**
+ * Array helper from Illuminate\Support\Arr.
+ */
+class Arr
+{
+    /**
+     * Add an element to an array using "dot" notation if it doesn't exist.
+     *
+     * @param array  $array
+     * @param string $key
+     * @param mixed  $value
+     *
+     * @return array
+     */
+    public static function add(array $array, $key, $value)
+    {
+        if (is_null(static::get($array, $key))) {
+            static::set($array, $key, $value);
+        }
+
+        return $array;
+    }
+
+    /**
+     * Cross join the given arrays, returning all possible permutations.
+     *
+     * @param array ...$arrays
+     *
+     * @return array
+     */
+    public static function crossJoin(...$arrays)
+    {
+        $results = [[]];
+
+        foreach ($arrays as $index => $array) {
+            $append = [];
+
+            foreach ($results as $product) {
+                foreach ($array as $item) {
+                    $product[$index] = $item;
+
+                    $append[] = $product;
+                }
+            }
+
+            $results = $append;
+        }
+
+        return $results;
+    }
+
+    /**
+     * Divide an array into two arrays. One with keys and the other with values.
+     *
+     * @param array $array
+     *
+     * @return array
+     */
+    public static function divide(array $array)
+    {
+        return [array_keys($array), array_values($array)];
+    }
+
+    /**
+     * Flatten a multi-dimensional associative array with dots.
+     *
+     * @param array  $array
+     * @param string $prepend
+     *
+     * @return array
+     */
+    public static function dot(array $array, $prepend = '')
+    {
+        $results = [];
+
+        foreach ($array as $key => $value) {
+            if (is_array($value) && !empty($value)) {
+                $results = array_merge($results, static::dot($value, $prepend.$key.'.'));
+            } else {
+                $results[$prepend.$key] = $value;
+            }
+        }
+
+        return $results;
+    }
+
+    /**
+     * Get all of the given array except for a specified array of items.
+     *
+     * @param array        $array
+     * @param array|string $keys
+     *
+     * @return array
+     */
+    public static function except(array $array, $keys)
+    {
+        static::forget($array, $keys);
+
+        return $array;
+    }
+
+    /**
+     * Determine if the given key exists in the provided array.
+     *
+     * @param array      $array
+     * @param string|int $key
+     *
+     * @return bool
+     */
+    public static function exists(array $array, $key)
+    {
+        return array_key_exists($key, $array);
+    }
+
+    /**
+     * Return the first element in an array passing a given truth test.
+     *
+     * @param array         $array
+     * @param callable|null $callback
+     * @param mixed         $default
+     *
+     * @return mixed
+     */
+    public static function first(array $array, callable $callback = null, $default = null)
+    {
+        if (is_null($callback)) {
+            if (empty($array)) {
+                return $default;
+            }
+
+            foreach ($array as $item) {
+                return $item;
+            }
+        }
+
+        foreach ($array as $key => $value) {
+            if (call_user_func($callback, $value, $key)) {
+                return $value;
+            }
+        }
+
+        return $default;
+    }
+
+    /**
+     * Return the last element in an array passing a given truth test.
+     *
+     * @param array         $array
+     * @param callable|null $callback
+     * @param mixed         $default
+     *
+     * @return mixed
+     */
+    public static function last(array $array, callable $callback = null, $default = null)
+    {
+        if (is_null($callback)) {
+            return empty($array) ? $default : end($array);
+        }
+
+        return static::first(array_reverse($array, true), $callback, $default);
+    }
+
+    /**
+     * Flatten a multi-dimensional array into a single level.
+     *
+     * @param array $array
+     * @param int   $depth
+     *
+     * @return array
+     */
+    public static function flatten(array $array, $depth = INF)
+    {
+        return array_reduce($array, function ($result, $item) use ($depth) {
+            $item = $item instanceof Collection ? $item->all() : $item;
+
+            if (!is_array($item)) {
+                return array_merge($result, [$item]);
+            } elseif (1 === $depth) {
+                return array_merge($result, array_values($item));
+            }
+
+            return array_merge($result, static::flatten($item, $depth - 1));
+        }, []);
+    }
+
+    /**
+     * Remove one or many array items from a given array using "dot" notation.
+     *
+     * @param array        $array
+     * @param array|string $keys
+     */
+    public static function forget(array &$array, $keys)
+    {
+        $original = &$array;
+
+        $keys = (array) $keys;
+
+        if (0 === count($keys)) {
+            return;
+        }
+
+        foreach ($keys as $key) {
+            // if the exact key exists in the top-level, remove it
+            if (static::exists($array, $key)) {
+                unset($array[$key]);
+
+                continue;
+            }
+
+            $parts = explode('.', $key);
+
+            // clean up before each pass
+            $array = &$original;
+
+            while (count($parts) > 1) {
+                $part = array_shift($parts);
+
+                if (isset($array[$part]) && is_array($array[$part])) {
+                    $array = &$array[$part];
+                } else {
+                    continue 2;
+                }
+            }
+
+            unset($array[array_shift($parts)]);
+        }
+    }
+
+    /**
+     * Get an item from an array using "dot" notation.
+     *
+     * @param array  $array
+     * @param string $key
+     * @param mixed  $default
+     *
+     * @return mixed
+     */
+    public static function get(array $array, $key, $default = null)
+    {
+        if (is_null($key)) {
+            return $array;
+        }
+
+        if (static::exists($array, $key)) {
+            return $array[$key];
+        }
+
+        foreach (explode('.', $key) as $segment) {
+            if (static::exists($array, $segment)) {
+                $array = $array[$segment];
+            } else {
+                return $default;
+            }
+        }
+
+        return $array;
+    }
+
+    /**
+     * Check if an item or items exist in an array using "dot" notation.
+     *
+     * @param array        $array
+     * @param string|array $keys
+     *
+     * @return bool
+     */
+    public static function has(array $array, $keys)
+    {
+        if (is_null($keys)) {
+            return false;
+        }
+
+        $keys = (array) $keys;
+
+        if (empty($array)) {
+            return false;
+        }
+
+        if ($keys === []) {
+            return false;
+        }
+
+        foreach ($keys as $key) {
+            $subKeyArray = $array;
+
+            if (static::exists($array, $key)) {
+                continue;
+            }
+
+            foreach (explode('.', $key) as $segment) {
+                if (static::exists($subKeyArray, $segment)) {
+                    $subKeyArray = $subKeyArray[$segment];
+                } else {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Determines if an array is associative.
+     *
+     * An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
+     *
+     * @param array $array
+     *
+     * @return bool
+     */
+    public static function isAssoc(array $array)
+    {
+        $keys = array_keys($array);
+
+        return array_keys($keys) !== $keys;
+    }
+
+    /**
+     * Get a subset of the items from the given array.
+     *
+     * @param array        $array
+     * @param array|string $keys
+     *
+     * @return array
+     */
+    public static function only(array $array, $keys)
+    {
+        return array_intersect_key($array, array_flip((array) $keys));
+    }
+
+    /**
+     * Push an item onto the beginning of an array.
+     *
+     * @param array $array
+     * @param mixed $value
+     * @param mixed $key
+     *
+     * @return array
+     */
+    public static function prepend(array $array, $value, $key = null)
+    {
+        if (is_null($key)) {
+            array_unshift($array, $value);
+        } else {
+            $array = [$key => $value] + $array;
+        }
+
+        return $array;
+    }
+
+    /**
+     * Get a value from the array, and remove it.
+     *
+     * @param array  $array
+     * @param string $key
+     * @param mixed  $default
+     *
+     * @return mixed
+     */
+    public static function pull(array &$array, $key, $default = null)
+    {
+        $value = static::get($array, $key, $default);
+
+        static::forget($array, $key);
+
+        return $value;
+    }
+
+    /**
+     * Get a 1 value from an array.
+     *
+     * @param array    $array
+     * @param int|null $amount
+     *
+     * @return mixed
+     *
+     * @throws \InvalidArgumentException
+     */
+    public static function random(array $array, int $amount = null)
+    {
+        if (is_null($amount)) {
+            return $array[array_rand($array)];
+        }
+
+        $keys = array_rand($array, $amount);
+
+        $results = [];
+
+        foreach ((array) $keys as $key) {
+            $results[] = $array[$key];
+        }
+
+        return $results;
+    }
+
+    /**
+     * Set an array item to a given value using "dot" notation.
+     *
+     * If no key is given to the method, the entire array will be replaced.
+     *
+     * @param array  $array
+     * @param string $key
+     * @param mixed  $value
+     *
+     * @return array
+     */
+    public static function set(array &$array, string $key, $value)
+    {
+        $keys = explode('.', $key);
+
+        while (count($keys) > 1) {
+            $key = array_shift($keys);
+
+            // If the key doesn't exist at this depth, we will just create an empty array
+            // to hold the next value, allowing us to create the arrays to hold final
+            // values at the correct depth. Then we'll keep digging into the array.
+            if (!isset($array[$key]) || !is_array($array[$key])) {
+                $array[$key] = [];
+            }
+
+            $array = &$array[$key];
+        }
+
+        $array[array_shift($keys)] = $value;
+
+        return $array;
+    }
+
+    /**
+     * Filter the array using the given callback.
+     *
+     * @param array    $array
+     * @param callable $callback
+     *
+     * @return array
+     */
+    public static function where(array $array, callable $callback)
+    {
+        return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
+    }
+
+    /**
+     * If the given value is not an array, wrap it in one.
+     *
+     * @param mixed $value
+     *
+     * @return array
+     */
+    public static function wrap($value)
+    {
+        return !is_array($value) ? [$value] : $value;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/ArrayAccessible.php b/vendor/overtrue/wechat/src/Kernel/Support/ArrayAccessible.php
new file mode 100644
index 0000000..72e0b04
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/ArrayAccessible.php
@@ -0,0 +1,66 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+use ArrayAccess;
+use ArrayIterator;
+use EasyWeChat\Kernel\Contracts\Arrayable;
+use IteratorAggregate;
+
+/**
+ * Class ArrayAccessible.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ArrayAccessible implements ArrayAccess, IteratorAggregate, Arrayable
+{
+    private $array;
+
+    public function __construct(array $array = [])
+    {
+        $this->array = $array;
+    }
+
+    public function offsetExists($offset)
+    {
+        return array_key_exists($offset, $this->array);
+    }
+
+    public function offsetGet($offset)
+    {
+        return $this->array[$offset];
+    }
+
+    public function offsetSet($offset, $value)
+    {
+        if (null === $offset) {
+            $this->array[] = $value;
+        } else {
+            $this->array[$offset] = $value;
+        }
+    }
+
+    public function offsetUnset($offset)
+    {
+        unset($this->array[$offset]);
+    }
+
+    public function getIterator()
+    {
+        return new ArrayIterator($this->array);
+    }
+
+    public function toArray()
+    {
+        return $this->array;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Collection.php b/vendor/overtrue/wechat/src/Kernel/Support/Collection.php
new file mode 100644
index 0000000..adf24ea
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/Collection.php
@@ -0,0 +1,421 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+use ArrayAccess;
+use ArrayIterator;
+use Countable;
+use EasyWeChat\Kernel\Contracts\Arrayable;
+use IteratorAggregate;
+use JsonSerializable;
+use Serializable;
+
+/**
+ * Class Collection.
+ */
+class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Serializable, Arrayable
+{
+    /**
+     * The collection data.
+     *
+     * @var array
+     */
+    protected $items = [];
+
+    /**
+     * set data.
+     *
+     * @param array $items
+     */
+    public function __construct(array $items = [])
+    {
+        foreach ($items as $key => $value) {
+            $this->set($key, $value);
+        }
+    }
+
+    /**
+     * Return all items.
+     *
+     * @return array
+     */
+    public function all()
+    {
+        return $this->items;
+    }
+
+    /**
+     * Return specific items.
+     *
+     * @param array $keys
+     *
+     * @return \EasyWeChat\Kernel\Support\Collection
+     */
+    public function only(array $keys)
+    {
+        $return = [];
+
+        foreach ($keys as $key) {
+            $value = $this->get($key);
+
+            if (!is_null($value)) {
+                $return[$key] = $value;
+            }
+        }
+
+        return new static($return);
+    }
+
+    /**
+     * Get all items except for those with the specified keys.
+     *
+     * @param mixed $keys
+     *
+     * @return static
+     */
+    public function except($keys)
+    {
+        $keys = is_array($keys) ? $keys : func_get_args();
+
+        return new static(Arr::except($this->items, $keys));
+    }
+
+    /**
+     * Merge data.
+     *
+     * @param Collection|array $items
+     *
+     * @return \EasyWeChat\Kernel\Support\Collection
+     */
+    public function merge($items)
+    {
+        $clone = new static($this->all());
+
+        foreach ($items as $key => $value) {
+            $clone->set($key, $value);
+        }
+
+        return $clone;
+    }
+
+    /**
+     * To determine Whether the specified element exists.
+     *
+     * @param string $key
+     *
+     * @return bool
+     */
+    public function has($key)
+    {
+        return !is_null(Arr::get($this->items, $key));
+    }
+
+    /**
+     * Retrieve the first item.
+     *
+     * @return mixed
+     */
+    public function first()
+    {
+        return reset($this->items);
+    }
+
+    /**
+     * Retrieve the last item.
+     *
+     * @return bool
+     */
+    public function last()
+    {
+        $end = end($this->items);
+
+        reset($this->items);
+
+        return $end;
+    }
+
+    /**
+     * add the item value.
+     *
+     * @param string $key
+     * @param mixed  $value
+     */
+    public function add($key, $value)
+    {
+        Arr::set($this->items, $key, $value);
+    }
+
+    /**
+     * Set the item value.
+     *
+     * @param string $key
+     * @param mixed  $value
+     */
+    public function set($key, $value)
+    {
+        Arr::set($this->items, $key, $value);
+    }
+
+    /**
+     * Retrieve item from Collection.
+     *
+     * @param string $key
+     * @param mixed  $default
+     *
+     * @return mixed
+     */
+    public function get($key, $default = null)
+    {
+        return Arr::get($this->items, $key, $default);
+    }
+
+    /**
+     * Remove item form Collection.
+     *
+     * @param string $key
+     */
+    public function forget($key)
+    {
+        Arr::forget($this->items, $key);
+    }
+
+    /**
+     * Build to array.
+     *
+     * @return array
+     */
+    public function toArray()
+    {
+        return $this->all();
+    }
+
+    /**
+     * Build to json.
+     *
+     * @param int $option
+     *
+     * @return string
+     */
+    public function toJson($option = JSON_UNESCAPED_UNICODE)
+    {
+        return json_encode($this->all(), $option);
+    }
+
+    /**
+     * To string.
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->toJson();
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.4.0)<br/>
+     * Specify data which should be serialized to JSON.
+     *
+     * @see http://php.net/manual/en/jsonserializable.jsonserialize.php
+     *
+     * @return mixed data which can be serialized by <b>json_encode</b>,
+     *               which is a value of any type other than a resource
+     */
+    public function jsonSerialize()
+    {
+        return $this->items;
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.1.0)<br/>
+     * String representation of object.
+     *
+     * @see http://php.net/manual/en/serializable.serialize.php
+     *
+     * @return string the string representation of the object or null
+     */
+    public function serialize()
+    {
+        return serialize($this->items);
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.0.0)<br/>
+     * Retrieve an external iterator.
+     *
+     * @see http://php.net/manual/en/iteratoraggregate.getiterator.php
+     *
+     * @return \ArrayIterator An instance of an object implementing <b>Iterator</b> or
+     *                        <b>Traversable</b>
+     */
+    public function getIterator()
+    {
+        return new ArrayIterator($this->items);
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.1.0)<br/>
+     * Count elements of an object.
+     *
+     * @see http://php.net/manual/en/countable.count.php
+     *
+     * @return int the custom count as an integer.
+     *             </p>
+     *             <p>
+     *             The return value is cast to an integer
+     */
+    public function count()
+    {
+        return count($this->items);
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.1.0)<br/>
+     * Constructs the object.
+     *
+     * @see  http://php.net/manual/en/serializable.unserialize.php
+     *
+     * @param string $serialized <p>
+     *                           The string representation of the object.
+     *                           </p>
+     *
+     * @return mixed|void
+     */
+    public function unserialize($serialized)
+    {
+        return $this->items = unserialize($serialized);
+    }
+
+    /**
+     * Get a data by key.
+     *
+     * @param string $key
+     *
+     * @return mixed
+     */
+    public function __get($key)
+    {
+        return $this->get($key);
+    }
+
+    /**
+     * Assigns a value to the specified data.
+     *
+     * @param string $key
+     * @param mixed  $value
+     */
+    public function __set($key, $value)
+    {
+        $this->set($key, $value);
+    }
+
+    /**
+     * Whether or not an data exists by key.
+     *
+     * @param string $key
+     *
+     * @return bool
+     */
+    public function __isset($key)
+    {
+        return $this->has($key);
+    }
+
+    /**
+     * Unset an data by key.
+     *
+     * @param string $key
+     */
+    public function __unset($key)
+    {
+        $this->forget($key);
+    }
+
+    /**
+     * var_export.
+     *
+     * @return array
+     */
+    public function __set_state()
+    {
+        return $this->all();
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.0.0)<br/>
+     * Whether a offset exists.
+     *
+     * @see http://php.net/manual/en/arrayaccess.offsetexists.php
+     *
+     * @param mixed $offset <p>
+     *                      An offset to check for.
+     *                      </p>
+     *
+     * @return bool true on success or false on failure.
+     *              The return value will be casted to boolean if non-boolean was returned
+     */
+    public function offsetExists($offset)
+    {
+        return $this->has($offset);
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.0.0)<br/>
+     * Offset to unset.
+     *
+     * @see http://php.net/manual/en/arrayaccess.offsetunset.php
+     *
+     * @param mixed $offset <p>
+     *                      The offset to unset.
+     *                      </p>
+     */
+    public function offsetUnset($offset)
+    {
+        if ($this->offsetExists($offset)) {
+            $this->forget($offset);
+        }
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.0.0)<br/>
+     * Offset to retrieve.
+     *
+     * @see http://php.net/manual/en/arrayaccess.offsetget.php
+     *
+     * @param mixed $offset <p>
+     *                      The offset to retrieve.
+     *                      </p>
+     *
+     * @return mixed Can return all value types
+     */
+    public function offsetGet($offset)
+    {
+        return $this->offsetExists($offset) ? $this->get($offset) : null;
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.0.0)<br/>
+     * Offset to set.
+     *
+     * @see http://php.net/manual/en/arrayaccess.offsetset.php
+     *
+     * @param mixed $offset <p>
+     *                      The offset to assign the value to.
+     *                      </p>
+     * @param mixed $value  <p>
+     *                      The value to set.
+     *                      </p>
+     */
+    public function offsetSet($offset, $value)
+    {
+        $this->set($offset, $value);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/File.php b/vendor/overtrue/wechat/src/Kernel/Support/File.php
new file mode 100644
index 0000000..b51b41d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/File.php
@@ -0,0 +1,135 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+use finfo;
+
+/**
+ * Class File.
+ */
+class File
+{
+    /**
+     * MIME mapping.
+     *
+     * @var array
+     */
+    protected static $extensionMap = [
+        'audio/wav' => '.wav',
+        'audio/x-ms-wma' => '.wma',
+        'video/x-ms-wmv' => '.wmv',
+        'video/mp4' => '.mp4',
+        'audio/mpeg' => '.mp3',
+        'audio/amr' => '.amr',
+        'application/vnd.rn-realmedia' => '.rm',
+        'audio/mid' => '.mid',
+        'image/bmp' => '.bmp',
+        'image/gif' => '.gif',
+        'image/png' => '.png',
+        'image/tiff' => '.tiff',
+        'image/jpeg' => '.jpg',
+        'application/pdf' => '.pdf',
+
+        // 列举更多的文件 mime, 企业号是支持的,公众平台这边之后万一也更新了呢
+        'application/msword' => '.doc',
+
+        'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => '.docx',
+        'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => '.dotx',
+        'application/vnd.ms-word.document.macroEnabled.12' => '.docm',
+        'application/vnd.ms-word.template.macroEnabled.12' => '.dotm',
+
+        'application/vnd.ms-excel' => '.xls',
+
+        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => '.xlsx',
+        'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => '.xltx',
+        'application/vnd.ms-excel.sheet.macroEnabled.12' => '.xlsm',
+        'application/vnd.ms-excel.template.macroEnabled.12' => '.xltm',
+        'application/vnd.ms-excel.addin.macroEnabled.12' => '.xlam',
+        'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => '.xlsb',
+
+        'application/vnd.ms-powerpoint' => '.ppt',
+
+        'application/vnd.openxmlformats-officedocument.presentationml.presentation' => '.pptx',
+        'application/vnd.openxmlformats-officedocument.presentationml.template' => '.potx',
+        'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => '.ppsx',
+        'application/vnd.ms-powerpoint.addin.macroEnabled.12' => '.ppam',
+    ];
+
+    /**
+     * File header signatures.
+     *
+     * @var array
+     */
+    protected static $signatures = [
+        'ffd8ff' => '.jpg',
+        '424d' => '.bmp',
+        '47494638' => '.gif',
+        '2f55736572732f6f7665' => '.png',
+        '89504e47' => '.png',
+        '494433' => '.mp3',
+        'fffb' => '.mp3',
+        'fff3' => '.mp3',
+        '3026b2758e66cf11' => '.wma',
+        '52494646' => '.wav',
+        '57415645' => '.wav',
+        '41564920' => '.avi',
+        '000001ba' => '.mpg',
+        '000001b3' => '.mpg',
+        '2321414d52' => '.amr',
+        '25504446' => '.pdf',
+    ];
+
+    /**
+     * Return steam extension.
+     *
+     * @param string $stream
+     *
+     * @return string|false
+     */
+    public static function getStreamExt($stream)
+    {
+        $ext = self::getExtBySignature($stream);
+
+        try {
+            if (empty($ext) && is_readable($stream)) {
+                $stream = file_get_contents($stream);
+            }
+        } catch (\Exception $e) {
+        }
+
+        $fileInfo = new finfo(FILEINFO_MIME);
+
+        $mime = strstr($fileInfo->buffer($stream), ';', true);
+
+        return isset(self::$extensionMap[$mime]) ? self::$extensionMap[$mime] : $ext;
+    }
+
+    /**
+     * Get file extension by file header signature.
+     *
+     * @param string $stream
+     *
+     * @return string
+     */
+    public static function getExtBySignature($stream)
+    {
+        $prefix = strval(bin2hex(mb_strcut($stream, 0, 10)));
+
+        foreach (self::$signatures as $signature => $extension) {
+            if (0 === strpos($prefix, strval($signature))) {
+                return $extension;
+            }
+        }
+
+        return '';
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Helpers.php b/vendor/overtrue/wechat/src/Kernel/Support/Helpers.php
new file mode 100644
index 0000000..0233787
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/Helpers.php
@@ -0,0 +1,114 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+/*
+ * helpers.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+
+/**
+ * Generate a signature.
+ *
+ * @param array  $attributes
+ * @param string $key
+ * @param string $encryptMethod
+ *
+ * @return string
+ */
+function generate_sign(array $attributes, $key, $encryptMethod = 'md5')
+{
+    ksort($attributes);
+
+    $attributes['key'] = $key;
+
+    return strtoupper(call_user_func_array($encryptMethod, [urldecode(http_build_query($attributes))]));
+}
+
+/**
+ * Get client ip.
+ *
+ * @return string
+ */
+function get_client_ip()
+{
+    if (!empty($_SERVER['REMOTE_ADDR'])) {
+        $ip = $_SERVER['REMOTE_ADDR'];
+    } else {
+        // for php-cli(phpunit etc.)
+        $ip = defined('PHPUNIT_RUNNING') ? '127.0.0.1' : gethostbyname(gethostname());
+    }
+
+    return filter_var($ip, FILTER_VALIDATE_IP) ?: '127.0.0.1';
+}
+
+/**
+ * Get current server ip.
+ *
+ * @return string
+ */
+function get_server_ip()
+{
+    if (!empty($_SERVER['SERVER_ADDR'])) {
+        $ip = $_SERVER['SERVER_ADDR'];
+    } elseif (!empty($_SERVER['SERVER_NAME'])) {
+        $ip = gethostbyname($_SERVER['SERVER_NAME']);
+    } else {
+        // for php-cli(phpunit etc.)
+        $ip = defined('PHPUNIT_RUNNING') ? '127.0.0.1' : gethostbyname(gethostname());
+    }
+
+    return filter_var($ip, FILTER_VALIDATE_IP) ?: '127.0.0.1';
+}
+
+/**
+ * Return current url.
+ *
+ * @return string
+ */
+function current_url()
+{
+    $protocol = 'http://';
+
+    if ((!empty($_SERVER['HTTPS']) && 'off' !== $_SERVER['HTTPS']) || ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? 'http') === 'https') {
+        $protocol = 'https://';
+    }
+
+    return $protocol.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
+}
+
+/**
+ * Return random string.
+ *
+ * @param string $length
+ *
+ * @return string
+ */
+function str_random($length)
+{
+    return Str::random($length);
+}
+
+/**
+ * @param string $content
+ * @param string $publicKey
+ *
+ * @return string
+ */
+function rsa_public_encrypt($content, $publicKey)
+{
+    $encrypted = '';
+    openssl_public_encrypt($content, $encrypted, openssl_pkey_get_public($publicKey), OPENSSL_PKCS1_OAEP_PADDING);
+
+    return base64_encode($encrypted);
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Str.php b/vendor/overtrue/wechat/src/Kernel/Support/Str.php
new file mode 100644
index 0000000..3a51968
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/Str.php
@@ -0,0 +1,193 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+
+/**
+ * Class Str.
+ */
+class Str
+{
+    /**
+     * The cache of snake-cased words.
+     *
+     * @var array
+     */
+    protected static $snakeCache = [];
+
+    /**
+     * The cache of camel-cased words.
+     *
+     * @var array
+     */
+    protected static $camelCache = [];
+
+    /**
+     * The cache of studly-cased words.
+     *
+     * @var array
+     */
+    protected static $studlyCache = [];
+
+    /**
+     * Convert a value to camel case.
+     *
+     * @param string $value
+     *
+     * @return string
+     */
+    public static function camel($value)
+    {
+        if (isset(static::$camelCache[$value])) {
+            return static::$camelCache[$value];
+        }
+
+        return static::$camelCache[$value] = lcfirst(static::studly($value));
+    }
+
+    /**
+     * Generate a more truly "random" alpha-numeric string.
+     *
+     * @param int $length
+     *
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public static function random($length = 16)
+    {
+        $string = '';
+
+        while (($len = strlen($string)) < $length) {
+            $size = $length - $len;
+
+            $bytes = static::randomBytes($size);
+
+            $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);
+        }
+
+        return $string;
+    }
+
+    /**
+     * Generate a more truly "random" bytes.
+     *
+     * @param int $length
+     *
+     * @return string
+     *
+     * @throws RuntimeException
+     *
+     * @codeCoverageIgnore
+     *
+     * @throws \Exception
+     */
+    public static function randomBytes($length = 16)
+    {
+        if (function_exists('random_bytes')) {
+            $bytes = random_bytes($length);
+        } elseif (function_exists('openssl_random_pseudo_bytes')) {
+            $bytes = openssl_random_pseudo_bytes($length, $strong);
+            if (false === $bytes || false === $strong) {
+                throw new RuntimeException('Unable to generate random string.');
+            }
+        } else {
+            throw new RuntimeException('OpenSSL extension is required for PHP 5 users.');
+        }
+
+        return $bytes;
+    }
+
+    /**
+     * Generate a "random" alpha-numeric string.
+     *
+     * Should not be considered sufficient for cryptography, etc.
+     *
+     * @param int $length
+     *
+     * @return string
+     */
+    public static function quickRandom($length = 16)
+    {
+        $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+        return substr(str_shuffle(str_repeat($pool, $length)), 0, $length);
+    }
+
+    /**
+     * Convert the given string to upper-case.
+     *
+     * @param string $value
+     *
+     * @return string
+     */
+    public static function upper($value)
+    {
+        return mb_strtoupper($value);
+    }
+
+    /**
+     * Convert the given string to title case.
+     *
+     * @param string $value
+     *
+     * @return string
+     */
+    public static function title($value)
+    {
+        return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
+    }
+
+    /**
+     * Convert a string to snake case.
+     *
+     * @param string $value
+     * @param string $delimiter
+     *
+     * @return string
+     */
+    public static function snake($value, $delimiter = '_')
+    {
+        $key = $value.$delimiter;
+
+        if (isset(static::$snakeCache[$key])) {
+            return static::$snakeCache[$key];
+        }
+
+        if (!ctype_lower($value)) {
+            $value = strtolower(preg_replace('/(.)(?=[A-Z])/', '$1'.$delimiter, $value));
+        }
+
+        return static::$snakeCache[$key] = trim($value, '_');
+    }
+
+    /**
+     * Convert a value to studly caps case.
+     *
+     * @param string $value
+     *
+     * @return string
+     */
+    public static function studly($value)
+    {
+        $key = $value;
+
+        if (isset(static::$studlyCache[$key])) {
+            return static::$studlyCache[$key];
+        }
+
+        $value = ucwords(str_replace(['-', '_'], ' ', $value));
+
+        return static::$studlyCache[$key] = str_replace(' ', '', $value);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/XML.php b/vendor/overtrue/wechat/src/Kernel/Support/XML.php
new file mode 100644
index 0000000..93026e6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/XML.php
@@ -0,0 +1,167 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+use SimpleXMLElement;
+
+/**
+ * Class XML.
+ */
+class XML
+{
+    /**
+     * XML to array.
+     *
+     * @param string $xml XML string
+     *
+     * @return array
+     */
+    public static function parse($xml)
+    {
+        $backup = libxml_disable_entity_loader(true);
+
+        $result = self::normalize(simplexml_load_string(self::sanitize($xml), 'SimpleXMLElement', LIBXML_COMPACT | LIBXML_NOCDATA | LIBXML_NOBLANKS));
+
+        libxml_disable_entity_loader($backup);
+
+        return $result;
+    }
+
+    /**
+     * XML encode.
+     *
+     * @param mixed  $data
+     * @param string $root
+     * @param string $item
+     * @param string $attr
+     * @param string $id
+     *
+     * @return string
+     */
+    public static function build(
+        $data,
+        $root = 'xml',
+        $item = 'item',
+        $attr = '',
+        $id = 'id'
+    ) {
+        if (is_array($attr)) {
+            $_attr = [];
+
+            foreach ($attr as $key => $value) {
+                $_attr[] = "{$key}=\"{$value}\"";
+            }
+
+            $attr = implode(' ', $_attr);
+        }
+
+        $attr = trim($attr);
+        $attr = empty($attr) ? '' : " {$attr}";
+        $xml = "<{$root}{$attr}>";
+        $xml .= self::data2Xml($data, $item, $id);
+        $xml .= "</{$root}>";
+
+        return $xml;
+    }
+
+    /**
+     * Build CDATA.
+     *
+     * @param string $string
+     *
+     * @return string
+     */
+    public static function cdata($string)
+    {
+        return sprintf('<![CDATA[%s]]>', $string);
+    }
+
+    /**
+     * Object to array.
+     *
+     *
+     * @param SimpleXMLElement $obj
+     *
+     * @return array
+     */
+    protected static function normalize($obj)
+    {
+        $result = null;
+
+        if (is_object($obj)) {
+            $obj = (array) $obj;
+        }
+
+        if (is_array($obj)) {
+            foreach ($obj as $key => $value) {
+                $res = self::normalize($value);
+                if (('@attributes' === $key) && ($key)) {
+                    $result = $res; // @codeCoverageIgnore
+                } else {
+                    $result[$key] = $res;
+                }
+            }
+        } else {
+            $result = $obj;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Array to XML.
+     *
+     * @param array  $data
+     * @param string $item
+     * @param string $id
+     *
+     * @return string
+     */
+    protected static function data2Xml($data, $item = 'item', $id = 'id')
+    {
+        $xml = $attr = '';
+
+        foreach ($data as $key => $val) {
+            if (is_numeric($key)) {
+                $id && $attr = " {$id}=\"{$key}\"";
+                $key = $item;
+            }
+
+            $xml .= "<{$key}{$attr}>";
+
+            if ((is_array($val) || is_object($val))) {
+                $xml .= self::data2Xml((array) $val, $item, $id);
+            } else {
+                $xml .= is_numeric($val) ? $val : self::cdata($val);
+            }
+
+            $xml .= "</{$key}>";
+        }
+
+        return $xml;
+    }
+
+    /**
+     * Delete invalid characters in XML.
+     *
+     * @see https://www.w3.org/TR/2008/REC-xml-20081126/#charsets - XML charset range
+     * @see http://php.net/manual/en/regexp.reference.escape.php - escape in UTF-8 mode
+     *
+     * @param string $xml
+     *
+     * @return string
+     */
+    public static function sanitize($xml)
+    {
+        return preg_replace('/[^\x{9}\x{A}\x{D}\x{20}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]+/u', '', $xml);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/HasAttributes.php b/vendor/overtrue/wechat/src/Kernel/Traits/HasAttributes.php
new file mode 100644
index 0000000..238d9e2
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Traits/HasAttributes.php
@@ -0,0 +1,251 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Traits;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Support\Arr;
+use EasyWeChat\Kernel\Support\Str;
+
+/**
+ * Trait Attributes.
+ */
+trait HasAttributes
+{
+    /**
+     * @var array
+     */
+    protected $attributes = [];
+
+    /**
+     * @var bool
+     */
+    protected $snakeable = true;
+
+    /**
+     * Set Attributes.
+     *
+     * @param array $attributes
+     *
+     * @return $this
+     */
+    public function setAttributes(array $attributes = [])
+    {
+        $this->attributes = $attributes;
+
+        return $this;
+    }
+
+    /**
+     * Set attribute.
+     *
+     * @param string $attribute
+     * @param string $value
+     *
+     * @return $this
+     */
+    public function setAttribute($attribute, $value)
+    {
+        Arr::set($this->attributes, $attribute, $value);
+
+        return $this;
+    }
+
+    /**
+     * Get attribute.
+     *
+     * @param string $attribute
+     * @param mixed  $default
+     *
+     * @return mixed
+     */
+    public function getAttribute($attribute, $default = null)
+    {
+        return Arr::get($this->attributes, $attribute, $default);
+    }
+
+    /**
+     * @param string $attribute
+     *
+     * @return bool
+     */
+    public function isRequired($attribute)
+    {
+        return in_array($attribute, $this->getRequired(), true);
+    }
+
+    /**
+     * @return array|mixed
+     */
+    public function getRequired()
+    {
+        return property_exists($this, 'required') ? $this->required : [];
+    }
+
+    /**
+     * Set attribute.
+     *
+     * @param string $attribute
+     * @param mixed  $value
+     *
+     * @return $this
+     */
+    public function with($attribute, $value)
+    {
+        $this->snakeable && $attribute = Str::snake($attribute);
+
+        $this->setAttribute($attribute, $value);
+
+        return $this;
+    }
+
+    /**
+     * Override parent set() method.
+     *
+     * @param string $attribute
+     * @param mixed  $value
+     *
+     * @return $this
+     */
+    public function set($attribute, $value)
+    {
+        $this->setAttribute($attribute, $value);
+
+        return $this;
+    }
+
+    /**
+     * Override parent get() method.
+     *
+     * @param string $attribute
+     * @param mixed  $default
+     *
+     * @return mixed
+     */
+    public function get($attribute, $default = null)
+    {
+        return $this->getAttribute($attribute, $default);
+    }
+
+    /**
+     * @param string $key
+     *
+     * @return bool
+     */
+    public function has(string $key)
+    {
+        return Arr::has($this->attributes, $key);
+    }
+
+    /**
+     * @param array $attributes
+     *
+     * @return $this
+     */
+    public function merge(array $attributes)
+    {
+        $this->attributes = array_merge($this->attributes, $attributes);
+
+        return $this;
+    }
+
+    /**
+     * @param array|string $keys
+     *
+     * @return array
+     */
+    public function only($keys)
+    {
+        return Arr::only($this->attributes, $keys);
+    }
+
+    /**
+     * Return all items.
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function all()
+    {
+        $this->checkRequiredAttributes();
+
+        return $this->attributes;
+    }
+
+    /**
+     * Magic call.
+     *
+     * @param string $method
+     * @param array  $args
+     *
+     * @return $this
+     */
+    public function __call($method, $args)
+    {
+        if (0 === stripos($method, 'with')) {
+            return $this->with(substr($method, 4), array_shift($args));
+        }
+
+        throw new \BadMethodCallException(sprintf('Method "%s" does not exists.', $method));
+    }
+
+    /**
+     * Magic get.
+     *
+     * @param string $property
+     *
+     * @return mixed
+     */
+    public function __get($property)
+    {
+        return $this->get($property);
+    }
+
+    /**
+     * Magic set.
+     *
+     * @param string $property
+     * @param mixed  $value
+     *
+     * @return $this
+     */
+    public function __set($property, $value)
+    {
+        return $this->with($property, $value);
+    }
+
+    /**
+     * Whether or not an data exists by key.
+     *
+     * @param string $key
+     *
+     * @return bool
+     */
+    public function __isset($key)
+    {
+        return isset($this->attributes[$key]);
+    }
+
+    /**
+     * Check required attributes.
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    protected function checkRequiredAttributes()
+    {
+        foreach ($this->getRequired() as $attribute) {
+            if (is_null($this->get($attribute))) {
+                throw new InvalidArgumentException(sprintf('"%s" cannot be empty.', $attribute));
+            }
+        }
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/HasHttpRequests.php b/vendor/overtrue/wechat/src/Kernel/Traits/HasHttpRequests.php
new file mode 100644
index 0000000..9bf0a02
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Traits/HasHttpRequests.php
@@ -0,0 +1,231 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Traits;
+
+use GuzzleHttp\Client;
+use GuzzleHttp\ClientInterface;
+use GuzzleHttp\HandlerStack;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Trait HasHttpRequests.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+trait HasHttpRequests
+{
+    use ResponseCastable;
+
+    /**
+     * @var \GuzzleHttp\ClientInterface
+     */
+    protected $httpClient;
+
+    /**
+     * @var array
+     */
+    protected $middlewares = [];
+
+    /**
+     * @var \GuzzleHttp\HandlerStack
+     */
+    protected $handlerStack;
+
+    /**
+     * @var array
+     */
+    protected static $defaults = [
+        'curl' => [
+            CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
+        ],
+    ];
+
+    /**
+     * Set guzzle default settings.
+     *
+     * @param array $defaults
+     */
+    public static function setDefaultOptions($defaults = [])
+    {
+        self::$defaults = $defaults;
+    }
+
+    /**
+     * Return current guzzle default settings.
+     *
+     * @return array
+     */
+    public static function getDefaultOptions(): array
+    {
+        return self::$defaults;
+    }
+
+    /**
+     * Set GuzzleHttp\Client.
+     *
+     * @param \GuzzleHttp\ClientInterface $httpClient
+     *
+     * @return $this
+     */
+    public function setHttpClient(ClientInterface $httpClient)
+    {
+        $this->httpClient = $httpClient;
+
+        return $this;
+    }
+
+    /**
+     * Return GuzzleHttp\ClientInterface instance.
+     *
+     * @return ClientInterface
+     */
+    public function getHttpClient(): ClientInterface
+    {
+        if (!($this->httpClient instanceof ClientInterface)) {
+            if (property_exists($this, 'app') && $this->app['http_client']) {
+                $this->httpClient = $this->app['http_client'];
+            } else {
+                $this->httpClient = new Client(['handler' => HandlerStack::create($this->getGuzzleHandler())]);
+            }
+        }
+
+        return $this->httpClient;
+    }
+
+    /**
+     * Add a middleware.
+     *
+     * @param callable $middleware
+     * @param string   $name
+     *
+     * @return $this
+     */
+    public function pushMiddleware(callable $middleware, string $name = null)
+    {
+        if (!is_null($name)) {
+            $this->middlewares[$name] = $middleware;
+        } else {
+            array_push($this->middlewares, $middleware);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Return all middlewares.
+     *
+     * @return array
+     */
+    public function getMiddlewares(): array
+    {
+        return $this->middlewares;
+    }
+
+    /**
+     * Make a request.
+     *
+     * @param string $url
+     * @param string $method
+     * @param array  $options
+     *
+     * @return \Psr\Http\Message\ResponseInterface
+     *
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function request($url, $method = 'GET', $options = []): ResponseInterface
+    {
+        $method = strtoupper($method);
+
+        $options = array_merge(self::$defaults, $options, ['handler' => $this->getHandlerStack()]);
+
+        $options = $this->fixJsonIssue($options);
+
+        if (property_exists($this, 'baseUri') && !is_null($this->baseUri)) {
+            $options['base_uri'] = $this->baseUri;
+        }
+
+        $response = $this->getHttpClient()->request($method, $url, $options);
+        $response->getBody()->rewind();
+
+        return $response;
+    }
+
+    /**
+     * @param \GuzzleHttp\HandlerStack $handlerStack
+     *
+     * @return $this
+     */
+    public function setHandlerStack(HandlerStack $handlerStack)
+    {
+        $this->handlerStack = $handlerStack;
+
+        return $this;
+    }
+
+    /**
+     * Build a handler stack.
+     *
+     * @return \GuzzleHttp\HandlerStack
+     */
+    public function getHandlerStack(): HandlerStack
+    {
+        if ($this->handlerStack) {
+            return $this->handlerStack;
+        }
+
+        $this->handlerStack = HandlerStack::create($this->getGuzzleHandler());
+
+        foreach ($this->middlewares as $name => $middleware) {
+            $this->handlerStack->push($middleware, $name);
+        }
+
+        return $this->handlerStack;
+    }
+
+    /**
+     * @param array $options
+     *
+     * @return array
+     */
+    protected function fixJsonIssue(array $options): array
+    {
+        if (isset($options['json']) && is_array($options['json'])) {
+            $options['headers'] = array_merge($options['headers'] ?? [], ['Content-Type' => 'application/json']);
+
+            if (empty($options['json'])) {
+                $options['body'] = \GuzzleHttp\json_encode($options['json'], JSON_FORCE_OBJECT);
+            } else {
+                $options['body'] = \GuzzleHttp\json_encode($options['json'], JSON_UNESCAPED_UNICODE);
+            }
+
+            unset($options['json']);
+        }
+
+        return $options;
+    }
+
+    /**
+     * Get guzzle handler.
+     *
+     * @return callable
+     */
+    protected function getGuzzleHandler()
+    {
+        if (property_exists($this, 'app') && isset($this->app['guzzle_handler'])) {
+            return is_string($handler = $this->app->raw('guzzle_handler'))
+                        ? new $handler()
+                        : $handler;
+        }
+
+        return \GuzzleHttp\choose_handler();
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/InteractsWithCache.php b/vendor/overtrue/wechat/src/Kernel/Traits/InteractsWithCache.php
new file mode 100644
index 0000000..e1c80e4
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Traits/InteractsWithCache.php
@@ -0,0 +1,109 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Traits;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\ServiceContainer;
+use Psr\Cache\CacheItemPoolInterface;
+use Psr\SimpleCache\CacheInterface as SimpleCacheInterface;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Psr16Cache;
+use Symfony\Component\Cache\Simple\FilesystemCache;
+
+/**
+ * Trait InteractsWithCache.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+trait InteractsWithCache
+{
+    /**
+     * @var \Psr\SimpleCache\CacheInterface
+     */
+    protected $cache;
+
+    /**
+     * Get cache instance.
+     *
+     * @return \Psr\SimpleCache\CacheInterface
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function getCache()
+    {
+        if ($this->cache) {
+            return $this->cache;
+        }
+
+        if (property_exists($this, 'app') && $this->app instanceof ServiceContainer && isset($this->app['cache'])) {
+            $this->setCache($this->app['cache']);
+
+            // Fix PHPStan error
+            assert($this->cache instanceof \Psr\SimpleCache\CacheInterface);
+
+            return $this->cache;
+        }
+
+        return $this->cache = $this->createDefaultCache();
+    }
+
+    /**
+     * Set cache instance.
+     *
+     * @param \Psr\SimpleCache\CacheInterface|\Psr\Cache\CacheItemPoolInterface $cache
+     *
+     * @return $this
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function setCache($cache)
+    {
+        if (empty(\array_intersect([SimpleCacheInterface::class, CacheItemPoolInterface::class], \class_implements($cache)))) {
+            throw new InvalidArgumentException(
+                \sprintf('The cache instance must implements %s or %s interface.',
+                    SimpleCacheInterface::class, CacheItemPoolInterface::class
+                )
+            );
+        }
+
+        if ($cache instanceof CacheItemPoolInterface) {
+            if (!$this->isSymfony43()) {
+                throw new InvalidArgumentException(sprintf('The cache instance must implements %s', SimpleCacheInterface::class));
+            }
+            $cache = new Psr16Cache($cache);
+        }
+
+        $this->cache = $cache;
+
+        return $this;
+    }
+
+    /**
+     * @return \Psr\SimpleCache\CacheInterface
+     */
+    protected function createDefaultCache()
+    {
+        if ($this->isSymfony43()) {
+            return new Psr16Cache(new FilesystemAdapter('easywechat', 1500));
+        }
+
+        return new FilesystemCache();
+    }
+
+    /**
+     * @return bool
+     */
+    protected function isSymfony43(): bool
+    {
+        return \class_exists('Symfony\Component\Cache\Psr16Cache');
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/Observable.php b/vendor/overtrue/wechat/src/Kernel/Traits/Observable.php
new file mode 100644
index 0000000..7db03f5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Traits/Observable.php
@@ -0,0 +1,273 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Traits;
+
+use EasyWeChat\Kernel\Clauses\Clause;
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+use EasyWeChat\Kernel\Decorators\FinallyResult;
+use EasyWeChat\Kernel\Decorators\TerminateResult;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Trait Observable.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+trait Observable
+{
+    /**
+     * @var array
+     */
+    protected $handlers = [];
+
+    /**
+     * @var array
+     */
+    protected $clauses = [];
+
+    /**
+     * @param \Closure|EventHandlerInterface|callable|string $handler
+     * @param \Closure|EventHandlerInterface|callable|string $condition
+     *
+     * @return \EasyWeChat\Kernel\Clauses\Clause
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \ReflectionException
+     */
+    public function push($handler, $condition = '*')
+    {
+        list($handler, $condition) = $this->resolveHandlerAndCondition($handler, $condition);
+
+        if (!isset($this->handlers[$condition])) {
+            $this->handlers[$condition] = [];
+        }
+
+        array_push($this->handlers[$condition], $handler);
+
+        return $this->newClause($handler);
+    }
+
+    /**
+     * @param \Closure|EventHandlerInterface|string $handler
+     * @param \Closure|EventHandlerInterface|string $condition
+     *
+     * @return \EasyWeChat\Kernel\Clauses\Clause
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \ReflectionException
+     */
+    public function unshift($handler, $condition = '*')
+    {
+        list($handler, $condition) = $this->resolveHandlerAndCondition($handler, $condition);
+
+        if (!isset($this->handlers[$condition])) {
+            $this->handlers[$condition] = [];
+        }
+
+        array_unshift($this->handlers[$condition], $handler);
+
+        return $this->newClause($handler);
+    }
+
+    /**
+     * @param string                                $condition
+     * @param \Closure|EventHandlerInterface|string $handler
+     *
+     * @return \EasyWeChat\Kernel\Clauses\Clause
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \ReflectionException
+     */
+    public function observe($condition, $handler)
+    {
+        return $this->push($handler, $condition);
+    }
+
+    /**
+     * @param string                                $condition
+     * @param \Closure|EventHandlerInterface|string $handler
+     *
+     * @return \EasyWeChat\Kernel\Clauses\Clause
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \ReflectionException
+     */
+    public function on($condition, $handler)
+    {
+        return $this->push($handler, $condition);
+    }
+
+    /**
+     * @param string|int $event
+     * @param mixed      ...$payload
+     *
+     * @return mixed|null
+     */
+    public function dispatch($event, $payload)
+    {
+        return $this->notify($event, $payload);
+    }
+
+    /**
+     * @param string|int $event
+     * @param mixed      ...$payload
+     *
+     * @return mixed|null
+     */
+    public function notify($event, $payload)
+    {
+        $result = null;
+
+        foreach ($this->handlers as $condition => $handlers) {
+            if ('*' === $condition || ($condition & $event) === $event) {
+                foreach ($handlers as $handler) {
+                    if ($clause = $this->clauses[$this->getHandlerHash($handler)] ?? null) {
+                        if ($clause->intercepted($payload)) {
+                            continue;
+                        }
+                    }
+
+                    $response = $this->callHandler($handler, $payload);
+
+                    switch (true) {
+                        case $response instanceof TerminateResult:
+                            return $response->content;
+                        case true === $response:
+                            continue 2;
+                        case false === $response:
+                            break 2;
+                        case !empty($response) && !($result instanceof FinallyResult):
+                            $result = $response;
+                    }
+                }
+            }
+        }
+
+        return $result instanceof FinallyResult ? $result->content : $result;
+    }
+
+    /**
+     * @return array
+     */
+    public function getHandlers()
+    {
+        return $this->handlers;
+    }
+
+    /**
+     * @param mixed $handler
+     *
+     * @return \EasyWeChat\Kernel\Clauses\Clause
+     */
+    protected function newClause($handler): Clause
+    {
+        return $this->clauses[$this->getHandlerHash($handler)] = new Clause();
+    }
+
+    /**
+     * @param mixed $handler
+     *
+     * @return string
+     */
+    protected function getHandlerHash($handler)
+    {
+        if (is_string($handler)) {
+            return $handler;
+        }
+
+        if (is_array($handler)) {
+            return is_string($handler[0])
+                ? $handler[0].'::'.$handler[1]
+                : get_class($handler[0]).$handler[1];
+        }
+
+        return spl_object_hash($handler);
+    }
+
+    /**
+     * @param callable $handler
+     * @param mixed    $payload
+     *
+     * @return mixed
+     */
+    protected function callHandler(callable $handler, $payload)
+    {
+        try {
+            return call_user_func_array($handler, [$payload]);
+        } catch (\Exception $e) {
+            if (property_exists($this, 'app') && $this->app instanceof ServiceContainer) {
+                $this->app['logger']->error($e->getCode().': '.$e->getMessage(), [
+                    'code' => $e->getCode(),
+                    'message' => $e->getMessage(),
+                    'file' => $e->getFile(),
+                    'line' => $e->getLine(),
+                ]);
+            }
+        }
+    }
+
+    /**
+     * @param mixed $handler
+     *
+     * @return \Closure
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \ReflectionException
+     */
+    protected function makeClosure($handler)
+    {
+        if (is_callable($handler)) {
+            return $handler;
+        }
+
+        if (is_string($handler) && '*' !== $handler) {
+            if (!class_exists($handler)) {
+                throw new InvalidArgumentException(sprintf('Class "%s" not exists.', $handler));
+            }
+
+            if (!in_array(EventHandlerInterface::class, (new \ReflectionClass($handler))->getInterfaceNames(), true)) {
+                throw new InvalidArgumentException(sprintf('Class "%s" not an instance of "%s".', $handler, EventHandlerInterface::class));
+            }
+
+            return function ($payload) use ($handler) {
+                return (new $handler($this->app ?? null))->handle($payload);
+            };
+        }
+
+        if ($handler instanceof EventHandlerInterface) {
+            return function () use ($handler) {
+                return $handler->handle(...func_get_args());
+            };
+        }
+
+        throw new InvalidArgumentException('No valid handler is found in arguments.');
+    }
+
+    /**
+     * @param mixed $handler
+     * @param mixed $condition
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \ReflectionException
+     */
+    protected function resolveHandlerAndCondition($handler, $condition): array
+    {
+        if (is_int($handler) || (is_string($handler) && !class_exists($handler))) {
+            list($handler, $condition) = [$condition, $handler];
+        }
+
+        return [$this->makeClosure($handler), $condition];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/ResponseCastable.php b/vendor/overtrue/wechat/src/Kernel/Traits/ResponseCastable.php
new file mode 100644
index 0000000..b74e1d7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Traits/ResponseCastable.php
@@ -0,0 +1,96 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Traits;
+
+use EasyWeChat\Kernel\Contracts\Arrayable;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
+use EasyWeChat\Kernel\Http\Response;
+use EasyWeChat\Kernel\Support\Collection;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Trait ResponseCastable.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+trait ResponseCastable
+{
+    /**
+     * @param \Psr\Http\Message\ResponseInterface $response
+     * @param string|null                         $type
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    protected function castResponseToType(ResponseInterface $response, $type = null)
+    {
+        $response = Response::buildFromPsrResponse($response);
+        $response->getBody()->rewind();
+
+        switch ($type ?? 'array') {
+            case 'collection':
+                return $response->toCollection();
+            case 'array':
+                return $response->toArray();
+            case 'object':
+                return $response->toObject();
+            case 'raw':
+                return $response;
+            default:
+                if (!is_subclass_of($type, Arrayable::class)) {
+                    throw new InvalidConfigException(sprintf(
+                        'Config key "response_type" classname must be an instanceof %s',
+                        Arrayable::class
+                    ));
+                }
+
+                return new $type($response);
+        }
+    }
+
+    /**
+     * @param mixed       $response
+     * @param string|null $type
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    protected function detectAndCastResponseToType($response, $type = null)
+    {
+        switch (true) {
+            case $response instanceof ResponseInterface:
+                $response = Response::buildFromPsrResponse($response);
+
+                break;
+            case $response instanceof Arrayable:
+                $response = new Response(200, [], json_encode($response->toArray()));
+
+                break;
+            case ($response instanceof Collection) || is_array($response) || is_object($response):
+                $response = new Response(200, [], json_encode($response));
+
+                break;
+            case is_scalar($response):
+                $response = new Response(200, [], (string) $response);
+
+                break;
+            default:
+                throw new InvalidArgumentException(sprintf('Unsupported response type "%s"', gettype($response)));
+        }
+
+        return $this->castResponseToType($response, $type);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Application.php b/vendor/overtrue/wechat/src/MicroMerchant/Application.php
new file mode 100644
index 0000000..ff7d06f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Application.php
@@ -0,0 +1,176 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\ServiceContainer;
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException;
+
+/**
+ * Class Application.
+ *
+ * @author liuml <liumenglei0211@gmail.com>
+ *
+ * @property \EasyWeChat\MicroMerchant\Certficates\Client    $certficates
+ * @property \EasyWeChat\MicroMerchant\Material\Client       $material
+ * @property \EasyWeChat\MicroMerchant\MerchantConfig\Client $merchantConfig
+ * @property \EasyWeChat\MicroMerchant\Withdraw\Client       $withdraw
+ * @property \EasyWeChat\MicroMerchant\Media\Client          $media
+ *
+ * @method mixed submitApplication(array $params)
+ * @method mixed getStatus(string $applymentId, string $businessCode = '')
+ * @method mixed upgrade(array $params)
+ * @method mixed getUpgradeStatus(string $subMchId = '')
+ */
+class Application extends ServiceContainer
+{
+    /**
+     * @var array
+     */
+    protected $providers = [
+        // Base services
+        Base\ServiceProvider::class,
+        Certficates\ServiceProvider::class,
+        MerchantConfig\ServiceProvider::class,
+        Material\ServiceProvider::class,
+        Withdraw\ServiceProvider::class,
+        Media\ServiceProvider::class,
+    ];
+
+    /**
+     * @var array
+     */
+    protected $defaultConfig = [
+        'http' => [
+            'base_uri' => 'https://api.mch.weixin.qq.com/',
+        ],
+        'log' => [
+            'default' => 'dev', // 默认使用的 channel,生产环境可以改为下面的 prod
+            'channels' => [
+                // 测试环境
+                'dev' => [
+                    'driver' => 'single',
+                    'path' => '/tmp/easywechat.log',
+                    'level' => 'debug',
+                ],
+                // 生产环境
+                'prod' => [
+                    'driver' => 'daily',
+                    'path' => '/tmp/easywechat.log',
+                    'level' => 'info',
+                ],
+            ],
+        ],
+    ];
+
+    /**
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function getKey()
+    {
+        $key = $this['config']->key;
+
+        if (empty($key)) {
+            throw new InvalidArgumentException('config key connot be empty.');
+        }
+
+        if (32 !== strlen($key)) {
+            throw new InvalidArgumentException(sprintf("'%s' should be 32 chars length.", $key));
+        }
+
+        return $key;
+    }
+
+    /**
+     * set sub-mch-id and appid.
+     *
+     * @param string $subMchId Identification Number of Small and Micro Businessmen Reported by Service Providers
+     * @param string $appId    Public Account ID of Service Provider
+     *
+     * @return $this
+     */
+    public function setSubMchId(string $subMchId, string $appId = '')
+    {
+        $this['config']->set('sub_mch_id', $subMchId);
+        $this['config']->set('appid', $appId);
+
+        return $this;
+    }
+
+    /**
+     * setCertificate.
+     *
+     * @param string $certificate
+     * @param string $serialNo
+     *
+     * @return $this
+     */
+    public function setCertificate(string $certificate, string $serialNo)
+    {
+        $this['config']->set('certificate', $certificate);
+        $this['config']->set('serial_no', $serialNo);
+
+        return $this;
+    }
+
+    /**
+     * Returning true indicates that the verification is successful,
+     * returning false indicates that the signature field does not exist or is empty,
+     * and if the signature verification is wrong, the InvalidSignException will be thrown directly.
+     *
+     * @param array $data
+     *
+     * @return bool
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException
+     */
+    public function verifySignature(array $data)
+    {
+        if (!isset($data['sign']) || empty($data['sign'])) {
+            return false;
+        }
+
+        $sign = $data['sign'];
+        strlen($sign) > 32 && $signType = 'HMAC-SHA256';
+        unset($data['sign']);
+        $secretKey = $this->getKey();
+
+        if ('HMAC-SHA256' === ($signType ?? 'MD5')) {
+            $encryptMethod = function ($str) use ($secretKey) {
+                return hash_hmac('sha256', $str, $secretKey);
+            };
+        } else {
+            $encryptMethod = 'md5';
+        }
+
+        if (Support\generate_sign($data, $secretKey, $encryptMethod) === $sign) {
+            return true;
+        }
+
+        throw new InvalidSignException('return value signature verification error');
+    }
+
+    /**
+     * @param string $name
+     * @param array  $arguments
+     *
+     * @return mixed
+     */
+    public function __call($name, $arguments)
+    {
+        return call_user_func_array([$this['base'], $name], $arguments);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Base/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Base/Client.php
new file mode 100644
index 0000000..ba92756
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Base/Client.php
@@ -0,0 +1,126 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Base;
+
+use EasyWeChat\MicroMerchant\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author   liuml  <liumenglei0211@163.com>
+ * @DateTime 2019-05-30  14:19
+ */
+class Client extends BaseClient
+{
+    /**
+     * apply to settle in to become a small micro merchant.
+     *
+     * @param array $params
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function submitApplication(array $params)
+    {
+        $params = $this->processParams(array_merge($params, [
+            'version' => '3.0',
+            'cert_sn' => '',
+            'sign_type' => 'HMAC-SHA256',
+            'nonce_str' => uniqid('micro'),
+        ]));
+
+        return $this->safeRequest('applyment/micro/submit', $params);
+    }
+
+    /**
+     * query application status.
+     *
+     * @param string $applymentId
+     * @param string $businessCode
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getStatus(string $applymentId, string $businessCode = '')
+    {
+        if (!empty($applymentId)) {
+            $params = [
+                'applyment_id' => $applymentId,
+            ];
+        } else {
+            $params = [
+                'business_code' => $businessCode,
+            ];
+        }
+
+        $params = array_merge($params, [
+            'version' => '1.0',
+            'sign_type' => 'HMAC-SHA256',
+            'nonce_str' => uniqid('micro'),
+        ]);
+
+        return $this->safeRequest('applyment/micro/getstate', $params);
+    }
+
+    /**
+     * merchant upgrade api.
+     *
+     * @param array $params
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function upgrade(array $params)
+    {
+        $params['sub_mch_id'] = $params['sub_mch_id'] ?? $this->app['config']->sub_mch_id;
+        $params = $this->processParams(array_merge($params, [
+            'version' => '1.0',
+            'cert_sn' => '',
+            'sign_type' => 'HMAC-SHA256',
+            'nonce_str' => uniqid('micro'),
+        ]));
+
+        return $this->safeRequest('applyment/micro/submitupgrade', $params);
+    }
+
+    /**
+     * get upgrade status.
+     *
+     * @param string $subMchId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getUpgradeStatus(string $subMchId = '')
+    {
+        return $this->safeRequest('applyment/micro/getupgradestate', [
+            'version' => '1.0',
+            'sign_type' => 'HMAC-SHA256',
+            'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id,
+            'nonce_str' => uniqid('micro'),
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Base/ServiceProvider.php
new file mode 100644
index 0000000..db2f056
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Base/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Base;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['base'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Certficates/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/Client.php
new file mode 100644
index 0000000..fc572dc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/Client.php
@@ -0,0 +1,103 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Certficates;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\MicroMerchant\Kernel\BaseClient;
+use EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidExtensionException;
+
+/**
+ * Class Client.
+ *
+ * @author   liuml  <liumenglei0211@163.com>
+ * @DateTime 2019-05-30  14:19
+ */
+class Client extends BaseClient
+{
+    /**
+     * get certficates.
+     *
+     * @param bool $returnRaw
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidExtensionException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(bool $returnRaw = false)
+    {
+        $params = [
+            'sign_type' => 'HMAC-SHA256',
+            'nonce_str' => uniqid('micro'),
+        ];
+
+        if (true === $returnRaw) {
+            return $this->requestRaw('risk/getcertficates', $params);
+        }
+        /** @var array $response */
+        $response = $this->requestArray('risk/getcertficates', $params);
+
+        if ('SUCCESS' !== $response['return_code']) {
+            throw new InvalidArgumentException(
+                sprintf(
+                    'Failed to get certificate. return_code_msg: "%s" .',
+                    $response['return_code'].'('.$response['return_msg'].')'
+                )
+            );
+        }
+        if ('SUCCESS' !== $response['result_code']) {
+            throw new InvalidArgumentException(
+                sprintf(
+                    'Failed to get certificate. result_err_code_des: "%s" .',
+                    $response['result_code'].'('.$response['err_code'].'['.$response['err_code_des'].'])'
+                )
+            );
+        }
+        $certificates = \GuzzleHttp\json_decode($response['certificates'], true)['data'][0];
+        $ciphertext = $this->decrypt($certificates['encrypt_certificate']);
+        unset($certificates['encrypt_certificate']);
+        $certificates['certificates'] = $ciphertext;
+
+        return $certificates;
+    }
+
+    /**
+     * decrypt ciphertext.
+     *
+     * @param array $encryptCertificate
+     *
+     * @return string
+     *
+     * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidExtensionException
+     */
+    public function decrypt(array $encryptCertificate)
+    {
+        if (false === extension_loaded('sodium')) {
+            throw new InvalidExtensionException('sodium extension is not installed,Reference link https://php.net/manual/zh/book.sodium.php');
+        }
+
+        if (false === sodium_crypto_aead_aes256gcm_is_available()) {
+            throw new InvalidExtensionException('aes256gcm is not currently supported');
+        }
+
+        // sodium_crypto_aead_aes256gcm_decrypt function needs to open libsodium extension.
+        // https://www.php.net/manual/zh/function.sodium-crypto-aead-aes256gcm-decrypt.php
+        return sodium_crypto_aead_aes256gcm_decrypt(
+            base64_decode($encryptCertificate['ciphertext'], true),
+            $encryptCertificate['associated_data'],
+            $encryptCertificate['nonce'],
+            $this->app['config']->apiv3_key
+        );
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Certficates/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/ServiceProvider.php
new file mode 100644
index 0000000..2e88b7e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Certficates;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['certficates'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/BaseClient.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/BaseClient.php
new file mode 100644
index 0000000..18c461e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/BaseClient.php
@@ -0,0 +1,261 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Kernel;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\MicroMerchant\Application;
+use EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException;
+use EasyWeChat\Payment\Kernel\BaseClient as PaymentBaseClient;
+
+/**
+ * Class BaseClient.
+ *
+ * @author   liuml  <liumenglei0211@163.com>
+ * @DateTime 2019-07-10  12:06
+ */
+class BaseClient extends PaymentBaseClient
+{
+    /**
+     * @var string
+     */
+    protected $certificates;
+
+    /**
+     * BaseClient constructor.
+     *
+     * @param \EasyWeChat\MicroMerchant\Application $app
+     */
+    public function __construct(Application $app)
+    {
+        $this->app = $app;
+
+        $this->setHttpClient($this->app['http_client']);
+    }
+
+    /**
+     * Extra request params.
+     *
+     * @return array
+     */
+    protected function prepends()
+    {
+        return [];
+    }
+
+    /**
+     * httpUpload.
+     *
+     * @param string $url
+     * @param array  $files
+     * @param array  $form
+     * @param array  $query
+     * @param bool   $returnResponse
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function httpUpload(string $url, array $files = [], array $form = [], array $query = [], $returnResponse = false)
+    {
+        $multipart = [];
+
+        foreach ($files as $name => $path) {
+            $multipart[] = [
+                'name' => $name,
+                'contents' => fopen($path, 'r'),
+            ];
+        }
+
+        $base = [
+            'mch_id' => $this->app['config']['mch_id'],
+        ];
+
+        $form = array_merge($base, $form);
+
+        $form['sign'] = $this->getSign($form);
+
+        foreach ($form as $name => $contents) {
+            $multipart[] = compact('name', 'contents');
+        }
+
+        $options = [
+            'query' => $query,
+            'multipart' => $multipart,
+            'connect_timeout' => 30,
+            'timeout' => 30,
+            'read_timeout' => 30,
+            'cert' => $this->app['config']->get('cert_path'),
+            'ssl_key' => $this->app['config']->get('key_path'),
+        ];
+
+        $this->pushMiddleware($this->logMiddleware(), 'log');
+
+        $response = $this->performRequest($url, 'POST', $options);
+
+        $result = $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
+        // auto verify signature
+        if ($returnResponse || 'array' !== ($this->app->config->get('response_type') ?? 'array')) {
+            $this->app->verifySignature($this->castResponseToType($response, 'array'));
+        } else {
+            $this->app->verifySignature($result);
+        }
+
+        return $result;
+    }
+
+    /**
+     * request.
+     *
+     * @param string $endpoint
+     * @param array  $params
+     * @param string $method
+     * @param array  $options
+     * @param bool   $returnResponse
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function request(string $endpoint, array $params = [], $method = 'post', array $options = [], $returnResponse = false)
+    {
+        $base = [
+            'mch_id' => $this->app['config']['mch_id'],
+        ];
+
+        $params = array_merge($base, $this->prepends(), $params);
+        $params['sign'] = $this->getSign($params);
+        $options = array_merge([
+            'body' => Support\XML::build($params),
+        ], $options);
+
+        $this->pushMiddleware($this->logMiddleware(), 'log');
+        $response = $this->performRequest($endpoint, $method, $options);
+        $result = $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
+        // auto verify signature
+        if ($returnResponse || 'array' !== ($this->app->config->get('response_type') ?? 'array')) {
+            $this->app->verifySignature($this->castResponseToType($response, 'array'));
+        } else {
+            $this->app->verifySignature($result);
+        }
+
+        return $result;
+    }
+
+    /**
+     * processing parameters contain fields that require sensitive information encryption.
+     *
+     * @param array $params
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException
+     */
+    protected function processParams(array $params)
+    {
+        $serial_no = $this->app['config']->get('serial_no');
+        if (null === $serial_no) {
+            throw new InvalidArgumentException('config serial_no connot be empty.');
+        }
+
+        $params['cert_sn'] = $serial_no;
+        $sensitive_fields = $this->getSensitiveFieldsName();
+        foreach ($params as $k => $v) {
+            if (in_array($k, $sensitive_fields, true)) {
+                $params[$k] = $this->encryptSensitiveInformation($v);
+            }
+        }
+
+        return $params;
+    }
+
+    /**
+     * To id card, mobile phone number and other fields sensitive information encryption.
+     *
+     * @param string $string
+     *
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException
+     */
+    protected function encryptSensitiveInformation(string $string)
+    {
+        $certificates = $this->app['config']->get('certificate');
+        if (null === $certificates) {
+            throw new InvalidArgumentException('config certificate connot be empty.');
+        }
+
+        $encrypted = '';
+        $publicKeyResource = openssl_get_publickey($certificates);
+        $f = openssl_public_encrypt($string, $encrypted, $publicKeyResource);
+        openssl_free_key($publicKeyResource);
+        if ($f) {
+            return base64_encode($encrypted);
+        }
+
+        throw new EncryptException('Encryption of sensitive information failed');
+    }
+
+    /**
+     * get sensitive fields name.
+     *
+     * @return array
+     */
+    protected function getSensitiveFieldsName()
+    {
+        return [
+            'id_card_name',
+            'id_card_number',
+            'account_name',
+            'account_number',
+            'contact',
+            'contact_phone',
+            'contact_email',
+            'legal_person',
+            'mobile_phone',
+            'email',
+        ];
+    }
+
+    /**
+     * getSign.
+     *
+     * @param array $params
+     *
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    protected function getSign(array $params)
+    {
+        $params = array_filter($params);
+
+        $key = $this->app->getKey();
+        if ('HMAC-SHA256' === ($params['sign_type'] ?? 'MD5')) {
+            $encryptMethod = function ($str) use ($key) {
+                return hash_hmac('sha256', $str, $key);
+            };
+        } else {
+            $encryptMethod = 'md5';
+        }
+
+        return Support\generate_sign($params, $key, $encryptMethod);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/EncryptException.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/EncryptException.php
new file mode 100644
index 0000000..dd874ae
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/EncryptException.php
@@ -0,0 +1,23 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Kernel\Exceptions;
+
+use EasyWeChat\Kernel\Exceptions\Exception;
+
+/**
+ * Class EncryptException.
+ *
+ * @author   liuml  <liumenglei0211@163.com>
+ */
+class EncryptException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidExtensionException.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidExtensionException.php
new file mode 100644
index 0000000..41e21cf
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidExtensionException.php
@@ -0,0 +1,23 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Kernel\Exceptions;
+
+use EasyWeChat\Kernel\Exceptions\Exception;
+
+/**
+ * Class InvalidExtensionException.
+ *
+ * @author   liuml  <liumenglei0211@163.com>
+ */
+class InvalidExtensionException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidSignException.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidSignException.php
new file mode 100644
index 0000000..d09d92b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidSignException.php
@@ -0,0 +1,23 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Kernel\Exceptions;
+
+use EasyWeChat\Kernel\Exceptions\Exception;
+
+/**
+ * Class InvalidSignException.
+ *
+ * @author   liuml  <liumenglei0211@163.com>
+ */
+class InvalidSignException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Material/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Material/Client.php
new file mode 100644
index 0000000..ee1038a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Material/Client.php
@@ -0,0 +1,73 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Material;
+
+use EasyWeChat\MicroMerchant\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author   liuml  <liumenglei0211@163.com>
+ * @DateTime 2019-05-30  14:19
+ */
+class Client extends BaseClient
+{
+    /**
+     * update settlement card.
+     *
+     * @param array $params
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setSettlementCard(array $params)
+    {
+        $params['sub_mch_id'] = $params['sub_mch_id'] ?? $this->app['config']->sub_mch_id;
+        $params = $this->processParams(array_merge($params, [
+            'version' => '1.0',
+            'cert_sn' => '',
+            'sign_type' => 'HMAC-SHA256',
+            'nonce_str' => uniqid('micro'),
+        ]));
+
+        return $this->safeRequest('applyment/micro/modifyarchives', $params);
+    }
+
+    /**
+     * update contact info.
+     *
+     * @param array $params
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function updateContact(array $params)
+    {
+        $params['sub_mch_id'] = $params['sub_mch_id'] ?? $this->app['config']->sub_mch_id;
+        $params = $this->processParams(array_merge($params, [
+            'version' => '1.0',
+            'cert_sn' => '',
+            'sign_type' => 'HMAC-SHA256',
+            'nonce_str' => uniqid('micro'),
+        ]));
+
+        return $this->safeRequest('applyment/micro/modifycontactinfo', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Material/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Material/ServiceProvider.php
new file mode 100644
index 0000000..dec5af7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Material/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Material;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['material'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Media/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Media/Client.php
new file mode 100644
index 0000000..fde755b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Media/Client.php
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Media;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\MicroMerchant\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author   liuml  <liumenglei0211@163.com>
+ * @DateTime 2019-06-10 14:50
+ */
+class Client extends BaseClient
+{
+    /**
+     * Upload material.
+     *
+     * @param string $path
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException
+     */
+    public function upload(string $path)
+    {
+        if (!file_exists($path) || !is_readable($path)) {
+            throw new InvalidArgumentException(sprintf("File does not exist, or the file is unreadable: '%s'", $path));
+        }
+
+        $form = [
+            'media_hash' => strtolower(md5_file($path)),
+            'sign_type' => 'HMAC-SHA256',
+        ];
+
+        return $this->httpUpload('secapi/mch/uploadmedia', ['media' => $path], $form);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Media/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Media/ServiceProvider.php
new file mode 100644
index 0000000..9d46a84
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Media/ServiceProvider.php
@@ -0,0 +1,44 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * ServiceProvider.php.
+ *
+ * This file is part of the wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Media;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['media'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/Client.php
new file mode 100644
index 0000000..c8573af
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/Client.php
@@ -0,0 +1,134 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\MerchantConfig;
+
+use EasyWeChat\MicroMerchant\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author   liuml  <liumenglei0211@163.com>
+ * @DateTime 2019-05-30  14:19
+ */
+class Client extends BaseClient
+{
+    /**
+     * Service providers configure recommendation functions for small and micro businesses.
+     *
+     * @param string $subAppId
+     * @param string $subscribeAppId
+     * @param string $receiptAppId
+     * @param string $subMchId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setFollowConfig(string $subAppId, string $subscribeAppId, string $receiptAppId = '', string $subMchId = '')
+    {
+        $params = [
+            'sub_appid' => $subAppId,
+            'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id,
+        ];
+
+        if (!empty($subscribeAppId)) {
+            $params['subscribe_appid'] = $subscribeAppId;
+        } else {
+            $params['receipt_appid'] = $receiptAppId;
+        }
+
+        return $this->safeRequest('secapi/mkt/addrecommendconf', array_merge($params, [
+            'sign_type' => 'HMAC-SHA256',
+            'nonce_str' => uniqid('micro'),
+        ]));
+    }
+
+    /**
+     * Configure the new payment directory.
+     *
+     * @param string $jsapiPath
+     * @param string $appId
+     * @param string $subMchId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function addPath(string $jsapiPath, string $appId = '', string $subMchId = '')
+    {
+        return $this->addConfig([
+            'appid' => $appId ?: $this->app['config']->appid,
+            'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id,
+            'jsapi_path' => $jsapiPath,
+        ]);
+    }
+
+    /**
+     * bind appid.
+     *
+     * @param string $subAppId
+     * @param string $appId
+     * @param string $subMchId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function bindAppId(string $subAppId, string $appId = '', string $subMchId = '')
+    {
+        return $this->addConfig([
+            'appid' => $appId ?: $this->app['config']->appid,
+            'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id,
+            'sub_appid' => $subAppId,
+        ]);
+    }
+
+    /**
+     * add sub dev config.
+     *
+     * @param array $params
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    private function addConfig(array $params)
+    {
+        return $this->safeRequest('secapi/mch/addsubdevconfig', $params);
+    }
+
+    /**
+     * query Sub Dev Config.
+     *
+     * @param string $subMchId
+     * @param string $appId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getConfig(string $subMchId = '', string $appId = '')
+    {
+        return $this->safeRequest('secapi/mch/querysubdevconfig', [
+            'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id,
+            'appid' => $appId ?: $this->app['config']->appid,
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/ServiceProvider.php
new file mode 100644
index 0000000..5b78710
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\MerchantConfig;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['merchantConfig'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/Client.php
new file mode 100644
index 0000000..c96c363
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/Client.php
@@ -0,0 +1,67 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Withdraw;
+
+use EasyWeChat\MicroMerchant\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author   liuml  <liumenglei0211@163.com>
+ * @DateTime 2019-05-30  14:19
+ */
+class Client extends BaseClient
+{
+    /**
+     * Query withdrawal status.
+     *
+     * @param string $date
+     * @param string $subMchId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function queryWithdrawalStatus($date, $subMchId = '')
+    {
+        return $this->safeRequest('fund/queryautowithdrawbydate', [
+            'date' => $date,
+            'sign_type' => 'HMAC-SHA256',
+            'nonce_str' => uniqid('micro'),
+            'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id,
+        ]);
+    }
+
+    /**
+     * Re-initiation of withdrawal.
+     *
+     * @param string $date
+     * @param string $subMchId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function requestWithdraw($date, $subMchId = '')
+    {
+        return $this->safeRequest('fund/reautowithdrawbydate', [
+            'date' => $date,
+            'sign_type' => 'HMAC-SHA256',
+            'nonce_str' => uniqid('micro'),
+            'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id,
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/ServiceProvider.php
new file mode 100644
index 0000000..b9c0141
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Withdraw;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['withdraw'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/Client.php
new file mode 100644
index 0000000..16f273d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/Client.php
@@ -0,0 +1,85 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\ActivityMessage;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+class Client extends BaseClient
+{
+    /**
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function createActivityId()
+    {
+        return $this->httpGet('cgi-bin/message/wxopen/activityid/create');
+    }
+
+    /**
+     * @param string $activityId
+     * @param int    $state
+     * @param array  $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function updateMessage(string $activityId, int $state = 0, array $params = [])
+    {
+        if (!in_array($state, [0, 1], true)) {
+            throw new InvalidArgumentException('"state" should be "0" or "1".');
+        }
+
+        $params = $this->formatParameters($params);
+
+        $params = [
+            'activity_id' => $activityId,
+            'target_state' => $state,
+            'template_info' => ['parameter_list' => $params],
+        ];
+
+        return $this->httpPostJson('cgi-bin/message/wxopen/updatablemsg/send', $params);
+    }
+
+    /**
+     * @param array $params
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    protected function formatParameters(array $params)
+    {
+        $formatted = [];
+
+        foreach ($params as $name => $value) {
+            if (!in_array($name, ['member_count', 'room_limit', 'path', 'version_type'], true)) {
+                continue;
+            }
+
+            if ('version_type' === $name && !in_array($value, ['develop', 'trial', 'release'], true)) {
+                throw new InvalidArgumentException('Invalid value of attribute "version_type".');
+            }
+
+            $formatted[] = [
+                'name' => $name,
+                'value' => strval($value),
+            ];
+        }
+
+        return $formatted;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/ServiceProvider.php
new file mode 100644
index 0000000..fa7d3cf
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/ServiceProvider.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\ActivityMessage;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['activity_message'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/AppCode/Client.php b/vendor/overtrue/wechat/src/MiniProgram/AppCode/Client.php
new file mode 100644
index 0000000..82d2af2
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/AppCode/Client.php
@@ -0,0 +1,92 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\AppCode;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Http\StreamResponse;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get AppCode.
+     *
+     * @param string $path
+     * @param array  $optional
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     */
+    public function get(string $path, array $optional = [])
+    {
+        $params = array_merge([
+            'path' => $path,
+        ], $optional);
+
+        return $this->getStream('wxa/getwxacode', $params);
+    }
+
+    /**
+     * Get AppCode unlimit.
+     *
+     * @param string $scene
+     * @param array  $optional
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     */
+    public function getUnlimit(string $scene, array $optional = [])
+    {
+        $params = array_merge([
+            'scene' => $scene,
+        ], $optional);
+
+        return $this->getStream('wxa/getwxacodeunlimit', $params);
+    }
+
+    /**
+     * Create QrCode.
+     *
+     * @param string   $path
+     * @param int|null $width
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     */
+    public function getQrCode(string $path, int $width = null)
+    {
+        return $this->getStream('cgi-bin/wxaapp/createwxaqrcode', compact('path', 'width'));
+    }
+
+    /**
+     * Get stream.
+     *
+     * @param string $endpoint
+     * @param array  $params
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function getStream(string $endpoint, array $params)
+    {
+        $response = $this->requestRaw($endpoint, 'POST', ['json' => $params]);
+
+        if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) {
+            return StreamResponse::buildFromPsrResponse($response);
+        }
+
+        return $this->castResponseToType($response, $this->app['config']->get('response_type'));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/AppCode/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/AppCode/ServiceProvider.php
new file mode 100644
index 0000000..fefdc22
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/AppCode/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\AppCode;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['app_code'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Application.php b/vendor/overtrue/wechat/src/MiniProgram/Application.php
new file mode 100644
index 0000000..4f81ff7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Application.php
@@ -0,0 +1,83 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram;
+
+use EasyWeChat\BasicService;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Class Application.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ *
+ * @property \EasyWeChat\MiniProgram\Auth\AccessToken           $access_token
+ * @property \EasyWeChat\MiniProgram\DataCube\Client            $data_cube
+ * @property \EasyWeChat\MiniProgram\AppCode\Client             $app_code
+ * @property \EasyWeChat\MiniProgram\Auth\Client                $auth
+ * @property \EasyWeChat\OfficialAccount\Server\Guard           $server
+ * @property \EasyWeChat\MiniProgram\Encryptor                  $encryptor
+ * @property \EasyWeChat\MiniProgram\TemplateMessage\Client     $template_message
+ * @property \EasyWeChat\OfficialAccount\CustomerService\Client $customer_service
+ * @property \EasyWeChat\MiniProgram\Plugin\Client              $plugin
+ * @property \EasyWeChat\MiniProgram\Plugin\DevClient           $plugin_dev
+ * @property \EasyWeChat\MiniProgram\UniformMessage\Client      $uniform_message
+ * @property \EasyWeChat\MiniProgram\ActivityMessage\Client     $activity_message
+ * @property \EasyWeChat\MiniProgram\Express\Client             $logistics
+ * @property \EasyWeChat\MiniProgram\NearbyPoi\Client           $nearby_poi
+ * @property \EasyWeChat\MiniProgram\OCR\Client                 $ocr
+ * @property \EasyWeChat\MiniProgram\Soter\Client               $soter
+ * @property \EasyWeChat\BasicService\Media\Client              $media
+ * @property \EasyWeChat\BasicService\ContentSecurity\Client    $content_security
+ * @property \EasyWeChat\MiniProgram\Mall\ForwardsMall          $mall
+ * @property \EasyWeChat\MiniProgram\SubscribeMessage\Client    $subscribe_message
+ */
+class Application extends ServiceContainer
+{
+    /**
+     * @var array
+     */
+    protected $providers = [
+        Auth\ServiceProvider::class,
+        DataCube\ServiceProvider::class,
+        AppCode\ServiceProvider::class,
+        Server\ServiceProvider::class,
+        TemplateMessage\ServiceProvider::class,
+        CustomerService\ServiceProvider::class,
+        UniformMessage\ServiceProvider::class,
+        ActivityMessage\ServiceProvider::class,
+        OpenData\ServiceProvider::class,
+        Plugin\ServiceProvider::class,
+        Base\ServiceProvider::class,
+        Express\ServiceProvider::class,
+        NearbyPoi\ServiceProvider::class,
+        OCR\ServiceProvider::class,
+        Soter\ServiceProvider::class,
+        Mall\ServiceProvider::class,
+        SubscribeMessage\ServiceProvider::class,
+        // Base services
+        BasicService\Media\ServiceProvider::class,
+        BasicService\ContentSecurity\ServiceProvider::class,
+    ];
+
+    /**
+     * Handle dynamic calls.
+     *
+     * @param string $method
+     * @param array  $args
+     *
+     * @return mixed
+     */
+    public function __call($method, $args)
+    {
+        return $this->base->$method(...$args);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Auth/AccessToken.php b/vendor/overtrue/wechat/src/MiniProgram/Auth/AccessToken.php
new file mode 100644
index 0000000..8147d25
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Auth/AccessToken.php
@@ -0,0 +1,39 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Auth;
+
+use EasyWeChat\Kernel\AccessToken as BaseAccessToken;
+
+/**
+ * Class AccessToken.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class AccessToken extends BaseAccessToken
+{
+    /**
+     * @var string
+     */
+    protected $endpointToGetToken = 'https://api.weixin.qq.com/cgi-bin/token';
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getCredentials(): array
+    {
+        return [
+            'grant_type' => 'client_credential',
+            'appid' => $this->app['config']['app_id'],
+            'secret' => $this->app['config']['secret'],
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Auth/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Auth/Client.php
new file mode 100644
index 0000000..90d2c88
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Auth/Client.php
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Auth;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Auth.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get session info by code.
+     *
+     * @param string $code
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function session(string $code)
+    {
+        $params = [
+            'appid' => $this->app['config']['app_id'],
+            'secret' => $this->app['config']['secret'],
+            'js_code' => $code,
+            'grant_type' => 'authorization_code',
+        ];
+
+        return $this->httpGet('sns/jscode2session', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Auth/ServiceProvider.php
new file mode 100644
index 0000000..fcb687b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Auth/ServiceProvider.php
@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Auth;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        !isset($app['access_token']) && $app['access_token'] = function ($app) {
+            return new AccessToken($app);
+        };
+
+        !isset($app['auth']) && $app['auth'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Base/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Base/Client.php
new file mode 100644
index 0000000..84d6b81
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Base/Client.php
@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Base;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get paid unionid.
+     *
+     * @param string $openid
+     * @param array  $options
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getPaidUnionid($openid, $options = [])
+    {
+        return $this->httpGet('wxa/getpaidunionid', compact('openid') + $options);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Base/ServiceProvider.php
new file mode 100644
index 0000000..d9aa41b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Base/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Base;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['base'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/CustomerService/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/CustomerService/ServiceProvider.php
new file mode 100644
index 0000000..cfd3039
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/CustomerService/ServiceProvider.php
@@ -0,0 +1,34 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\CustomerService;
+
+use EasyWeChat\OfficialAccount\CustomerService\Client;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['customer_service'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/DataCube/Client.php b/vendor/overtrue/wechat/src/MiniProgram/DataCube/Client.php
new file mode 100644
index 0000000..8a10b7e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/DataCube/Client.php
@@ -0,0 +1,174 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\DataCube;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get summary trend.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function summaryTrend(string $from, string $to)
+    {
+        return $this->query('datacube/getweanalysisappiddailysummarytrend', $from, $to);
+    }
+
+    /**
+     * Get daily visit trend.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function dailyVisitTrend(string $from, string $to)
+    {
+        return $this->query('datacube/getweanalysisappiddailyvisittrend', $from, $to);
+    }
+
+    /**
+     * Get weekly visit trend.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function weeklyVisitTrend(string $from, string $to)
+    {
+        return $this->query('datacube/getweanalysisappidweeklyvisittrend', $from, $to);
+    }
+
+    /**
+     * Get monthly visit trend.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function monthlyVisitTrend(string $from, string $to)
+    {
+        return $this->query('datacube/getweanalysisappidmonthlyvisittrend', $from, $to);
+    }
+
+    /**
+     * Get visit distribution.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function visitDistribution(string $from, string $to)
+    {
+        return $this->query('datacube/getweanalysisappidvisitdistribution', $from, $to);
+    }
+
+    /**
+     * Get daily retain info.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function dailyRetainInfo(string $from, string $to)
+    {
+        return $this->query('datacube/getweanalysisappiddailyretaininfo', $from, $to);
+    }
+
+    /**
+     * Get weekly retain info.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function weeklyRetainInfo(string $from, string $to)
+    {
+        return $this->query('datacube/getweanalysisappidweeklyretaininfo', $from, $to);
+    }
+
+    /**
+     * Get monthly retain info.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function monthlyRetainInfo(string $from, string $to)
+    {
+        return $this->query('datacube/getweanalysisappidmonthlyretaininfo', $from, $to);
+    }
+
+    /**
+     * Get visit page.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function visitPage(string $from, string $to)
+    {
+        return $this->query('datacube/getweanalysisappidvisitpage', $from, $to);
+    }
+
+    /**
+     * Get user portrait.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function userPortrait(string $from, string $to)
+    {
+        return $this->query('datacube/getweanalysisappiduserportrait', $from, $to);
+    }
+
+    /**
+     * Unify query.
+     *
+     * @param string $api
+     * @param string $from
+     * @param string $to
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function query(string $api, string $from, string $to)
+    {
+        $params = [
+            'begin_date' => $from,
+            'end_date' => $to,
+        ];
+
+        return $this->httpPostJson($api, $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/DataCube/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/DataCube/ServiceProvider.php
new file mode 100644
index 0000000..258d7c4
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/DataCube/ServiceProvider.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\DataCube;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['data_cube'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Encryptor.php b/vendor/overtrue/wechat/src/MiniProgram/Encryptor.php
new file mode 100644
index 0000000..167a2a0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Encryptor.php
@@ -0,0 +1,50 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram;
+
+use EasyWeChat\Kernel\Encryptor as BaseEncryptor;
+use EasyWeChat\Kernel\Exceptions\DecryptException;
+use EasyWeChat\Kernel\Support\AES;
+
+/**
+ * Class Encryptor.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Encryptor extends BaseEncryptor
+{
+    /**
+     * Decrypt data.
+     *
+     * @param string $sessionKey
+     * @param string $iv
+     * @param string $encrypted
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\DecryptException
+     */
+    public function decryptData(string $sessionKey, string $iv, string $encrypted): array
+    {
+        $decrypted = AES::decrypt(
+            base64_decode($encrypted, false), base64_decode($sessionKey, false), base64_decode($iv, false)
+        );
+
+        $decrypted = json_decode($this->pkcs7Unpad($decrypted), true);
+
+        if (!$decrypted) {
+            throw new DecryptException('The given payload is invalid.');
+        }
+
+        return $decrypted;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Express/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Express/Client.php
new file mode 100644
index 0000000..a0311c8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Express/Client.php
@@ -0,0 +1,133 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Express;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author kehuanhuan <1152018701@qq.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function listProviders()
+    {
+        return $this->httpGet('cgi-bin/express/business/delivery/getall');
+    }
+
+    /**
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function createWaybill(array $params = [])
+    {
+        return $this->httpPostJson('cgi-bin/express/business/order/add', $params);
+    }
+
+    /**
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function deleteWaybill(array $params = [])
+    {
+        return $this->httpPostJson('cgi-bin/express/business/order/cancel', $params);
+    }
+
+    /**
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getWaybill(array $params = [])
+    {
+        return $this->httpPostJson('cgi-bin/express/business/order/get', $params);
+    }
+
+    /**
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getWaybillTrack(array $params = [])
+    {
+        return $this->httpPostJson('cgi-bin/express/business/path/get', $params);
+    }
+
+    /**
+     * @param string $deliveryId
+     * @param string $bizId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getBalance(string $deliveryId, string $bizId)
+    {
+        return $this->httpPostJson('cgi-bin/express/business/quota/get', [
+            'delivery_id' => $deliveryId,
+            'biz_id' => $bizId,
+        ]);
+    }
+
+    /**
+     * @param string $openid
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function bindPrinter(string $openid)
+    {
+        return $this->httpPostJson('cgi-bin/express/business/printer/update', [
+            'update_type' => 'bind',
+            'openid' => $openid,
+        ]);
+    }
+
+    /**
+     * @param string $openid
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function unbindPrinter(string $openid)
+    {
+        return $this->httpPostJson('cgi-bin/express/business/printer/update', [
+            'update_type' => 'unbind',
+            'openid' => $openid,
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Express/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Express/ServiceProvider.php
new file mode 100644
index 0000000..4794094
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Express/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Express;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author kehuanhuan <1152018701@qq.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['express'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/CartClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/CartClient.php
new file mode 100644
index 0000000..7a07e77
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/CartClient.php
@@ -0,0 +1,88 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Mall;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class CartClient extends BaseClient
+{
+    /**
+     * 导入收藏.
+     *
+     * @param array $params
+     * @param bool  $isTest
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function add($params, $isTest = false)
+    {
+        return $this->httpPostJson('mall/addshoppinglist', $params, ['is_test' => (int) $isTest]);
+    }
+
+    /**
+     * 查询用户收藏信息.
+     *
+     * @param array $params
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function query($params)
+    {
+        return $this->httpPostJson('mall/queryshoppinglist', $params, ['type' => 'batchquery']);
+    }
+
+    /**
+     * 查询用户收藏信息.
+     *
+     * @param array $params
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function queryByPage($params)
+    {
+        return $this->httpPostJson('mall/queryshoppinglist', $params, ['type' => 'getbypage']);
+    }
+
+    /**
+     * 删除收藏.
+     *
+     * @param string $openid
+     * @param array  $products
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete($openid, array $products = [])
+    {
+        if (empty($products)) {
+            return $this->httpPostJson('mall/deletebizallshoppinglist', ['user_open_id' => $openid]);
+        }
+
+        return $this->httpPostJson('mall/deleteshoppinglist', ['user_open_id' => $openid, 'sku_product_list' => $products]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/ForwardsMall.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/ForwardsMall.php
new file mode 100644
index 0000000..f727b17
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/ForwardsMall.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Mall;
+
+/**
+ * Class Application.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ *
+ * @property \EasyWeChat\MiniProgram\Mall\OrderClient   $order
+ * @property \EasyWeChat\MiniProgram\Mall\CartClient    $cart
+ * @property \EasyWeChat\MiniProgram\Mall\ProductClient $product
+ * @property \EasyWeChat\MiniProgram\Mall\MediaClient   $media
+ */
+class ForwardsMall
+{
+    /**
+     * @var \EasyWeChat\Kernel\ServiceContainer
+     */
+    protected $app;
+
+    /**
+     * @param \EasyWeChat\Kernel\ServiceContainer $app
+     */
+    public function __construct($app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * @param string $property
+     *
+     * @return mixed
+     */
+    public function __get($property)
+    {
+        return $this->app["mall.{$property}"];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/MediaClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/MediaClient.php
new file mode 100644
index 0000000..a3827bc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/MediaClient.php
@@ -0,0 +1,37 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Mall;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class MediaClient extends BaseClient
+{
+    /**
+     * 更新或导入媒体信息.
+     *
+     * @param array $params
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function import($params)
+    {
+        return $this->httpPostJson('mall/importmedia', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/OrderClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/OrderClient.php
new file mode 100644
index 0000000..9879695
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/OrderClient.php
@@ -0,0 +1,75 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Mall;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class OrderClient extends BaseClient
+{
+    /**
+     * 导入订单.
+     *
+     * @param array $params
+     * @param bool  $isHistory
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function add($params, $isHistory = false)
+    {
+        return $this->httpPostJson('mall/importorder', $params, ['action' => 'add-order', 'is_history' => (int) $isHistory]);
+    }
+
+    /**
+     * 导入订单.
+     *
+     * @param array $params
+     * @param bool  $isHistory
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update($params, $isHistory = false)
+    {
+        return $this->httpPostJson('mall/importorder', $params, ['action' => 'update-order', 'is_history' => (int) $isHistory]);
+    }
+
+    /**
+     * 删除订单.
+     *
+     * @param string $openid
+     * @param string $orderId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete($openid, $orderId)
+    {
+        $params = [
+            'user_open_id' => $openid,
+            'order_id' => $orderId,
+        ];
+
+        return $this->httpPostJson('mall/deleteorder', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/ProductClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/ProductClient.php
new file mode 100644
index 0000000..f7fbfd0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/ProductClient.php
@@ -0,0 +1,68 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Mall;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ProductClient extends BaseClient
+{
+    /**
+     * 更新或导入物品信息.
+     *
+     * @param array $params
+     * @param bool  $isTest
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function import($params, $isTest = false)
+    {
+        return $this->httpPostJson('mall/importproduct', $params, ['is_test' => (int) $isTest]);
+    }
+
+    /**
+     * 查询物品信息.
+     *
+     * @param array $params
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function query($params)
+    {
+        return $this->httpPostJson('mall/queryproduct', $params, ['type' => 'batchquery']);
+    }
+
+    /**
+     * 小程序的物品是否可被搜索.
+     *
+     * @param bool $value
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setSearchable($value)
+    {
+        return $this->httpPostJson('mall/brandmanage', ['can_be_search' => $value], ['action' => 'set_biz_can_be_search']);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/ServiceProvider.php
new file mode 100644
index 0000000..964395b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/ServiceProvider.php
@@ -0,0 +1,44 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Mall;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['mall'] = function ($app) {
+            return new ForwardsMall($app);
+        };
+
+        $app['mall.order'] = function ($app) {
+            return new OrderClient($app);
+        };
+
+        $app['mall.cart'] = function ($app) {
+            return new CartClient($app);
+        };
+
+        $app['mall.product'] = function ($app) {
+            return new ProductClient($app);
+        };
+
+        $app['mall.media'] = function ($app) {
+            return new MediaClient($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/Client.php b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/Client.php
new file mode 100644
index 0000000..da9ee05
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/Client.php
@@ -0,0 +1,123 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\NearbyPoi;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class Client.
+ *
+ * @author joyeekk <xygao2420@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Add nearby poi.
+     *
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function add(array $params)
+    {
+        $params = array_merge([
+            'is_comm_nearby' => '1',
+            'poi_id' => '',
+        ], $params);
+
+        return $this->httpPostJson('wxa/addnearbypoi', $params);
+    }
+
+    /**
+     * Update nearby poi.
+     *
+     * @param string $poiId
+     * @param array  $params
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(string $poiId, array $params)
+    {
+        $params = array_merge([
+            'is_comm_nearby' => '1',
+            'poi_id' => $poiId,
+        ], $params);
+
+        return $this->httpPostJson('wxa/addnearbypoi', $params);
+    }
+
+    /**
+     * Delete nearby poi.
+     *
+     * @param string $poiId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete(string $poiId)
+    {
+        return $this->httpPostJson('wxa/delnearbypoi', [
+            'poi_id' => $poiId,
+        ]);
+    }
+
+    /**
+     * Get nearby poi list.
+     *
+     * @param int $page
+     * @param int $pageRows
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function list(int $page, int $pageRows)
+    {
+        return $this->httpGet('wxa/getnearbypoilist', [
+            'page' => $page,
+            'page_rows' => $pageRows,
+        ]);
+    }
+
+    /**
+     * Set nearby poi show status.
+     *
+     * @param string $poiId
+     * @param int    $status
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setVisibility(string $poiId, int $status)
+    {
+        if (!in_array($status, [0, 1], true)) {
+            throw new InvalidArgumentException('status should be 0 or 1.');
+        }
+
+        return $this->httpPostJson('wxa/setnearbypoishowstatus', [
+            'poi_id' => $poiId,
+            'status' => $status,
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/ServiceProvider.php
new file mode 100644
index 0000000..cb15bb3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\NearbyPoi;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author joyeekk <xygao2420@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['nearby_poi'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/OCR/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/OCR/ServiceProvider.php
new file mode 100644
index 0000000..80e0001
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/OCR/ServiceProvider.php
@@ -0,0 +1,34 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\OCR;
+
+use EasyWeChat\OfficialAccount\OCR\Client;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author joyeekk <xygao2420@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['ocr'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/OpenData/Client.php b/vendor/overtrue/wechat/src/MiniProgram/OpenData/Client.php
new file mode 100644
index 0000000..c9472c6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/OpenData/Client.php
@@ -0,0 +1,96 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\OpenData;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author tianyong90 <412039588@qq.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @var string
+     */
+    protected $baseUri = 'https://api.weixin.qq.com/wxa/';
+
+    /**
+     * removeUserStorage.
+     *
+     * @param string $openid
+     * @param string $sessionKey
+     * @param array  $key
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function removeUserStorage(string $openid, string $sessionKey, array $key)
+    {
+        $data = ['key' => $key];
+        $query = [
+            'openid' => $openid,
+            'sig_method' => 'hmac_sha256',
+            'signature' => hash_hmac('sha256', json_encode($data), $sessionKey),
+        ];
+
+        return $this->httpPostJson('remove_user_storage', $data, $query);
+    }
+
+    /**
+     * setUserStorage.
+     *
+     * @param string $openid
+     * @param string $sessionKey
+     * @param array  $kvList
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setUserStorage(string $openid, string $sessionKey, array $kvList)
+    {
+        $kvList = $this->formatKVLists($kvList);
+
+        $data = ['kv_list' => $kvList];
+        $query = [
+            'openid' => $openid,
+            'sig_method' => 'hmac_sha256',
+            'signature' => hash_hmac('sha256', json_encode($data), $sessionKey),
+        ];
+
+        return $this->httpPostJson('set_user_storage', $data, $query);
+    }
+
+    /**
+     * @param array $params
+     *
+     * @return array
+     */
+    protected function formatKVLists(array $params)
+    {
+        $formatted = [];
+
+        foreach ($params as $name => $value) {
+            $formatted[] = [
+                'key' => $name,
+                'value' => strval($value),
+            ];
+        }
+
+        return $formatted;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/OpenData/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/OpenData/ServiceProvider.php
new file mode 100644
index 0000000..3ea1287
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/OpenData/ServiceProvider.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\OpenData;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['open_data'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Plugin/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Plugin/Client.php
new file mode 100644
index 0000000..cd885cc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Plugin/Client.php
@@ -0,0 +1,67 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Plugin;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @param string $appId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function apply($appId)
+    {
+        return $this->httpPostJson('wxa/plugin', [
+            'action' => 'apply',
+            'plugin_appid' => $appId,
+        ]);
+    }
+
+    /**
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list()
+    {
+        return $this->httpPostJson('wxa/plugin', [
+            'action' => 'list',
+        ]);
+    }
+
+    /**
+     * @param string $appId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function unbind($appId)
+    {
+        return $this->httpPostJson('wxa/plugin', [
+            'action' => 'unbind',
+            'plugin_appid' => $appId,
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Plugin/DevClient.php b/vendor/overtrue/wechat/src/MiniProgram/Plugin/DevClient.php
new file mode 100644
index 0000000..3f25edc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Plugin/DevClient.php
@@ -0,0 +1,93 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Plugin;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class DevClient.
+ *
+ * @author her-cat <i@her-cat.com>
+ */
+class DevClient extends BaseClient
+{
+    /**
+     * Get users.
+     *
+     * @param int $page
+     * @param int $size
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getUsers(int $page = 1, int $size = 10)
+    {
+        return $this->httpPostJson('wxa/devplugin', [
+            'action' => 'dev_apply_list',
+            'page' => $page,
+            'num' => $size,
+        ]);
+    }
+
+    /**
+     * Agree to use plugin.
+     *
+     * @param string $appId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function agree(string $appId)
+    {
+        return $this->httpPostJson('wxa/devplugin', [
+            'action' => 'dev_agree',
+            'appid' => $appId,
+        ]);
+    }
+
+    /**
+     * Refuse to use plugin.
+     *
+     * @param string $reason
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function refuse(string $reason)
+    {
+        return $this->httpPostJson('wxa/devplugin', [
+            'action' => 'dev_refuse',
+            'reason' => $reason,
+        ]);
+    }
+
+    /**
+     * Delete rejected applications.
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete()
+    {
+        return $this->httpPostJson('wxa/devplugin', [
+            'action' => 'dev_delete',
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Plugin/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Plugin/ServiceProvider.php
new file mode 100644
index 0000000..097c77e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Plugin/ServiceProvider.php
@@ -0,0 +1,42 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Plugin;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * Registers services on the given container.
+     *
+     * This method should only be used to configure services and parameters.
+     * It should not get services.
+     *
+     * @param \Pimple\Container $app
+     */
+    public function register(Container $app)
+    {
+        $app['plugin'] = function ($app) {
+            return new Client($app);
+        };
+
+        $app['plugin_dev'] = function ($app) {
+            return new DevClient($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Server/ServiceProvider.php
new file mode 100644
index 0000000..0586bb9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Server/ServiceProvider.php
@@ -0,0 +1,42 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Server;
+
+use EasyWeChat\MiniProgram\Encryptor;
+use EasyWeChat\OfficialAccount\Server\Guard;
+use EasyWeChat\OfficialAccount\Server\Handlers\EchoStrHandler;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        !isset($app['encryptor']) && $app['encryptor'] = function ($app) {
+            return new Encryptor(
+                $app['config']['app_id'],
+                $app['config']['token'],
+                $app['config']['aes_key']
+            );
+        };
+
+        !isset($app['server']) && $app['server'] = function ($app) {
+            $guard = new Guard($app);
+            $guard->push(new EchoStrHandler($app));
+
+            return $guard;
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Soter/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Soter/Client.php
new file mode 100644
index 0000000..cdf66d7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Soter/Client.php
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Soter;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author her-cat <hxhsoft@foxmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @param string $openid
+     * @param string $json
+     * @param string $signature
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function verifySignature(string $openid, string $json, string $signature)
+    {
+        return $this->httpPostJson('cgi-bin/soter/verify_signature', [
+            'openid' => $openid,
+            'json_string' => $json,
+            'json_signature' => $signature,
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Soter/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Soter/ServiceProvider.php
new file mode 100644
index 0000000..c8520db
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Soter/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Soter;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author her-cat <hxhsoft@foxmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function register(Container $app)
+    {
+        $app['soter'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/Client.php
new file mode 100644
index 0000000..8669614
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/Client.php
@@ -0,0 +1,112 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\SubscribeMessage;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use ReflectionClass;
+
+/**
+ * Class Client.
+ *
+ * @author hugo <rabbitzhang52@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * {@inheritdoc}.
+     */
+    protected $message = [
+        'touser' => '',
+        'template_id' => '',
+        'page' => '',
+        'data' => [],
+    ];
+
+    /**
+     * {@inheritdoc}.
+     */
+    protected $required = ['touser', 'template_id', 'data'];
+
+    /**
+     * Send a template message.
+     *
+     * @param array $data
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function send(array $data = [])
+    {
+        $params = $this->formatMessage($data);
+
+        $this->restoreMessage();
+
+        return $this->httpPostJson('cgi-bin/message/subscribe/send', $params);
+    }
+
+    /**
+     * @param array $data
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    protected function formatMessage(array $data = [])
+    {
+        $params = array_merge($this->message, $data);
+
+        foreach ($params as $key => $value) {
+            if (in_array($key, $this->required, true) && empty($value) && empty($this->message[$key])) {
+                throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key));
+            }
+
+            $params[$key] = empty($value) ? $this->message[$key] : $value;
+        }
+
+        foreach ($params['data'] as $key => $value) {
+            if (is_array($value)) {
+                if (isset($value['value'])) {
+                    $params['data'][$key] = ['value' => $value['value']];
+
+                    continue;
+                }
+
+                if (count($value) >= 1) {
+                    $value = [
+                        'value' => $value[0],
+//                        'color' => $value[1],// color unsupported
+                    ];
+                }
+            } else {
+                $value = [
+                    'value' => strval($value),
+                ];
+            }
+
+            $params['data'][$key] = $value;
+        }
+
+        return $params;
+    }
+
+    /**
+     * Restore message.
+     */
+    protected function restoreMessage()
+    {
+        $this->message = (new ReflectionClass(static::class))->getDefaultProperties()['message'];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/ServiceProvider.php
new file mode 100644
index 0000000..726b3ed
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/ServiceProvider.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\SubscribeMessage;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['subscribe_message'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/Client.php
new file mode 100644
index 0000000..08f8a3b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/Client.php
@@ -0,0 +1,114 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\TemplateMessage;
+
+use EasyWeChat\OfficialAccount\TemplateMessage\Client as BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    const API_SEND = 'cgi-bin/message/wxopen/template/send';
+
+    /**
+     * {@inheritdoc}.
+     */
+    protected $message = [
+        'touser' => '',
+        'template_id' => '',
+        'page' => '',
+        'form_id' => '',
+        'data' => [],
+        'emphasis_keyword' => '',
+    ];
+
+    /**
+     * {@inheritdoc}.
+     */
+    protected $required = ['touser', 'template_id', 'form_id'];
+
+    /**
+     * @param int $offset
+     * @param int $count
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list(int $offset, int $count)
+    {
+        return $this->httpPostJson('cgi-bin/wxopen/template/library/list', compact('offset', 'count'));
+    }
+
+    /**
+     * @param string $id
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(string $id)
+    {
+        return $this->httpPostJson('cgi-bin/wxopen/template/library/get', compact('id'));
+    }
+
+    /**
+     * @param string $id
+     * @param array  $keyword
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function add(string $id, array $keyword)
+    {
+        return $this->httpPostJson('cgi-bin/wxopen/template/add', [
+            'id' => $id,
+            'keyword_id_list' => $keyword,
+        ]);
+    }
+
+    /**
+     * @param string $templateId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete(string $templateId)
+    {
+        return $this->httpPostJson('cgi-bin/wxopen/template/del', [
+            'template_id' => $templateId,
+        ]);
+    }
+
+    /**
+     * @param int $offset
+     * @param int $count
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getTemplates(int $offset, int $count)
+    {
+        return $this->httpPostJson('cgi-bin/wxopen/template/list', compact('offset', 'count'));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/ServiceProvider.php
new file mode 100644
index 0000000..776a15e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/ServiceProvider.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\TemplateMessage;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['template_message'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/Client.php
new file mode 100644
index 0000000..6acb13e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/Client.php
@@ -0,0 +1,146 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\UniformMessage;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\OfficialAccount\TemplateMessage\Client as BaseClient;
+
+class Client extends BaseClient
+{
+    const API_SEND = 'cgi-bin/message/wxopen/template/uniform_send';
+
+    /**
+     * {@inheritdoc}.
+     *
+     * @var array
+     */
+    protected $message = [
+        'touser' => '',
+    ];
+
+    /**
+     * Weapp Attributes.
+     *
+     * @var array
+     */
+    protected $weappMessage = [
+        'template_id' => '',
+        'page' => '',
+        'form_id' => '',
+        'data' => [],
+        'emphasis_keyword' => '',
+    ];
+
+    /**
+     * Official account attributes.
+     *
+     * @var array
+     */
+    protected $mpMessage = [
+        'appid' => '',
+        'template_id' => '',
+        'url' => '',
+        'miniprogram' => [],
+        'data' => [],
+    ];
+
+    /**
+     * Required attributes.
+     *
+     * @var array
+     */
+    protected $required = ['touser', 'template_id', 'form_id', 'miniprogram', 'appid'];
+
+    /**
+     * @param array $data
+     *
+     * @return array
+     *
+     * @throws InvalidArgumentException
+     */
+    protected function formatMessage(array $data = [])
+    {
+        $params = array_merge($this->message, $data);
+
+        if (empty($params['touser'])) {
+            throw new InvalidArgumentException(sprintf('Attribute "touser" can not be empty!'));
+        }
+
+        if (!empty($params['weapp_template_msg'])) {
+            $params['weapp_template_msg'] = $this->formatWeappMessage($params['weapp_template_msg']);
+        }
+
+        if (!empty($params['mp_template_msg'])) {
+            $params['mp_template_msg'] = $this->formatMpMessage($params['mp_template_msg']);
+        }
+
+        return $params;
+    }
+
+    /**
+     * @param array $data
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    protected function formatWeappMessage(array $data = [])
+    {
+        $params = $this->baseFormat($data, $this->weappMessage);
+
+        $params['data'] = $this->formatData($params['data'] ?? []);
+
+        return $params;
+    }
+
+    /**
+     * @param array $data
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    protected function formatMpMessage(array $data = [])
+    {
+        $params = $this->baseFormat($data, $this->mpMessage);
+
+        if (empty($params['miniprogram']['appid'])) {
+            $params['miniprogram']['appid'] = $this->app['config']['app_id'];
+        }
+
+        $params['data'] = $this->formatData($params['data'] ?? []);
+
+        return $params;
+    }
+
+    /**
+     * @param array $data
+     * @param array $default
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    protected function baseFormat($data = [], $default = [])
+    {
+        $params = array_merge($default, $data);
+        foreach ($params as $key => $value) {
+            if (in_array($key, $this->required, true) && empty($value) && empty($default[$key])) {
+                throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key));
+            }
+
+            $params[$key] = empty($value) ? $default[$key] : $value;
+        }
+
+        return $params;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/ServiceProvider.php
new file mode 100644
index 0000000..0e86db3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/ServiceProvider.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\UniformMessage;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['uniform_message'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Application.php b/vendor/overtrue/wechat/src/OfficialAccount/Application.php
new file mode 100644
index 0000000..745363f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Application.php
@@ -0,0 +1,88 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount;
+
+use EasyWeChat\BasicService;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Class Application.
+ *
+ * @author overtrue <i@overtrue.me>
+ *
+ * @property \EasyWeChat\BasicService\Media\Client                     $media
+ * @property \EasyWeChat\BasicService\Url\Client                       $url
+ * @property \EasyWeChat\BasicService\QrCode\Client                    $qrcode
+ * @property \EasyWeChat\BasicService\Jssdk\Client                     $jssdk
+ * @property \EasyWeChat\OfficialAccount\Auth\AccessToken              $access_token
+ * @property \EasyWeChat\OfficialAccount\Server\Guard                  $server
+ * @property \EasyWeChat\OfficialAccount\User\UserClient               $user
+ * @property \EasyWeChat\OfficialAccount\User\TagClient                $user_tag
+ * @property \EasyWeChat\OfficialAccount\Menu\Client                   $menu
+ * @property \EasyWeChat\OfficialAccount\TemplateMessage\Client        $template_message
+ * @property \EasyWeChat\OfficialAccount\Material\Client               $material
+ * @property \EasyWeChat\OfficialAccount\CustomerService\Client        $customer_service
+ * @property \EasyWeChat\OfficialAccount\CustomerService\SessionClient $customer_service_session
+ * @property \EasyWeChat\OfficialAccount\Semantic\Client               $semantic
+ * @property \EasyWeChat\OfficialAccount\DataCube\Client               $data_cube
+ * @property \EasyWeChat\OfficialAccount\AutoReply\Client              $auto_reply
+ * @property \EasyWeChat\OfficialAccount\Broadcasting\Client           $broadcasting
+ * @property \EasyWeChat\OfficialAccount\Card\Card                     $card
+ * @property \EasyWeChat\OfficialAccount\Device\Client                 $device
+ * @property \EasyWeChat\OfficialAccount\ShakeAround\ShakeAround       $shake_around
+ * @property \EasyWeChat\OfficialAccount\POI\Client                    $poi
+ * @property \EasyWeChat\OfficialAccount\Store\Client                  $store
+ * @property \EasyWeChat\OfficialAccount\Base\Client                   $base
+ * @property \EasyWeChat\OfficialAccount\Comment\Client                $comment
+ * @property \EasyWeChat\OfficialAccount\OCR\Client                    $ocr
+ * @property \EasyWeChat\OfficialAccount\Goods\Client                  $goods
+ * @property \Overtrue\Socialite\Providers\WeChatProvider              $oauth
+ * @property \EasyWeChat\OfficialAccount\WiFi\Client                   $wifi
+ * @property \EasyWeChat\OfficialAccount\WiFi\CardClient               $wifi_card
+ * @property \EasyWeChat\OfficialAccount\WiFi\DeviceClient             $wifi_device
+ * @property \EasyWeChat\OfficialAccount\WiFi\ShopClient               $wifi_shop
+ */
+class Application extends ServiceContainer
+{
+    /**
+     * @var array
+     */
+    protected $providers = [
+        Auth\ServiceProvider::class,
+        Server\ServiceProvider::class,
+        User\ServiceProvider::class,
+        OAuth\ServiceProvider::class,
+        Menu\ServiceProvider::class,
+        TemplateMessage\ServiceProvider::class,
+        Material\ServiceProvider::class,
+        CustomerService\ServiceProvider::class,
+        Semantic\ServiceProvider::class,
+        DataCube\ServiceProvider::class,
+        POI\ServiceProvider::class,
+        AutoReply\ServiceProvider::class,
+        Broadcasting\ServiceProvider::class,
+        Card\ServiceProvider::class,
+        Device\ServiceProvider::class,
+        ShakeAround\ServiceProvider::class,
+        Store\ServiceProvider::class,
+        Comment\ServiceProvider::class,
+        Base\ServiceProvider::class,
+        OCR\ServiceProvider::class,
+        Goods\ServiceProvider::class,
+        WiFi\ServiceProvider::class,
+        // Base services
+        BasicService\QrCode\ServiceProvider::class,
+        BasicService\Media\ServiceProvider::class,
+        BasicService\Url\ServiceProvider::class,
+        BasicService\Jssdk\ServiceProvider::class,
+    ];
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OfficialAccount/Auth/AccessToken.php
new file mode 100644
index 0000000..aca1aca
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Auth/AccessToken.php
@@ -0,0 +1,39 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Auth;
+
+use EasyWeChat\Kernel\AccessToken as BaseAccessToken;
+
+/**
+ * Class AuthorizerAccessToken.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class AccessToken extends BaseAccessToken
+{
+    /**
+     * @var string
+     */
+    protected $endpointToGetToken = 'https://api.weixin.qq.com/cgi-bin/token';
+
+    /**
+     * @return array
+     */
+    protected function getCredentials(): array
+    {
+        return [
+            'grant_type' => 'client_credential',
+            'appid' => $this->app['config']['app_id'],
+            'secret' => $this->app['config']['secret'],
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Auth/ServiceProvider.php
new file mode 100644
index 0000000..e748730
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Auth/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Auth;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        !isset($app['access_token']) && $app['access_token'] = function ($app) {
+            return new AccessToken($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/Client.php
new file mode 100644
index 0000000..6d2e4d6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/Client.php
@@ -0,0 +1,34 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\AutoReply;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get current auto reply settings.
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function current()
+    {
+        return $this->httpGet('cgi-bin/get_current_autoreply_info');
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/ServiceProvider.php
new file mode 100644
index 0000000..4377550
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\AutoReply;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['auto_reply'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Base/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Base/Client.php
new file mode 100644
index 0000000..ce8abc5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Base/Client.php
@@ -0,0 +1,84 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Base;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Clear quota.
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function clearQuota()
+    {
+        $params = [
+            'appid' => $this->app['config']['app_id'],
+        ];
+
+        return $this->httpPostJson('cgi-bin/clear_quota', $params);
+    }
+
+    /**
+     * Get wechat callback ip.
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function getValidIps()
+    {
+        return $this->httpGet('cgi-bin/getcallbackip');
+    }
+
+    /**
+     * Check the callback address network.
+     *
+     * @param string $action
+     * @param string $operator
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function checkCallbackUrl(string $action = 'all', string $operator = 'DEFAULT')
+    {
+        if (!in_array($action, ['dns', 'ping', 'all'], true)) {
+            throw new InvalidArgumentException('The action must be dns, ping, all.');
+        }
+
+        $operator = strtoupper($operator);
+
+        if (!in_array($operator, ['CHINANET', 'UNICOM', 'CAP', 'DEFAULT'], true)) {
+            throw new InvalidArgumentException('The operator must be CHINANET, UNICOM, CAP, DEFAULT.');
+        }
+
+        $params = [
+            'action' => $action,
+            'check_operator' => $operator,
+        ];
+
+        return $this->httpPostJson('cgi-bin/callback/check', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Base/ServiceProvider.php
new file mode 100644
index 0000000..409593d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Base/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Base;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['base'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/Client.php
new file mode 100644
index 0000000..6c2eee1
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/Client.php
@@ -0,0 +1,383 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Broadcasting;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Contracts\MessageInterface;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Messages\Card;
+use EasyWeChat\Kernel\Messages\Image;
+use EasyWeChat\Kernel\Messages\Media;
+use EasyWeChat\Kernel\Messages\Text;
+use EasyWeChat\Kernel\Support\Arr;
+
+/**
+ * Class Client.
+ *
+ * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewTextByName($text, $name);
+ * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewNewsByName($mediaId, $name);
+ * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewVoiceByName($mediaId, $name);
+ * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewImageByName($mediaId, $name);
+ * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewVideoByName($message, $name);
+ * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewCardByName($cardId, $name);
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    const PREVIEW_BY_OPENID = 'touser';
+    const PREVIEW_BY_NAME = 'towxname';
+
+    /**
+     * Send a message.
+     *
+     * @param array $message
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function send(array $message)
+    {
+        if (empty($message['filter']) && empty($message['touser'])) {
+            throw new RuntimeException('The message reception object is not specified');
+        }
+
+        $api = Arr::get($message, 'touser') ? 'cgi-bin/message/mass/send' : 'cgi-bin/message/mass/sendall';
+
+        return $this->httpPostJson($api, $message);
+    }
+
+    /**
+     * Preview a message.
+     *
+     * @param array $message
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function preview(array $message)
+    {
+        return $this->httpPostJson('cgi-bin/message/mass/preview', $message);
+    }
+
+    /**
+     * Delete a broadcast.
+     *
+     * @param string $msgId
+     * @param int    $index
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete(string $msgId, int $index = 0)
+    {
+        $options = [
+            'msg_id' => $msgId,
+            'article_idx' => $index,
+        ];
+
+        return $this->httpPostJson('cgi-bin/message/mass/delete', $options);
+    }
+
+    /**
+     * Get a broadcast status.
+     *
+     * @param string $msgId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function status(string $msgId)
+    {
+        $options = [
+            'msg_id' => $msgId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/message/mass/get', $options);
+    }
+
+    /**
+     * Send a text message.
+     *
+     * @param string $message
+     * @param mixed  $reception
+     * @param array  $attributes
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function sendText(string $message, $reception = null, array $attributes = [])
+    {
+        return $this->sendMessage(new Text($message), $reception, $attributes);
+    }
+
+    /**
+     * Send a news message.
+     *
+     * @param string $mediaId
+     * @param mixed  $reception
+     * @param array  $attributes
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function sendNews(string $mediaId, $reception = null, array $attributes = [])
+    {
+        return $this->sendMessage(new Media($mediaId, 'mpnews'), $reception, $attributes);
+    }
+
+    /**
+     * Send a voice message.
+     *
+     * @param string $mediaId
+     * @param mixed  $reception
+     * @param array  $attributes
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function sendVoice(string $mediaId, $reception = null, array $attributes = [])
+    {
+        return $this->sendMessage(new Media($mediaId, 'voice'), $reception, $attributes);
+    }
+
+    /**
+     * Send a image message.
+     *
+     * @param string $mediaId
+     * @param mixed  $reception
+     * @param array  $attributes
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function sendImage(string $mediaId, $reception = null, array $attributes = [])
+    {
+        return $this->sendMessage(new Image($mediaId), $reception, $attributes);
+    }
+
+    /**
+     * Send a video message.
+     *
+     * @param string $mediaId
+     * @param mixed  $reception
+     * @param array  $attributes
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function sendVideo(string $mediaId, $reception = null, array $attributes = [])
+    {
+        return $this->sendMessage(new Media($mediaId, 'mpvideo'), $reception, $attributes);
+    }
+
+    /**
+     * Send a card message.
+     *
+     * @param string $cardId
+     * @param mixed  $reception
+     * @param array  $attributes
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function sendCard(string $cardId, $reception = null, array $attributes = [])
+    {
+        return $this->sendMessage(new Card($cardId), $reception, $attributes);
+    }
+
+    /**
+     * Preview a text message.
+     *
+     * @param string $message   message
+     * @param string $reception
+     * @param string $method
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function previewText(string $message, $reception, $method = self::PREVIEW_BY_OPENID)
+    {
+        return $this->previewMessage(new Text($message), $reception, $method);
+    }
+
+    /**
+     * Preview a news message.
+     *
+     * @param string $mediaId   message
+     * @param string $reception
+     * @param string $method
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function previewNews(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID)
+    {
+        return $this->previewMessage(new Media($mediaId, 'mpnews'), $reception, $method);
+    }
+
+    /**
+     * Preview a voice message.
+     *
+     * @param string $mediaId   message
+     * @param string $reception
+     * @param string $method
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function previewVoice(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID)
+    {
+        return $this->previewMessage(new Media($mediaId, 'voice'), $reception, $method);
+    }
+
+    /**
+     * Preview a image message.
+     *
+     * @param string $mediaId   message
+     * @param string $reception
+     * @param string $method
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function previewImage(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID)
+    {
+        return $this->previewMessage(new Image($mediaId), $reception, $method);
+    }
+
+    /**
+     * Preview a video message.
+     *
+     * @param string $mediaId   message
+     * @param string $reception
+     * @param string $method
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function previewVideo(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID)
+    {
+        return $this->previewMessage(new Media($mediaId, 'mpvideo'), $reception, $method);
+    }
+
+    /**
+     * Preview a card message.
+     *
+     * @param string $cardId    message
+     * @param string $reception
+     * @param string $method
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function previewCard(string $cardId, $reception, $method = self::PREVIEW_BY_OPENID)
+    {
+        return $this->previewMessage(new Card($cardId), $reception, $method);
+    }
+
+    /**
+     * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message
+     * @param string                                        $reception
+     * @param string                                        $method
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function previewMessage(MessageInterface $message, string $reception, $method = self::PREVIEW_BY_OPENID)
+    {
+        $message = (new MessageBuilder())->message($message)->buildForPreview($method, $reception);
+
+        return $this->preview($message);
+    }
+
+    /**
+     * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message
+     * @param mixed                                         $reception
+     * @param array                                         $attributes
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function sendMessage(MessageInterface $message, $reception = null, $attributes = [])
+    {
+        $message = (new MessageBuilder())->message($message)->with($attributes)->toAll();
+
+        if (\is_int($reception)) {
+            $message->toTag($reception);
+        } elseif (\is_array($reception)) {
+            $message->toUsers($reception);
+        }
+
+        return $this->send($message->build());
+    }
+
+    /**
+     * @codeCoverageIgnore
+     *
+     * @param string $method
+     * @param array  $args
+     *
+     * @return mixed
+     */
+    public function __call($method, $args)
+    {
+        if (strpos($method, 'ByName') > 0) {
+            $method = strstr($method, 'ByName', true);
+
+            if (method_exists($this, $method)) {
+                array_push($args, self::PREVIEW_BY_NAME);
+
+                return $this->$method(...$args);
+            }
+        }
+
+        throw new \BadMethodCallException(sprintf('Method %s not exists.', $method));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/MessageBuilder.php b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/MessageBuilder.php
new file mode 100644
index 0000000..70465b0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/MessageBuilder.php
@@ -0,0 +1,162 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Broadcasting;
+
+use EasyWeChat\Kernel\Contracts\MessageInterface;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+
+/**
+ * Class MessageBuilder.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class MessageBuilder
+{
+    /**
+     * @var array
+     */
+    protected $to = [];
+
+    /**
+     * @var \EasyWeChat\Kernel\Contracts\MessageInterface
+     */
+    protected $message;
+
+    /**
+     * @var array
+     */
+    protected $attributes = [];
+
+    /**
+     * Set message.
+     *
+     * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message
+     *
+     * @return $this
+     */
+    public function message(MessageInterface $message)
+    {
+        $this->message = $message;
+
+        return $this;
+    }
+
+    /**
+     * Set target user or group.
+     *
+     * @param array $to
+     *
+     * @return $this
+     */
+    public function to(array $to)
+    {
+        $this->to = $to;
+
+        return $this;
+    }
+
+    /**
+     * @param int $tagId
+     *
+     * @return \EasyWeChat\OfficialAccount\Broadcasting\MessageBuilder
+     */
+    public function toTag(int $tagId)
+    {
+        $this->to([
+            'filter' => [
+                'is_to_all' => false,
+                'tag_id' => $tagId,
+            ],
+        ]);
+
+        return $this;
+    }
+
+    /**
+     * @param array $openids
+     *
+     * @return \EasyWeChat\OfficialAccount\Broadcasting\MessageBuilder
+     */
+    public function toUsers(array $openids)
+    {
+        $this->to([
+            'touser' => $openids,
+        ]);
+
+        return $this;
+    }
+
+    /**
+     * @return $this
+     */
+    public function toAll()
+    {
+        $this->to([
+            'filter' => ['is_to_all' => true],
+        ]);
+
+        return $this;
+    }
+
+    /**
+     * @param array $attributes
+     *
+     * @return \EasyWeChat\OfficialAccount\Broadcasting\MessageBuilder
+     */
+    public function with(array $attributes)
+    {
+        $this->attributes = $attributes;
+
+        return $this;
+    }
+
+    /**
+     * Build message.
+     *
+     * @param array $prepends
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function build(array $prepends = []): array
+    {
+        if (empty($this->message)) {
+            throw new RuntimeException('No message content to send.');
+        }
+
+        $content = $this->message->transformForJsonRequest();
+
+        if (empty($prepends)) {
+            $prepends = $this->to;
+        }
+
+        $message = array_merge($prepends, $content, $this->attributes);
+
+        return $message;
+    }
+
+    /**
+     * Build preview message.
+     *
+     * @param string $by
+     * @param string $user
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function buildForPreview(string $by, string $user): array
+    {
+        return $this->build([$by => $user]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/ServiceProvider.php
new file mode 100644
index 0000000..1f956cf
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Broadcasting;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['broadcasting'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/BoardingPassClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/BoardingPassClient.php
new file mode 100644
index 0000000..b1575ab
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/BoardingPassClient.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+/**
+ * Class BoardingPassClient.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class BoardingPassClient extends Client
+{
+    /**
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function checkin(array $params)
+    {
+        return $this->httpPostJson('card/boardingpass/checkin', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/Card.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/Card.php
new file mode 100644
index 0000000..9950944
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/Card.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class Card.
+ *
+ * @author overtrue <i@overtrue.me>
+ *
+ * @property \EasyWeChat\OfficialAccount\Card\CodeClient          $code
+ * @property \EasyWeChat\OfficialAccount\Card\MeetingTicketClient $meeting_ticket
+ * @property \EasyWeChat\OfficialAccount\Card\MemberCardClient    $member_card
+ * @property \EasyWeChat\OfficialAccount\Card\GeneralCardClient   $general_card
+ * @property \EasyWeChat\OfficialAccount\Card\MovieTicketClient   $movie_ticket
+ * @property \EasyWeChat\OfficialAccount\Card\CoinClient          $coin
+ * @property \EasyWeChat\OfficialAccount\Card\SubMerchantClient   $sub_merchant
+ * @property \EasyWeChat\OfficialAccount\Card\BoardingPassClient  $boarding_pass
+ * @property \EasyWeChat\OfficialAccount\Card\JssdkClient         $jssdk
+ */
+class Card extends Client
+{
+    /**
+     * @param string $property
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function __get($property)
+    {
+        if (isset($this->app["card.{$property}"])) {
+            return $this->app["card.{$property}"];
+        }
+
+        throw new InvalidArgumentException(sprintf('No card service named "%s".', $property));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/Client.php
new file mode 100644
index 0000000..5ff122f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/Client.php
@@ -0,0 +1,405 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Traits\InteractsWithCache;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    use InteractsWithCache;
+
+    /**
+     * @var string
+     */
+    protected $url;
+
+    /**
+     * Ticket cache key.
+     *
+     * @var string
+     */
+    protected $ticketCacheKey;
+
+    /**
+     * Ticket cache prefix.
+     *
+     * @var string
+     */
+    protected $ticketCachePrefix = 'easywechat.official_account.card.api_ticket.';
+
+    /**
+     * 获取卡券颜色.
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function colors()
+    {
+        return $this->httpGet('card/getcolors');
+    }
+
+    /**
+     * 卡券开放类目查询接口.
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function categories()
+    {
+        return $this->httpGet('card/getapplyprotocol');
+    }
+
+    /**
+     * 创建卡券.
+     *
+     * @param string $cardType
+     * @param array  $attributes
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create($cardType = 'member_card', array $attributes)
+    {
+        $params = [
+            'card' => [
+                'card_type' => strtoupper($cardType),
+                strtolower($cardType) => $attributes,
+            ],
+        ];
+
+        return $this->httpPostJson('card/create', $params);
+    }
+
+    /**
+     * 查看卡券详情.
+     *
+     * @param string $cardId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get($cardId)
+    {
+        $params = [
+            'card_id' => $cardId,
+        ];
+
+        return $this->httpPostJson('card/get', $params);
+    }
+
+    /**
+     * 批量查询卡列表.
+     *
+     * @param int    $offset
+     * @param int    $count
+     * @param string $statusList
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list($offset = 0, $count = 10, $statusList = 'CARD_STATUS_VERIFY_OK')
+    {
+        $params = [
+            'offset' => $offset,
+            'count' => $count,
+            'status_list' => $statusList,
+        ];
+
+        return $this->httpPostJson('card/batchget', $params);
+    }
+
+    /**
+     * 更改卡券信息接口 and 设置跟随推荐接口.
+     *
+     * @param string $cardId
+     * @param string $type
+     * @param array  $attributes
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update($cardId, $type, array $attributes = [])
+    {
+        $card = [];
+        $card['card_id'] = $cardId;
+        $card[strtolower($type)] = $attributes;
+
+        return $this->httpPostJson('card/update', $card);
+    }
+
+    /**
+     * 删除卡券接口.
+     *
+     * @param string $cardId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete($cardId)
+    {
+        $params = [
+            'card_id' => $cardId,
+        ];
+
+        return $this->httpPostJson('card/delete', $params);
+    }
+
+    /**
+     * 创建二维码.
+     *
+     * @param array $cards
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function createQrCode(array $cards)
+    {
+        return $this->httpPostJson('card/qrcode/create', $cards);
+    }
+
+    /**
+     * ticket 换取二维码图片.
+     *
+     * @param string $ticket
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getQrCode($ticket)
+    {
+        $baseUri = 'https://mp.weixin.qq.com/cgi-bin/showqrcode';
+        $params = [
+            'ticket' => $ticket,
+        ];
+
+        $response = $this->requestRaw($baseUri, 'GET', $params);
+
+        return [
+            'status' => $response->getStatusCode(),
+            'reason' => $response->getReasonPhrase(),
+            'headers' => $response->getHeaders(),
+            'body' => strval($response->getBody()),
+            'url' => $baseUri.'?'.http_build_query($params),
+        ];
+    }
+
+    /**
+     * 通过ticket换取二维码 链接.
+     *
+     * @param string $ticket
+     *
+     * @return string
+     */
+    public function getQrCodeUrl($ticket)
+    {
+        return sprintf('https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s', $ticket);
+    }
+
+    /**
+     * 创建货架接口.
+     *
+     * @param string $banner
+     * @param string $pageTitle
+     * @param bool   $canShare
+     * @param string $scene     [SCENE_NEAR_BY 附近,SCENE_MENU 自定义菜单,SCENE_QRCODE 二维码,SCENE_ARTICLE 公众号文章,
+     *                          SCENE_H5 h5页面,SCENE_IVR 自动回复,SCENE_CARD_CUSTOM_CELL 卡券自定义cell]
+     * @param array  $cardList
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function createLandingPage($banner, $pageTitle, $canShare, $scene, $cardList)
+    {
+        $params = [
+            'banner' => $banner,
+            'page_title' => $pageTitle,
+            'can_share' => $canShare,
+            'scene' => $scene,
+            'card_list' => $cardList,
+        ];
+
+        return $this->httpPostJson('card/landingpage/create', $params);
+    }
+
+    /**
+     * 图文消息群发卡券.
+     *
+     * @param string $cardId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getHtml($cardId)
+    {
+        $params = [
+            'card_id' => $cardId,
+        ];
+
+        return $this->httpPostJson('card/mpnews/gethtml', $params);
+    }
+
+    /**
+     * 设置测试白名单.
+     *
+     * @param array $openids
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setTestWhitelist($openids)
+    {
+        $params = [
+            'openid' => $openids,
+        ];
+
+        return $this->httpPostJson('card/testwhitelist/set', $params);
+    }
+
+    /**
+     * 设置测试白名单(by username).
+     *
+     * @param array $usernames
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setTestWhitelistByName(array $usernames)
+    {
+        $params = [
+            'username' => $usernames,
+        ];
+
+        return $this->httpPostJson('card/testwhitelist/set', $params);
+    }
+
+    /**
+     * 获取用户已领取卡券接口.
+     *
+     * @param string $openid
+     * @param string $cardId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getUserCards($openid, $cardId = '')
+    {
+        $params = [
+            'openid' => $openid,
+            'card_id' => $cardId,
+        ];
+
+        return $this->httpPostJson('card/user/getcardlist', $params);
+    }
+
+    /**
+     * 设置微信买单接口.
+     * 设置买单的 card_id 必须已经配置了门店,否则会报错.
+     *
+     * @param string $cardId
+     * @param bool   $isOpen
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setPayCell($cardId, $isOpen = true)
+    {
+        $params = [
+            'card_id' => $cardId,
+            'is_open' => $isOpen,
+        ];
+
+        return $this->httpPostJson('card/paycell/set', $params);
+    }
+
+    /**
+     * 增加库存.
+     *
+     * @param string $cardId
+     * @param int    $amount
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function increaseStock($cardId, $amount)
+    {
+        return $this->updateStock($cardId, $amount, 'increase');
+    }
+
+    /**
+     * 减少库存.
+     *
+     * @param string $cardId
+     * @param int    $amount
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function reduceStock($cardId, $amount)
+    {
+        return $this->updateStock($cardId, $amount, 'reduce');
+    }
+
+    /**
+     * 修改库存接口.
+     *
+     * @param string $cardId
+     * @param int    $amount
+     * @param string $action
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function updateStock($cardId, $amount, $action = 'increase')
+    {
+        $key = 'increase' === $action ? 'increase_stock_value' : 'reduce_stock_value';
+        $params = [
+            'card_id' => $cardId,
+            $key => abs($amount),
+        ];
+
+        return $this->httpPostJson('card/modifystock', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/CodeClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/CodeClient.php
new file mode 100644
index 0000000..dcf96da
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/CodeClient.php
@@ -0,0 +1,193 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class CodeClient.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class CodeClient extends BaseClient
+{
+    /**
+     * 导入code接口.
+     *
+     * @param string $cardId
+     * @param array  $codes
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function deposit(string $cardId, array $codes)
+    {
+        $params = [
+            'card_id' => $cardId,
+            'code' => $codes,
+        ];
+
+        return $this->httpPostJson('card/code/deposit', $params);
+    }
+
+    /**
+     * 查询导入code数目.
+     *
+     * @param string $cardId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getDepositedCount(string $cardId)
+    {
+        $params = [
+            'card_id' => $cardId,
+        ];
+
+        return $this->httpPostJson('card/code/getdepositcount', $params);
+    }
+
+    /**
+     * 核查code接口.
+     *
+     * @param string $cardId
+     * @param array  $codes
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function check(string $cardId, array $codes)
+    {
+        $params = [
+            'card_id' => $cardId,
+            'code' => $codes,
+        ];
+
+        return $this->httpPostJson('card/code/checkcode', $params);
+    }
+
+    /**
+     * 查询 Code 接口.
+     *
+     * @param string $code
+     * @param string $cardId
+     * @param bool   $checkConsume
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(string $code, string $cardId = '', bool $checkConsume = true)
+    {
+        $params = [
+            'code' => $code,
+            'check_consume' => $checkConsume,
+            'card_id' => $cardId,
+        ];
+
+        return $this->httpPostJson('card/code/get', $params);
+    }
+
+    /**
+     * 更改Code接口.
+     *
+     * @param string $code
+     * @param string $newCode
+     * @param string $cardId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(string $code, string $newCode, string $cardId = '')
+    {
+        $params = [
+            'code' => $code,
+            'new_code' => $newCode,
+            'card_id' => $cardId,
+        ];
+
+        return $this->httpPostJson('card/code/update', $params);
+    }
+
+    /**
+     * 设置卡券失效.
+     *
+     * @param string $code
+     * @param string $cardId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function disable(string $code, string $cardId = '')
+    {
+        $params = [
+            'code' => $code,
+            'card_id' => $cardId,
+        ];
+
+        return $this->httpPostJson('card/code/unavailable', $params);
+    }
+
+    /**
+     * 核销 Code 接口.
+     *
+     * @param string      $code
+     * @param string|null $cardId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function consume(string $code, string $cardId = null)
+    {
+        $params = [
+            'code' => $code,
+        ];
+
+        if (!is_null($cardId)) {
+            $params['card_id'] = $cardId;
+        }
+
+        return $this->httpPostJson('card/code/consume', $params);
+    }
+
+    /**
+     * Code解码接口.
+     *
+     * @param string $encryptedCode
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function decrypt(string $encryptedCode)
+    {
+        $params = [
+            'encrypt_code' => $encryptedCode,
+        ];
+
+        return $this->httpPostJson('card/code/decrypt', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/CoinClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/CoinClient.php
new file mode 100644
index 0000000..b99d841
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/CoinClient.php
@@ -0,0 +1,119 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class CoinClient.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class CoinClient extends BaseClient
+{
+    /**
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function activate()
+    {
+        return $this->httpGet('card/pay/activate');
+    }
+
+    /**
+     * @param string $cardId
+     * @param int    $quantity
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getPrice(string $cardId, int $quantity)
+    {
+        return $this->httpPostJson('card/pay/getpayprice', [
+            'card_id' => $cardId,
+            'quantity' => $quantity,
+        ]);
+    }
+
+    /**
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function summary()
+    {
+        return $this->httpGet('card/pay/getcoinsinfo');
+    }
+
+    /**
+     * @param int $count
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function recharge(int $count)
+    {
+        return $this->httpPostJson('card/pay/recharge', [
+            'coin_count' => $count,
+        ]);
+    }
+
+    /**
+     * @param string $orderId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function order(string $orderId)
+    {
+        return $this->httpPostJson('card/pay/getorder', ['order_id' => $orderId]);
+    }
+
+    /**
+     * @param array $filters
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function orders(array $filters)
+    {
+        return $this->httpPostJson('card/pay/getorderlist', $filters);
+    }
+
+    /**
+     * @param string $cardId
+     * @param string $orderId
+     * @param int    $quantity
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function confirm(string $cardId, string $orderId, int $quantity)
+    {
+        return $this->httpPostJson('card/pay/confirm', [
+            'card_id' => $cardId,
+            'order_id' => $orderId,
+            'quantity' => $quantity,
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/GeneralCardClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/GeneralCardClient.php
new file mode 100644
index 0000000..1505981
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/GeneralCardClient.php
@@ -0,0 +1,71 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+/**
+ * Class GeneralCardClient.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class GeneralCardClient extends Client
+{
+    /**
+     * 通用卡接口激活.
+     *
+     * @param array $info
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function activate(array $info = [])
+    {
+        return $this->httpPostJson('card/generalcard/activate', $info);
+    }
+
+    /**
+     * 通用卡撤销激活.
+     *
+     * @param string $cardId
+     * @param string $code
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function deactivate(string $cardId, string $code)
+    {
+        $params = [
+            'card_id' => $cardId,
+            'code' => $code,
+        ];
+
+        return $this->httpPostJson('card/generalcard/unactivate', $params);
+    }
+
+    /**
+     * 更新会员信息.
+     *
+     * @param array $params
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function updateUser(array $params = [])
+    {
+        return $this->httpPostJson('card/generalcard/updateuser', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/JssdkClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/JssdkClient.php
new file mode 100644
index 0000000..9a6bb51
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/JssdkClient.php
@@ -0,0 +1,85 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\BasicService\Jssdk\Client as Jssdk;
+use EasyWeChat\Kernel\Support\Arr;
+use function EasyWeChat\Kernel\Support\str_random;
+
+/**
+ * Class Jssdk.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class JssdkClient extends Jssdk
+{
+    /**
+     * @param bool   $refresh
+     * @param string $type
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function getTicket(bool $refresh = false, string $type = 'wx_card'): array
+    {
+        return parent::getTicket($refresh, $type);
+    }
+
+    /**
+     * 微信卡券:JSAPI 卡券发放.
+     *
+     * @param array $cards
+     *
+     * @return string
+     */
+    public function assign(array $cards)
+    {
+        return json_encode(array_map(function ($card) {
+            return $this->attachExtension($card['card_id'], $card);
+        }, $cards));
+    }
+
+    /**
+     * 生成 js添加到卡包 需要的 card_list 项.
+     *
+     * @param string $cardId
+     * @param array  $extension
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function attachExtension($cardId, array $extension = [])
+    {
+        $timestamp = time();
+        $nonce = str_random(6);
+        $ticket = $this->getTicket()['ticket'];
+
+        $ext = array_merge(['timestamp' => $timestamp, 'nonce_str' => $nonce], Arr::only(
+            $extension,
+            ['code', 'openid', 'outer_id', 'balance', 'fixed_begintimestamp', 'outer_str']
+        ));
+
+        $ext['signature'] = $this->dictionaryOrderSignature($ticket, $timestamp, $cardId, $ext['code'] ?? '', $ext['openid'] ?? '', $nonce);
+
+        return [
+            'cardId' => $cardId,
+            'cardExt' => json_encode($ext),
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/MeetingTicketClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/MeetingTicketClient.php
new file mode 100644
index 0000000..892c5ad
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/MeetingTicketClient.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+/**
+ * Class MeetingTicketClient.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class MeetingTicketClient extends Client
+{
+    /**
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function updateUser(array $params)
+    {
+        return $this->httpPostJson('card/meetingticket/updateuser', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/MemberCardClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/MemberCardClient.php
new file mode 100644
index 0000000..41d051c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/MemberCardClient.php
@@ -0,0 +1,123 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+/**
+ * Class MemberCardClient.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class MemberCardClient extends Client
+{
+    /**
+     * 会员卡接口激活.
+     *
+     * @param array $info
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function activate(array $info = [])
+    {
+        return $this->httpPostJson('card/membercard/activate', $info);
+    }
+
+    /**
+     * 设置开卡字段接口.
+     *
+     * @param string $cardId
+     * @param array  $settings
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setActivationForm(string $cardId, array $settings)
+    {
+        $params = array_merge(['card_id' => $cardId], $settings);
+
+        return $this->httpPostJson('card/membercard/activateuserform/set', $params);
+    }
+
+    /**
+     * 拉取会员信息接口.
+     *
+     * @param string $cardId
+     * @param string $code
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getUser(string $cardId, string $code)
+    {
+        $params = [
+            'card_id' => $cardId,
+            'code' => $code,
+        ];
+
+        return $this->httpPostJson('card/membercard/userinfo/get', $params);
+    }
+
+    /**
+     * 更新会员信息.
+     *
+     * @param array $params
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function updateUser(array $params = [])
+    {
+        return $this->httpPostJson('card/membercard/updateuser', $params);
+    }
+
+    /**
+     * 获取用户提交资料.
+     *
+     * @param string $activateTicket
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getActivationForm($activateTicket)
+    {
+        $params = [
+            'activate_ticket' => $activateTicket,
+        ];
+
+        return $this->httpPostJson('card/membercard/activatetempinfo/get', $params);
+    }
+
+    /**
+     * 获取开卡组件链接接口.
+     *
+     * @param array $params 包含会员卡ID和随机字符串
+     *
+     * @return string 开卡组件链接
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getActivateUrl(array $params = [])
+    {
+        return $this->httpPostJson('card/membercard/activate/geturl', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/MovieTicketClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/MovieTicketClient.php
new file mode 100644
index 0000000..e6d9036
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/MovieTicketClient.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+/**
+ * Class MovieTicketClient.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class MovieTicketClient extends Client
+{
+    /**
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function updateUser(array $params)
+    {
+        return $this->httpPostJson('card/movieticket/updateuser', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/ServiceProvider.php
new file mode 100644
index 0000000..9ebeeaa
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/ServiceProvider.php
@@ -0,0 +1,73 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['card'] = function ($app) {
+            return new Card($app);
+        };
+
+        $app['card.client'] = function ($app) {
+            return new Client($app);
+        };
+
+        $app['card.coin'] = function ($app) {
+            return new CoinClient($app);
+        };
+
+        $app['card.sub_merchant'] = function ($app) {
+            return new SubMerchantClient($app);
+        };
+
+        $app['card.code'] = function ($app) {
+            return new CodeClient($app);
+        };
+
+        $app['card.movie_ticket'] = function ($app) {
+            return new MovieTicketClient($app);
+        };
+
+        $app['card.member_card'] = function ($app) {
+            return new MemberCardClient($app);
+        };
+
+        $app['card.general_card'] = function ($app) {
+            return new GeneralCardClient($app);
+        };
+
+        $app['card.boarding_pass'] = function ($app) {
+            return new BoardingPassClient($app);
+        };
+
+        $app['card.meeting_ticket'] = function ($app) {
+            return new MeetingTicketClient($app);
+        };
+
+        $app['card.jssdk'] = function ($app) {
+            return new JssdkClient($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/SubMerchantClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/SubMerchantClient.php
new file mode 100644
index 0000000..f058492
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/SubMerchantClient.php
@@ -0,0 +1,121 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Support\Arr;
+
+/**
+ * Class SubMerchantClient.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class SubMerchantClient extends BaseClient
+{
+    /**
+     * 添加子商户.
+     *
+     * @param array $info
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create(array $info = [])
+    {
+        $params = [
+            'info' => Arr::only($info, [
+                'brand_name',
+                'logo_url',
+                'protocol',
+                'end_time',
+                'primary_category_id',
+                'secondary_category_id',
+                'agreement_media_id',
+                'operator_media_id',
+                'app_id',
+            ]),
+        ];
+
+        return $this->httpPostJson('card/submerchant/submit', $params);
+    }
+
+    /**
+     * 更新子商户.
+     *
+     * @param int   $merchantId
+     * @param array $info
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(int $merchantId, array $info = [])
+    {
+        $params = [
+            'info' => array_merge(['merchant_id' => $merchantId],
+                Arr::only($info, [
+                    'brand_name',
+                    'logo_url',
+                    'protocol',
+                    'end_time',
+                    'primary_category_id',
+                    'secondary_category_id',
+                    'agreement_media_id',
+                    'operator_media_id',
+                    'app_id',
+                ])),
+        ];
+
+        return $this->httpPostJson('card/submerchant/update', $params);
+    }
+
+    /**
+     * 获取子商户信息.
+     *
+     * @param int $merchantId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(int $merchantId)
+    {
+        return $this->httpPostJson('card/submerchant/get', ['merchant_id' => $merchantId]);
+    }
+
+    /**
+     * 批量获取子商户信息.
+     *
+     * @param int    $beginId
+     * @param int    $limit
+     * @param string $status
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list(int $beginId = 0, int $limit = 50, string $status = 'CHECKING')
+    {
+        $params = [
+            'begin_id' => $beginId,
+            'limit' => $limit,
+            'status' => $status,
+        ];
+
+        return $this->httpPostJson('card/submerchant/batchget', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Comment/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Comment/Client.php
new file mode 100644
index 0000000..4f45b74
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Comment/Client.php
@@ -0,0 +1,208 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Comment;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Open article comment.
+     *
+     * @param string   $msgId
+     * @param int|null $index
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function open(string $msgId, int $index = null)
+    {
+        $params = [
+            'msg_data_id' => $msgId,
+            'index' => $index,
+        ];
+
+        return $this->httpPostJson('cgi-bin/comment/open', $params);
+    }
+
+    /**
+     * Close comment.
+     *
+     * @param string   $msgId
+     * @param int|null $index
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function close(string $msgId, int $index = null)
+    {
+        $params = [
+            'msg_data_id' => $msgId,
+            'index' => $index,
+        ];
+
+        return $this->httpPostJson('cgi-bin/comment/close', $params);
+    }
+
+    /**
+     * Get article comments.
+     *
+     * @param string $msgId
+     * @param int    $index
+     * @param int    $begin
+     * @param int    $count
+     * @param int    $type
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list(string $msgId, int $index, int $begin, int $count, int $type = 0)
+    {
+        $params = [
+            'msg_data_id' => $msgId,
+            'index' => $index,
+            'begin' => $begin,
+            'count' => $count,
+            'type' => $type,
+        ];
+
+        return $this->httpPostJson('cgi-bin/comment/list', $params);
+    }
+
+    /**
+     * Mark elect comment.
+     *
+     * @param string $msgId
+     * @param int    $index
+     * @param int    $commentId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function markElect(string $msgId, int $index, int $commentId)
+    {
+        $params = [
+            'msg_data_id' => $msgId,
+            'index' => $index,
+            'user_comment_id' => $commentId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/comment/markelect', $params);
+    }
+
+    /**
+     * Unmark elect comment.
+     *
+     * @param string $msgId
+     * @param int    $index
+     * @param int    $commentId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function unmarkElect(string $msgId, int $index, int $commentId)
+    {
+        $params = [
+            'msg_data_id' => $msgId,
+            'index' => $index,
+            'user_comment_id' => $commentId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/comment/unmarkelect', $params);
+    }
+
+    /**
+     * Delete comment.
+     *
+     * @param string $msgId
+     * @param int    $index
+     * @param int    $commentId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete(string $msgId, int $index, int $commentId)
+    {
+        $params = [
+            'msg_data_id' => $msgId,
+            'index' => $index,
+            'user_comment_id' => $commentId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/comment/delete', $params);
+    }
+
+    /**
+     * Reply to a comment.
+     *
+     * @param string $msgId
+     * @param int    $index
+     * @param int    $commentId
+     * @param string $content
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function reply(string $msgId, int $index, int $commentId, string $content)
+    {
+        $params = [
+            'msg_data_id' => $msgId,
+            'index' => $index,
+            'user_comment_id' => $commentId,
+            'content' => $content,
+        ];
+
+        return $this->httpPostJson('cgi-bin/comment/reply/add', $params);
+    }
+
+    /**
+     * Delete a reply.
+     *
+     * @param string $msgId
+     * @param int    $index
+     * @param int    $commentId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function deleteReply(string $msgId, int $index, int $commentId)
+    {
+        $params = [
+            'msg_data_id' => $msgId,
+            'index' => $index,
+            'user_comment_id' => $commentId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/comment/reply/delete', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Comment/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Comment/ServiceProvider.php
new file mode 100644
index 0000000..8d6806c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Comment/ServiceProvider.php
@@ -0,0 +1,44 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * ServiceProvider.php.
+ *
+ * This file is part of the wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Comment;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['comment'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Client.php
new file mode 100644
index 0000000..64e43e5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Client.php
@@ -0,0 +1,230 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\CustomerService;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    /**
+     * List all staffs.
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function list()
+    {
+        return $this->httpGet('cgi-bin/customservice/getkflist');
+    }
+
+    /**
+     * List all online staffs.
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function online()
+    {
+        return $this->httpGet('cgi-bin/customservice/getonlinekflist');
+    }
+
+    /**
+     * Create a staff.
+     *
+     * @param string $account
+     * @param string $nickname
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create(string $account, string $nickname)
+    {
+        $params = [
+            'kf_account' => $account,
+            'nickname' => $nickname,
+        ];
+
+        return $this->httpPostJson('customservice/kfaccount/add', $params);
+    }
+
+    /**
+     * Update a staff.
+     *
+     * @param string $account
+     * @param string $nickname
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(string $account, string $nickname)
+    {
+        $params = [
+            'kf_account' => $account,
+            'nickname' => $nickname,
+        ];
+
+        return $this->httpPostJson('customservice/kfaccount/update', $params);
+    }
+
+    /**
+     * Delete a staff.
+     *
+     * @param string $account
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete(string $account)
+    {
+        return $this->httpPostJson('customservice/kfaccount/del', [], ['kf_account' => $account]);
+    }
+
+    /**
+     * Invite a staff.
+     *
+     * @param string $account
+     * @param string $wechatId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function invite(string $account, string $wechatId)
+    {
+        $params = [
+            'kf_account' => $account,
+            'invite_wx' => $wechatId,
+        ];
+
+        return $this->httpPostJson('customservice/kfaccount/inviteworker', $params);
+    }
+
+    /**
+     * Set staff avatar.
+     *
+     * @param string $account
+     * @param string $path
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setAvatar(string $account, string $path)
+    {
+        return $this->httpUpload('customservice/kfaccount/uploadheadimg', ['media' => $path], [], ['kf_account' => $account]);
+    }
+
+    /**
+     * Get message builder.
+     *
+     * @param \EasyWeChat\Kernel\Messages\Message|string $message
+     *
+     * @return \EasyWeChat\OfficialAccount\CustomerService\Messenger
+     */
+    public function message($message)
+    {
+        $messageBuilder = new Messenger($this);
+
+        return $messageBuilder->message($message);
+    }
+
+    /**
+     * Send a message.
+     *
+     * @param array $message
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function send(array $message)
+    {
+        return $this->httpPostJson('cgi-bin/message/custom/send', $message);
+    }
+
+    /**
+     * Show typing status.
+     *
+     * @param string $openid
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function showTypingStatusToUser(string $openid)
+    {
+        return $this->httpPostJson('cgi-bin/message/custom/typing', [
+            'touser' => $openid,
+            'command' => 'Typing',
+        ]);
+    }
+
+    /**
+     * Hide typing status.
+     *
+     * @param string $openid
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function hideTypingStatusToUser(string $openid)
+    {
+        return $this->httpPostJson('cgi-bin/message/custom/typing', [
+            'touser' => $openid,
+            'command' => 'CancelTyping',
+        ]);
+    }
+
+    /**
+     * Get messages history.
+     *
+     * @param int $startTime
+     * @param int $endTime
+     * @param int $msgId
+     * @param int $number
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function messages($startTime, $endTime, int $msgId = 1, int $number = 10000)
+    {
+        $params = [
+            'starttime' => is_numeric($startTime) ? $startTime : strtotime($startTime),
+            'endtime' => is_numeric($endTime) ? $endTime : strtotime($endTime),
+            'msgid' => $msgId,
+            'number' => $number,
+        ];
+
+        return $this->httpPostJson('customservice/msgrecord/getmsglist', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Messenger.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Messenger.php
new file mode 100644
index 0000000..69bf2f3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Messenger.php
@@ -0,0 +1,165 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\CustomerService;
+
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Messages\Message;
+use EasyWeChat\Kernel\Messages\Raw as RawMessage;
+use EasyWeChat\Kernel\Messages\Text;
+
+/**
+ * Class MessageBuilder.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Messenger
+{
+    /**
+     * Messages to send.
+     *
+     * @var \EasyWeChat\Kernel\Messages\Message;
+     */
+    protected $message;
+
+    /**
+     * Messages target user open id.
+     *
+     * @var string
+     */
+    protected $to;
+
+    /**
+     * Messages sender staff id.
+     *
+     * @var string
+     */
+    protected $account;
+
+    /**
+     * Customer service instance.
+     *
+     * @var \EasyWeChat\OfficialAccount\CustomerService\Client
+     */
+    protected $client;
+
+    /**
+     * MessageBuilder constructor.
+     *
+     * @param \EasyWeChat\OfficialAccount\CustomerService\Client $client
+     */
+    public function __construct(Client $client)
+    {
+        $this->client = $client;
+    }
+
+    /**
+     * Set message to send.
+     *
+     * @param string|Message $message
+     *
+     * @return Messenger
+     */
+    public function message($message)
+    {
+        if (is_string($message)) {
+            $message = new Text($message);
+        }
+
+        $this->message = $message;
+
+        return $this;
+    }
+
+    /**
+     * Set staff account to send message.
+     *
+     * @param string $account
+     *
+     * @return Messenger
+     */
+    public function by(string $account)
+    {
+        $this->account = $account;
+
+        return $this;
+    }
+
+    /**
+     * @param string $account
+     *
+     * @return Messenger
+     */
+    public function from(string $account)
+    {
+        return $this->by($account);
+    }
+
+    /**
+     * Set target user open id.
+     *
+     * @param string $openid
+     *
+     * @return Messenger
+     */
+    public function to($openid)
+    {
+        $this->to = $openid;
+
+        return $this;
+    }
+
+    /**
+     * Send the message.
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    public function send()
+    {
+        if (empty($this->message)) {
+            throw new RuntimeException('No message to send.');
+        }
+
+        if ($this->message instanceof RawMessage) {
+            $message = json_decode($this->message->get('content'), true);
+        } else {
+            $prepends = [
+                'touser' => $this->to,
+            ];
+            if ($this->account) {
+                $prepends['customservice'] = ['kf_account' => $this->account];
+            }
+            $message = $this->message->transformForJsonRequest($prepends);
+        }
+
+        return $this->client->send($message);
+    }
+
+    /**
+     * Return property.
+     *
+     * @param string $property
+     *
+     * @return mixed
+     */
+    public function __get(string $property)
+    {
+        if (property_exists($this, $property)) {
+            return $this->$property;
+        }
+
+        return null;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/ServiceProvider.php
new file mode 100644
index 0000000..a879ce8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/ServiceProvider.php
@@ -0,0 +1,37 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\CustomerService;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['customer_service'] = function ($app) {
+            return new Client($app);
+        };
+
+        $app['customer_service_session'] = function ($app) {
+            return new SessionClient($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/SessionClient.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/SessionClient.php
new file mode 100644
index 0000000..b92b6db
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/SessionClient.php
@@ -0,0 +1,104 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\CustomerService;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class SessionClient.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class SessionClient extends BaseClient
+{
+    /**
+     * List all sessions of $account.
+     *
+     * @param string $account
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function list(string $account)
+    {
+        return $this->httpGet('customservice/kfsession/getsessionlist', ['kf_account' => $account]);
+    }
+
+    /**
+     * List all the people waiting.
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function waiting()
+    {
+        return $this->httpGet('customservice/kfsession/getwaitcase');
+    }
+
+    /**
+     * Create a session.
+     *
+     * @param string $account
+     * @param string $openid
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create(string $account, string $openid)
+    {
+        $params = [
+            'kf_account' => $account,
+            'openid' => $openid,
+        ];
+
+        return $this->httpPostJson('customservice/kfsession/create', $params);
+    }
+
+    /**
+     * Close a session.
+     *
+     * @param string $account
+     * @param string $openid
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function close(string $account, string $openid)
+    {
+        $params = [
+            'kf_account' => $account,
+            'openid' => $openid,
+        ];
+
+        return $this->httpPostJson('customservice/kfsession/close', $params);
+    }
+
+    /**
+     * Get a session.
+     *
+     * @param string $openid
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function get(string $openid)
+    {
+        return $this->httpGet('customservice/kfsession/getsession', ['openid' => $openid]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/DataCube/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/Client.php
new file mode 100644
index 0000000..0bf1c21
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/Client.php
@@ -0,0 +1,340 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\DataCube;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    /**
+     * 获取用户增减数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function userSummary(string $from, string $to)
+    {
+        return $this->query('datacube/getusersummary', $from, $to);
+    }
+
+    /**
+     * 获取累计用户数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function userCumulate(string $from, string $to)
+    {
+        return $this->query('datacube/getusercumulate', $from, $to);
+    }
+
+    /**
+     * 获取图文群发每日数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function articleSummary(string $from, string $to)
+    {
+        return $this->query('datacube/getarticlesummary', $from, $to);
+    }
+
+    /**
+     * 获取图文群发总数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function articleTotal(string $from, string $to)
+    {
+        return $this->query('datacube/getarticletotal', $from, $to);
+    }
+
+    /**
+     * 获取图文统计数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function userReadSummary(string $from, string $to)
+    {
+        return $this->query('datacube/getuserread', $from, $to);
+    }
+
+    /**
+     * 获取图文统计分时数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function userReadHourly(string $from, string $to)
+    {
+        return $this->query('datacube/getuserreadhour', $from, $to);
+    }
+
+    /**
+     * 获取图文分享转发数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function userShareSummary(string $from, string $to)
+    {
+        return $this->query('datacube/getusershare', $from, $to);
+    }
+
+    /**
+     * 获取图文分享转发分时数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function userShareHourly(string $from, string $to)
+    {
+        return $this->query('datacube/getusersharehour', $from, $to);
+    }
+
+    /**
+     * 获取消息发送概况数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function upstreamMessageSummary(string $from, string $to)
+    {
+        return $this->query('datacube/getupstreammsg', $from, $to);
+    }
+
+    /**
+     * 获取消息分送分时数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function upstreamMessageHourly(string $from, string $to)
+    {
+        return $this->query('datacube/getupstreammsghour', $from, $to);
+    }
+
+    /**
+     * 获取消息发送周数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function upstreamMessageWeekly(string $from, string $to)
+    {
+        return $this->query('datacube/getupstreammsgweek', $from, $to);
+    }
+
+    /**
+     * 获取消息发送月数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function upstreamMessageMonthly(string $from, string $to)
+    {
+        return $this->query('datacube/getupstreammsgmonth', $from, $to);
+    }
+
+    /**
+     * 获取消息发送分布数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function upstreamMessageDistSummary(string $from, string $to)
+    {
+        return $this->query('datacube/getupstreammsgdist', $from, $to);
+    }
+
+    /**
+     * 获取消息发送分布周数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function upstreamMessageDistWeekly(string $from, string $to)
+    {
+        return $this->query('datacube/getupstreammsgdistweek', $from, $to);
+    }
+
+    /**
+     * 获取消息发送分布月数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function upstreamMessageDistMonthly(string $from, string $to)
+    {
+        return $this->query('datacube/getupstreammsgdistmonth', $from, $to);
+    }
+
+    /**
+     * 获取接口分析数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function interfaceSummary(string $from, string $to)
+    {
+        return $this->query('datacube/getinterfacesummary', $from, $to);
+    }
+
+    /**
+     * 获取接口分析分时数据.
+     *
+     * @param string $from
+     * @param string $to
+     *
+     * @return mixed
+     */
+    public function interfaceSummaryHourly(string $from, string $to)
+    {
+        return $this->query('datacube/getinterfacesummaryhour', $from, $to);
+    }
+
+    /**
+     * 拉取卡券概况数据接口.
+     *
+     * @param string $from
+     * @param string $to
+     * @param int    $condSource
+     *
+     * @return mixed
+     */
+    public function cardSummary(string $from, string $to, $condSource = 0)
+    {
+        $ext = [
+            'cond_source' => intval($condSource),
+        ];
+
+        return $this->query('datacube/getcardbizuininfo', $from, $to, $ext);
+    }
+
+    /**
+     * 获取免费券数据接口.
+     *
+     * @param string $from
+     * @param string $to
+     * @param int    $condSource
+     * @param string $cardId
+     *
+     * @return mixed
+     */
+    public function freeCardSummary(string $from, string $to, int $condSource = 0, string $cardId = '')
+    {
+        $ext = [
+            'cond_source' => intval($condSource),
+            'card_id' => $cardId,
+        ];
+
+        return $this->query('datacube/getcardcardinfo', $from, $to, $ext);
+    }
+
+    /**
+     * 拉取会员卡数据接口.
+     *
+     * @param string $from
+     * @param string $to
+     * @param int    $condSource
+     *
+     * @return mixed
+     */
+    public function memberCardSummary(string $from, string $to, $condSource = 0)
+    {
+        $ext = [
+            'cond_source' => intval($condSource),
+        ];
+
+        return $this->query('datacube/getcardmembercardinfo', $from, $to, $ext);
+    }
+
+    /**
+     * 拉取单张会员卡数据接口.
+     *
+     * @param string $from
+     * @param string $to
+     * @param string $cardId
+     *
+     * @return mixed
+     */
+    public function memberCardSummaryById(string $from, string $to, string $cardId)
+    {
+        $ext = [
+            'card_id' => $cardId,
+        ];
+
+        return $this->query('datacube/getcardmembercarddetail', $from, $to, $ext);
+    }
+
+    /**
+     * 查询数据.
+     *
+     * @param string $api
+     * @param string $from
+     * @param string $to
+     * @param array  $ext
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function query(string $api, string $from, string $to, array $ext = [])
+    {
+        $params = array_merge([
+            'begin_date' => $from,
+            'end_date' => $to,
+        ], $ext);
+
+        return $this->httpPostJson($api, $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/DataCube/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/ServiceProvider.php
new file mode 100644
index 0000000..bfec89a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\DataCube;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['data_cube'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Device/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Device/Client.php
new file mode 100644
index 0000000..8dc9d73
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Device/Client.php
@@ -0,0 +1,251 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Device;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @see http://iot.weixin.qq.com/wiki/new/index.html
+ *
+ * @author soone <66812590@qq.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @param string $deviceId
+     * @param string $openid
+     * @param string $content
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function message(string $deviceId, string $openid, string $content)
+    {
+        $params = [
+            'device_type' => $this->app['config']['device_type'],
+            'device_id' => $deviceId,
+            'open_id' => $openid,
+            'content' => base64_encode($content),
+        ];
+
+        return $this->httpPostJson('device/transmsg', $params);
+    }
+
+    /**
+     * Get device qrcode.
+     *
+     * @param array $deviceIds
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function qrCode(array $deviceIds)
+    {
+        $params = [
+            'device_num' => count($deviceIds),
+            'device_id_list' => $deviceIds,
+        ];
+
+        return $this->httpPostJson('device/create_qrcode', $params);
+    }
+
+    /**
+     * @param array  $devices
+     * @param string $productId
+     * @param int    $opType
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function authorize(array $devices, string $productId, int $opType = 0)
+    {
+        $params = [
+            'device_num' => count($devices),
+            'device_list' => $devices,
+            'op_type' => $opType,
+            'product_id' => $productId,
+        ];
+
+        return $this->httpPostJson('device/authorize_device', $params);
+    }
+
+    /**
+     * 获取 device id 和二维码
+     *
+     * @param string $productId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function createId(string $productId)
+    {
+        $params = [
+            'product_id' => $productId,
+        ];
+
+        return $this->httpGet('device/getqrcode', $params);
+    }
+
+    /**
+     * @param string $openid
+     * @param string $deviceId
+     * @param string $ticket
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function bind(string $openid, string $deviceId, string $ticket)
+    {
+        $params = [
+            'ticket' => $ticket,
+            'device_id' => $deviceId,
+            'openid' => $openid,
+        ];
+
+        return $this->httpPostJson('device/bind', $params);
+    }
+
+    /**
+     * @param string $openid
+     * @param string $deviceId
+     * @param string $ticket
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function unbind(string $openid, string $deviceId, string $ticket)
+    {
+        $params = [
+            'ticket' => $ticket,
+            'device_id' => $deviceId,
+            'openid' => $openid,
+        ];
+
+        return $this->httpPostJson('device/unbind', $params);
+    }
+
+    /**
+     * @param string $openid
+     * @param string $deviceId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function forceBind(string $openid, string $deviceId)
+    {
+        $params = [
+            'device_id' => $deviceId,
+            'openid' => $openid,
+        ];
+
+        return $this->httpPostJson('device/compel_bind', $params);
+    }
+
+    /**
+     * @param string $openid
+     * @param string $deviceId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function forceUnbind(string $openid, string $deviceId)
+    {
+        $params = [
+            'device_id' => $deviceId,
+            'openid' => $openid,
+        ];
+
+        return $this->httpPostJson('device/compel_unbind', $params);
+    }
+
+    /**
+     * @param string $deviceId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function status(string $deviceId)
+    {
+        $params = [
+            'device_id' => $deviceId,
+        ];
+
+        return $this->httpGet('device/get_stat', $params);
+    }
+
+    /**
+     * @param string $ticket
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function verify(string $ticket)
+    {
+        $params = [
+            'ticket' => $ticket,
+        ];
+
+        return $this->httpPost('device/verify_qrcode', $params);
+    }
+
+    /**
+     * @param string $deviceId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function openid(string $deviceId)
+    {
+        $params = [
+            'device_type' => $this->app['config']['device_type'],
+            'device_id' => $deviceId,
+        ];
+
+        return $this->httpGet('device/get_openid', $params);
+    }
+
+    /**
+     * @param string $openid
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function listByOpenid(string $openid)
+    {
+        $params = [
+            'openid' => $openid,
+        ];
+
+        return $this->httpGet('device/get_bind_device', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Device/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Device/ServiceProvider.php
new file mode 100644
index 0000000..e3dce8e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Device/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Device;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author soone <66812590@qq.com
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['device'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Goods/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Goods/Client.php
new file mode 100644
index 0000000..c720e31
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Goods/Client.php
@@ -0,0 +1,113 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Goods;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author her-cat <hxhsoft@foxmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Add the goods.
+     *
+     * @param array $data
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function add(array $data)
+    {
+        return $this->httpPostJson('scan/product/v2/add', [
+            'product' => $data,
+        ]);
+    }
+
+    /**
+     * Update the goods.
+     *
+     * @param array $data
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(array $data)
+    {
+        return $this->httpPostJson('scan/product/v2/add', [
+            'product' => $data,
+        ]);
+    }
+
+    /**
+     * Get add or update goods results.
+     *
+     * @param string $ticket
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function status(string $ticket)
+    {
+        return $this->httpPostJson('scan/product/v2/status', [
+            'status_ticket' => $ticket,
+        ]);
+    }
+
+    /**
+     * Get goods information.
+     *
+     * @param string $pid
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(string $pid)
+    {
+        return $this->httpPostJson('scan/product/v2/getinfo', [
+            'product' => [
+                'pid' => $pid,
+            ],
+        ]);
+    }
+
+    /**
+     * Get a list of goods.
+     *
+     * @param string $context
+     * @param int    $page
+     * @param int    $size
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list(string $context = '', int $page = 1, int $size = 10)
+    {
+        return $this->httpPostJson('scan/product/v2/getinfobypage', [
+            'page_context' => $context,
+            'page_num' => $page,
+            'page_size' => $size,
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Goods/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Goods/ServiceProvider.php
new file mode 100644
index 0000000..38a0902
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Goods/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Goods;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author her-cat <hxhsoft@foxmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['goods'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Material/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Material/Client.php
new file mode 100644
index 0000000..a62fded
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Material/Client.php
@@ -0,0 +1,301 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Material;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Http\StreamResponse;
+use EasyWeChat\Kernel\Messages\Article;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Allow media type.
+     *
+     * @var array
+     */
+    protected $allowTypes = ['image', 'voice', 'video', 'thumb', 'news_image'];
+
+    /**
+     * Upload image.
+     *
+     * @param string $path
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function uploadImage(string $path)
+    {
+        return $this->upload('image', $path);
+    }
+
+    /**
+     * Upload voice.
+     *
+     * @param string $path
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function uploadVoice(string $path)
+    {
+        return $this->upload('voice', $path);
+    }
+
+    /**
+     * Upload thumb.
+     *
+     * @param string $path
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function uploadThumb(string $path)
+    {
+        return $this->upload('thumb', $path);
+    }
+
+    /**
+     * Upload video.
+     *
+     * @param string $path
+     * @param string $title
+     * @param string $description
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function uploadVideo(string $path, string $title, string $description)
+    {
+        $params = [
+            'description' => json_encode(
+                [
+                    'title' => $title,
+                    'introduction' => $description,
+                ], JSON_UNESCAPED_UNICODE),
+        ];
+
+        return $this->upload('video', $path, $params);
+    }
+
+    /**
+     * Upload articles.
+     *
+     * @param array|Article $articles
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function uploadArticle($articles)
+    {
+        if ($articles instanceof Article || !empty($articles['title'])) {
+            $articles = [$articles];
+        }
+
+        $params = ['articles' => array_map(function ($article) {
+            if ($article instanceof Article) {
+                return $article->transformForJsonRequestWithoutType();
+            }
+
+            return $article;
+        }, $articles)];
+
+        return $this->httpPostJson('cgi-bin/material/add_news', $params);
+    }
+
+    /**
+     * Update article.
+     *
+     * @param string        $mediaId
+     * @param array|Article $article
+     * @param int           $index
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function updateArticle(string $mediaId, $article, int $index = 0)
+    {
+        if ($article instanceof Article) {
+            $article = $article->transformForJsonRequestWithoutType();
+        }
+
+        $params = [
+            'media_id' => $mediaId,
+            'index' => $index,
+            'articles' => isset($article['title']) ? $article : (isset($article[$index]) ? $article[$index] : []),
+        ];
+
+        return $this->httpPostJson('cgi-bin/material/update_news', $params);
+    }
+
+    /**
+     * Upload image for article.
+     *
+     * @param string $path
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function uploadArticleImage(string $path)
+    {
+        return $this->upload('news_image', $path);
+    }
+
+    /**
+     * Fetch material.
+     *
+     * @param string $mediaId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(string $mediaId)
+    {
+        $response = $this->requestRaw('cgi-bin/material/get_material', 'POST', ['json' => ['media_id' => $mediaId]]);
+
+        if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) {
+            return StreamResponse::buildFromPsrResponse($response);
+        }
+
+        return $this->castResponseToType($response, $this->app['config']->get('response_type'));
+    }
+
+    /**
+     * Delete material by media ID.
+     *
+     * @param string $mediaId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete(string $mediaId)
+    {
+        return $this->httpPostJson('cgi-bin/material/del_material', ['media_id' => $mediaId]);
+    }
+
+    /**
+     * List materials.
+     *
+     * example:
+     *
+     * {
+     *   "total_count": TOTAL_COUNT,
+     *   "item_count": ITEM_COUNT,
+     *   "item": [{
+     *             "media_id": MEDIA_ID,
+     *             "name": NAME,
+     *             "update_time": UPDATE_TIME
+     *         },
+     *         // more...
+     *   ]
+     * }
+     *
+     * @param string $type
+     * @param int    $offset
+     * @param int    $count
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list(string $type, int $offset = 0, int $count = 20)
+    {
+        $params = [
+            'type' => $type,
+            'offset' => $offset,
+            'count' => $count,
+        ];
+
+        return $this->httpPostJson('cgi-bin/material/batchget_material', $params);
+    }
+
+    /**
+     * Get stats of materials.
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function stats()
+    {
+        return $this->httpGet('cgi-bin/material/get_materialcount');
+    }
+
+    /**
+     * Upload material.
+     *
+     * @param string $type
+     * @param string $path
+     * @param array  $form
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function upload(string $type, string $path, array $form = [])
+    {
+        if (!file_exists($path) || !is_readable($path)) {
+            throw new InvalidArgumentException(sprintf('File does not exist, or the file is unreadable: "%s"', $path));
+        }
+
+        $form['type'] = $type;
+
+        return $this->httpUpload($this->getApiByType($type), ['media' => $path], $form);
+    }
+
+    /**
+     * Get API by type.
+     *
+     * @param string $type
+     *
+     * @return string
+     */
+    public function getApiByType(string $type)
+    {
+        switch ($type) {
+            case 'news_image':
+                return 'cgi-bin/media/uploadimg';
+            default:
+                return 'cgi-bin/material/add_material';
+        }
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Material/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Material/ServiceProvider.php
new file mode 100644
index 0000000..089a8f8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Material/ServiceProvider.php
@@ -0,0 +1,44 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * ServiceProvider.php.
+ *
+ * This file is part of the wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Material;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['material'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Menu/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Menu/Client.php
new file mode 100644
index 0000000..9c132c6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Menu/Client.php
@@ -0,0 +1,103 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Menu;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get all menus.
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function list()
+    {
+        return $this->httpGet('cgi-bin/menu/get');
+    }
+
+    /**
+     * Get current menus.
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function current()
+    {
+        return $this->httpGet('cgi-bin/get_current_selfmenu_info');
+    }
+
+    /**
+     * Add menu.
+     *
+     * @param array $buttons
+     * @param array $matchRule
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create(array $buttons, array $matchRule = [])
+    {
+        if (!empty($matchRule)) {
+            return $this->httpPostJson('cgi-bin/menu/addconditional', [
+                'button' => $buttons,
+                'matchrule' => $matchRule,
+            ]);
+        }
+
+        return $this->httpPostJson('cgi-bin/menu/create', ['button' => $buttons]);
+    }
+
+    /**
+     * Destroy menu.
+     *
+     * @param int $menuId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete(int $menuId = null)
+    {
+        if (is_null($menuId)) {
+            return $this->httpGet('cgi-bin/menu/delete');
+        }
+
+        return $this->httpPostJson('cgi-bin/menu/delconditional', ['menuid' => $menuId]);
+    }
+
+    /**
+     * Test conditional menu.
+     *
+     * @param string $userId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function match(string $userId)
+    {
+        return $this->httpPostJson('cgi-bin/menu/trymatch', ['user_id' => $userId]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Menu/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Menu/ServiceProvider.php
new file mode 100644
index 0000000..e79b105
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Menu/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Menu;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['menu'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/OAuth/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/OAuth/ServiceProvider.php
new file mode 100644
index 0000000..ba176dc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/OAuth/ServiceProvider.php
@@ -0,0 +1,66 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\OAuth;
+
+use Overtrue\Socialite\SocialiteManager as Socialite;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['oauth'] = function ($app) {
+            $socialite = (new Socialite([
+                'wechat' => [
+                    'client_id' => $app['config']['app_id'],
+                    'client_secret' => $app['config']['secret'],
+                    'redirect' => $this->prepareCallbackUrl($app),
+                ],
+            ], $app['request']))->driver('wechat');
+
+            $scopes = (array) $app['config']->get('oauth.scopes', ['snsapi_userinfo']);
+
+            if (!empty($scopes)) {
+                $socialite->scopes($scopes);
+            }
+
+            return $socialite;
+        };
+    }
+
+    /**
+     * Prepare the OAuth callback url for wechat.
+     *
+     * @param Container $app
+     *
+     * @return string
+     */
+    private function prepareCallbackUrl($app)
+    {
+        $callback = $app['config']->get('oauth.callback');
+        if (0 === stripos($callback, 'http')) {
+            return $callback;
+        }
+        $baseUrl = $app['request']->getSchemeAndHttpHost();
+
+        return $baseUrl.'/'.ltrim($callback, '/');
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/OCR/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/OCR/Client.php
new file mode 100644
index 0000000..6aa44bc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/OCR/Client.php
@@ -0,0 +1,85 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\OCR;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class Client.
+ *
+ * @author joyeekk <xygao2420@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Allow image parameter type.
+     *
+     * @var array
+     */
+    protected $allowTypes = ['photo', 'scan'];
+
+    /**
+     * ID card OCR.
+     *
+     * @param string $path
+     * @param string $type
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function idCard(string $path, string $type = 'photo')
+    {
+        if (!\in_array($type, $this->allowTypes, true)) {
+            throw new InvalidArgumentException(sprintf("Unsupported type: '%s'", $type));
+        }
+
+        return $this->httpGet('cv/ocr/idcard', [
+            'type' => $type,
+            'img_url' => $path,
+        ]);
+    }
+
+    /**
+     * Bank card OCR.
+     *
+     * @param string $path
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function bankCard(string $path)
+    {
+        return $this->httpGet('cv/ocr/bankcard', [
+            'img_url' => $path,
+        ]);
+    }
+
+    /**
+     * Vehicle license OCR.
+     *
+     * @param string $path
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function vehicleLicense(string $path)
+    {
+        return $this->httpGet('cv/ocr/driving', [
+            'img_url' => $path,
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/OCR/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/OCR/ServiceProvider.php
new file mode 100644
index 0000000..1773079
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/OCR/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\OCR;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author joyeekk <xygao2420@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['ocr'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/POI/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/POI/Client.php
new file mode 100644
index 0000000..6a8e570
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/POI/Client.php
@@ -0,0 +1,145 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\POI;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get POI supported categories.
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function categories()
+    {
+        return $this->httpGet('cgi-bin/poi/getwxcategory');
+    }
+
+    /**
+     * Get POI by ID.
+     *
+     * @param int $poiId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(int $poiId)
+    {
+        return $this->httpPostJson('cgi-bin/poi/getpoi', ['poi_id' => $poiId]);
+    }
+
+    /**
+     * List POI.
+     *
+     * @param int $offset
+     * @param int $limit
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list(int $offset = 0, int $limit = 10)
+    {
+        $params = [
+            'begin' => $offset,
+            'limit' => $limit,
+        ];
+
+        return $this->httpPostJson('cgi-bin/poi/getpoilist', $params);
+    }
+
+    /**
+     * Create a POI.
+     *
+     * @param array $baseInfo
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create(array $baseInfo)
+    {
+        $params = [
+            'business' => [
+                'base_info' => $baseInfo,
+            ],
+        ];
+
+        return $this->httpPostJson('cgi-bin/poi/addpoi', $params);
+    }
+
+    /**
+     * @param array $databaseInfo
+     *
+     * @return int
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function createAndGetId(array $databaseInfo)
+    {
+        /** @var array $response */
+        $response = $this->detectAndCastResponseToType($this->create($databaseInfo), 'array');
+
+        return $response['poi_id'];
+    }
+
+    /**
+     * Update a POI.
+     *
+     * @param int   $poiId
+     * @param array $baseInfo
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(int $poiId, array $baseInfo)
+    {
+        $params = [
+            'business' => [
+                'base_info' => array_merge($baseInfo, ['poi_id' => $poiId]),
+            ],
+        ];
+
+        return $this->httpPostJson('cgi-bin/poi/updatepoi', $params);
+    }
+
+    /**
+     * Delete a POI.
+     *
+     * @param int $poiId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete(int $poiId)
+    {
+        return $this->httpPostJson('cgi-bin/poi/delpoi', ['poi_id' => $poiId]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/POI/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/POI/ServiceProvider.php
new file mode 100644
index 0000000..156440b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/POI/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\POI;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['poi'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Semantic/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/Client.php
new file mode 100644
index 0000000..e83792f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/Client.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Semantic;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get the semantic content of giving string.
+     *
+     * @param string $keyword
+     * @param string $categories
+     * @param array  $optional
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function query(string $keyword, string $categories, array $optional = [])
+    {
+        $params = [
+            'query' => $keyword,
+            'category' => $categories,
+            'appid' => $this->app['config']['app_id'],
+        ];
+
+        return $this->httpPostJson('semantic/semproxy/search', array_merge($params, $optional));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Semantic/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/ServiceProvider.php
new file mode 100644
index 0000000..835b7fc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/ServiceProvider.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Semantic;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['semantic'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Server/Guard.php b/vendor/overtrue/wechat/src/OfficialAccount/Server/Guard.php
new file mode 100644
index 0000000..c723847
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Server/Guard.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Server;
+
+use EasyWeChat\Kernel\ServerGuard;
+
+/**
+ * Class Guard.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Guard extends ServerGuard
+{
+    /**
+     * @return bool
+     */
+    protected function shouldReturnRawResponse(): bool
+    {
+        return !is_null($this->app['request']->get('echostr'));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Server/Handlers/EchoStrHandler.php b/vendor/overtrue/wechat/src/OfficialAccount/Server/Handlers/EchoStrHandler.php
new file mode 100644
index 0000000..f076abf
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Server/Handlers/EchoStrHandler.php
@@ -0,0 +1,51 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Server\Handlers;
+
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+use EasyWeChat\Kernel\Decorators\FinallyResult;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Class EchoStrHandler.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class EchoStrHandler implements EventHandlerInterface
+{
+    /**
+     * @var ServiceContainer
+     */
+    protected $app;
+
+    /**
+     * EchoStrHandler constructor.
+     *
+     * @param ServiceContainer $app
+     */
+    public function __construct(ServiceContainer $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * @param mixed $payload
+     *
+     * @return FinallyResult|null
+     */
+    public function handle($payload = null)
+    {
+        if ($str = $this->app['request']->get('echostr')) {
+            return new FinallyResult($str);
+        }
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Server/ServiceProvider.php
new file mode 100644
index 0000000..0c6716b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Server/ServiceProvider.php
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Server;
+
+use EasyWeChat\Kernel\Encryptor;
+use EasyWeChat\OfficialAccount\Server\Handlers\EchoStrHandler;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        !isset($app['encryptor']) && $app['encryptor'] = function ($app) {
+            return new Encryptor(
+                $app['config']['app_id'],
+                $app['config']['token'],
+                $app['config']['aes_key']
+            );
+        };
+
+        !isset($app['server']) && $app['server'] = function ($app) {
+            $guard = new Guard($app);
+            $guard->push(new EchoStrHandler($app));
+
+            return $guard;
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/Client.php
new file mode 100644
index 0000000..c401008
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/Client.php
@@ -0,0 +1,81 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @param array $data
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function register($data)
+    {
+        return $this->httpPostJson('shakearound/account/register', $data);
+    }
+
+    /**
+     * Get audit status.
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function status()
+    {
+        return $this->httpGet('shakearound/account/auditstatus');
+    }
+
+    /**
+     * Get shake info.
+     *
+     * @param string $ticket
+     * @param bool   $needPoi
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function user(string $ticket, bool $needPoi = false)
+    {
+        $params = [
+            'ticket' => $ticket,
+        ];
+
+        if ($needPoi) {
+            $params['need_poi'] = 1;
+        }
+
+        return $this->httpPostJson('shakearound/user/getshakeinfo', $params);
+    }
+
+    /**
+     * @param string $ticket
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     */
+    public function userWithPoi(string $ticket)
+    {
+        return $this->user($ticket, true);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/DeviceClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/DeviceClient.php
new file mode 100644
index 0000000..859a120
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/DeviceClient.php
@@ -0,0 +1,190 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class DeviceClient.
+ *
+ * @author allen05ren <allen05ren@outlook.com>
+ */
+class DeviceClient extends BaseClient
+{
+    /**
+     * @param array $data
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function apply(array $data)
+    {
+        return $this->httpPostJson('shakearound/device/applyid', $data);
+    }
+
+    /**
+     * Get audit status.
+     *
+     * @param int $applyId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function status(int $applyId)
+    {
+        $params = [
+            'apply_id' => $applyId,
+        ];
+
+        return $this->httpPostJson('shakearound/device/applystatus', $params);
+    }
+
+    /**
+     * Update a device comment.
+     *
+     * @param array  $deviceIdentifier
+     * @param string $comment
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(array $deviceIdentifier, string $comment)
+    {
+        $params = [
+            'device_identifier' => $deviceIdentifier,
+            'comment' => $comment,
+        ];
+
+        return $this->httpPostJson('shakearound/device/update', $params);
+    }
+
+    /**
+     * Bind location for device.
+     *
+     * @param array $deviceIdentifier
+     * @param int   $poiId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function bindPoi(array $deviceIdentifier, int $poiId)
+    {
+        $params = [
+            'device_identifier' => $deviceIdentifier,
+            'poi_id' => $poiId,
+        ];
+
+        return $this->httpPostJson('shakearound/device/bindlocation', $params);
+    }
+
+    /**
+     * @param array  $deviceIdentifier
+     * @param int    $poiId
+     * @param string $appId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function bindThirdPoi(array $deviceIdentifier, int $poiId, string $appId)
+    {
+        $params = [
+            'device_identifier' => $deviceIdentifier,
+            'poi_id' => $poiId,
+            'type' => 2,
+            'poi_appid' => $appId,
+        ];
+
+        return $this->httpPostJson('shakearound/device/bindlocation', $params);
+    }
+
+    /**
+     * Fetch batch of devices by deviceIds.
+     *
+     * @param array $deviceIdentifiers
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function listByIds(array $deviceIdentifiers)
+    {
+        $params = [
+            'type' => 1,
+            'device_identifiers' => $deviceIdentifiers,
+        ];
+
+        return $this->search($params);
+    }
+
+    /**
+     * Pagination to get batch of devices.
+     *
+     * @param int $lastId
+     * @param int $count
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function list(int $lastId, int $count)
+    {
+        $params = [
+            'type' => 2,
+            'last_seen' => $lastId,
+            'count' => $count,
+        ];
+
+        return $this->search($params);
+    }
+
+    /**
+     * Fetch batch of devices by applyId.
+     *
+     * @param int $applyId
+     * @param int $lastId
+     * @param int $count
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     */
+    public function listByApplyId(int $applyId, int $lastId, int $count)
+    {
+        $params = [
+            'type' => 3,
+            'apply_id' => $applyId,
+            'last_seen' => $lastId,
+            'count' => $count,
+        ];
+
+        return $this->search($params);
+    }
+
+    /**
+     * Fetch batch of devices.
+     *
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function search(array $params)
+    {
+        return $this->httpPostJson('shakearound/device/search', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/GroupClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/GroupClient.php
new file mode 100644
index 0000000..9fd578d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/GroupClient.php
@@ -0,0 +1,167 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class GroupClient.
+ *
+ * @author allen05ren <allen05ren@outlook.com>
+ */
+class GroupClient extends BaseClient
+{
+    /**
+     * Add device group.
+     *
+     * @param string $name
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create(string $name)
+    {
+        $params = [
+            'group_name' => $name,
+        ];
+
+        return $this->httpPostJson('shakearound/device/group/add', $params);
+    }
+
+    /**
+     * Update a device group name.
+     *
+     * @param int    $groupId
+     * @param string $name
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(int $groupId, string $name)
+    {
+        $params = [
+            'group_id' => $groupId,
+            'group_name' => $name,
+        ];
+
+        return $this->httpPostJson('shakearound/device/group/update', $params);
+    }
+
+    /**
+     * Delete device group.
+     *
+     * @param int $groupId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete(int $groupId)
+    {
+        $params = [
+            'group_id' => $groupId,
+        ];
+
+        return $this->httpPostJson('shakearound/device/group/delete', $params);
+    }
+
+    /**
+     * List all device groups.
+     *
+     * @param int $begin
+     * @param int $count
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list(int $begin, int $count)
+    {
+        $params = [
+            'begin' => $begin,
+            'count' => $count,
+        ];
+
+        return $this->httpPostJson('shakearound/device/group/getlist', $params);
+    }
+
+    /**
+     * Get detail of a device group.
+     *
+     * @param int $groupId
+     * @param int $begin
+     * @param int $count
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(int $groupId, int $begin, int $count)
+    {
+        $params = [
+            'group_id' => $groupId,
+            'begin' => $begin,
+            'count' => $count,
+        ];
+
+        return $this->httpPostJson('shakearound/device/group/getdetail', $params);
+    }
+
+    /**
+     * Add  one or more devices to a device group.
+     *
+     * @param int   $groupId
+     * @param array $deviceIdentifiers
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function addDevices(int $groupId, array $deviceIdentifiers)
+    {
+        $params = [
+            'group_id' => $groupId,
+            'device_identifiers' => $deviceIdentifiers,
+        ];
+
+        return $this->httpPostJson('shakearound/device/group/adddevice', $params);
+    }
+
+    /**
+     * Remove one or more devices from a device group.
+     *
+     * @param int   $groupId
+     * @param array $deviceIdentifiers
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function removeDevices(int $groupId, array $deviceIdentifiers)
+    {
+        $params = [
+            'group_id' => $groupId,
+            'device_identifiers' => $deviceIdentifiers,
+        ];
+
+        return $this->httpPostJson('shakearound/device/group/deletedevice', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/MaterialClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/MaterialClient.php
new file mode 100644
index 0000000..edc40b2
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/MaterialClient.php
@@ -0,0 +1,44 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class MaterialClient.
+ *
+ * @author allen05ren <allen05ren@outlook.com>
+ */
+class MaterialClient extends BaseClient
+{
+    /**
+     * Upload image material.
+     *
+     * @param string $path
+     * @param string $type
+     *
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function uploadImage(string $path, string $type = 'icon')
+    {
+        if (!file_exists($path) || !is_readable($path)) {
+            throw new InvalidArgumentException(sprintf('File does not exist, or the file is unreadable: "%s"', $path));
+        }
+
+        return $this->httpUpload('shakearound/material/add', ['media' => $path], [], ['type' => strtolower($type)]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/PageClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/PageClient.php
new file mode 100644
index 0000000..73ba1ac
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/PageClient.php
@@ -0,0 +1,110 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class PageClient.
+ *
+ * @author allen05ren <allen05ren@outlook.com>
+ */
+class PageClient extends BaseClient
+{
+    /**
+     * @param array $data
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create(array $data)
+    {
+        return $this->httpPostJson('shakearound/page/add', $data);
+    }
+
+    /**
+     * @param int   $pageId
+     * @param array $data
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(int $pageId, array $data)
+    {
+        return $this->httpPostJson('shakearound/page/update', array_merge(['page_id' => $pageId], $data));
+    }
+
+    /**
+     * Fetch batch of pages by pageIds.
+     *
+     * @param array $pageIds
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function listByIds(array $pageIds)
+    {
+        $params = [
+            'type' => 1,
+            'page_ids' => $pageIds,
+        ];
+
+        return $this->httpPostJson('shakearound/page/search', $params);
+    }
+
+    /**
+     * Pagination to get batch of pages.
+     *
+     * @param int $begin
+     * @param int $count
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list(int $begin, int $count)
+    {
+        $params = [
+            'type' => 2,
+            'begin' => $begin,
+            'count' => $count,
+        ];
+
+        return $this->httpPostJson('shakearound/page/search', $params);
+    }
+
+    /**
+     * delete a page.
+     *
+     * @param int $pageId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete(int $pageId)
+    {
+        $params = [
+            'page_id' => $pageId,
+        ];
+
+        return $this->httpPostJson('shakearound/page/delete', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/RelationClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/RelationClient.php
new file mode 100644
index 0000000..de80911
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/RelationClient.php
@@ -0,0 +1,87 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class RelationClient.
+ *
+ * @author allen05ren <allen05ren@outlook.com>
+ */
+class RelationClient extends BaseClient
+{
+    /**
+     * Bind pages for device.
+     *
+     * @param array $deviceIdentifier
+     * @param array $pageIds
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function bindPages(array $deviceIdentifier, array $pageIds)
+    {
+        $params = [
+            'device_identifier' => $deviceIdentifier,
+            'page_ids' => $pageIds,
+        ];
+
+        return $this->httpPostJson('shakearound/device/bindpage', $params);
+    }
+
+    /**
+     * Get pageIds by deviceId.
+     *
+     * @param array $deviceIdentifier
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function listByDeviceId(array $deviceIdentifier)
+    {
+        $params = [
+            'type' => 1,
+            'device_identifier' => $deviceIdentifier,
+        ];
+
+        return $this->httpPostJson('shakearound/relation/search', $params);
+    }
+
+    /**
+     * Get devices by pageId.
+     *
+     * @param int $pageId
+     * @param int $begin
+     * @param int $count
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function listByPageId(int $pageId, int $begin, int $count)
+    {
+        $params = [
+            'type' => 2,
+            'page_id' => $pageId,
+            'begin' => $begin,
+            'count' => $count,
+        ];
+
+        return $this->httpPostJson('shakearound/relation/search', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ServiceProvider.php
new file mode 100644
index 0000000..a13d2b0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ServiceProvider.php
@@ -0,0 +1,57 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author allen05ren <allen05ren@outlook.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['shake_around'] = function ($app) {
+            return new ShakeAround($app);
+        };
+
+        $app['shake_around.device'] = function ($app) {
+            return new DeviceClient($app);
+        };
+
+        $app['shake_around.page'] = function ($app) {
+            return new PageClient($app);
+        };
+
+        $app['shake_around.material'] = function ($app) {
+            return new MaterialClient($app);
+        };
+
+        $app['shake_around.group'] = function ($app) {
+            return new GroupClient($app);
+        };
+
+        $app['shake_around.relation'] = function ($app) {
+            return new RelationClient($app);
+        };
+
+        $app['shake_around.stats'] = function ($app) {
+            return new StatsClient($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ShakeAround.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ShakeAround.php
new file mode 100644
index 0000000..666a38a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ShakeAround.php
@@ -0,0 +1,44 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class Card.
+ *
+ * @author overtrue <i@overtrue.me>
+ *
+ * @property \EasyWeChat\OfficialAccount\ShakeAround\DeviceClient   $device
+ * @property \EasyWeChat\OfficialAccount\ShakeAround\GroupClient    $group
+ * @property \EasyWeChat\OfficialAccount\ShakeAround\MaterialClient $material
+ * @property \EasyWeChat\OfficialAccount\ShakeAround\RelationClient $relation
+ * @property \EasyWeChat\OfficialAccount\ShakeAround\StatsClient    $stats
+ */
+class ShakeAround extends Client
+{
+    /**
+     * @param string $property
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function __get($property)
+    {
+        if (isset($this->app["shake_around.{$property}"])) {
+            return $this->app["shake_around.{$property}"];
+        }
+
+        throw new InvalidArgumentException(sprintf('No shake_around service named "%s".', $property));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/StatsClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/StatsClient.php
new file mode 100644
index 0000000..a4b55eb
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/StatsClient.php
@@ -0,0 +1,110 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class StatsClient.
+ *
+ * @author allen05ren <allen05ren@outlook.com>
+ */
+class StatsClient extends BaseClient
+{
+    /**
+     * Fetch statistics data by deviceId.
+     *
+     * @param array $deviceIdentifier
+     * @param int   $beginTime        (Unix timestamp)
+     * @param int   $endTime          (Unix timestamp)
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function deviceSummary(array $deviceIdentifier, int $beginTime, int $endTime)
+    {
+        $params = [
+            'device_identifier' => $deviceIdentifier,
+            'begin_date' => $beginTime,
+            'end_date' => $endTime,
+        ];
+
+        return $this->httpPostJson('shakearound/statistics/device', $params);
+    }
+
+    /**
+     * Fetch all devices statistics data by date.
+     *
+     * @param int $timestamp
+     * @param int $pageIndex
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function devicesSummary(int $timestamp, int $pageIndex)
+    {
+        $params = [
+            'date' => $timestamp,
+            'page_index' => $pageIndex,
+        ];
+
+        return $this->httpPostJson('shakearound/statistics/devicelist', $params);
+    }
+
+    /**
+     * Fetch statistics data by pageId.
+     *
+     * @param int $pageId
+     * @param int $beginTime (Unix timestamp)
+     * @param int $endTime   (Unix timestamp)
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function pageSummary(int $pageId, int $beginTime, int $endTime)
+    {
+        $params = [
+            'page_id' => $pageId,
+            'begin_date' => $beginTime,
+            'end_date' => $endTime,
+        ];
+
+        return $this->httpPostJson('shakearound/statistics/page', $params);
+    }
+
+    /**
+     * Fetch all pages statistics data by date.
+     *
+     * @param int $timestamp
+     * @param int $pageIndex
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function pagesSummary(int $timestamp, int $pageIndex)
+    {
+        $params = [
+            'date' => $timestamp,
+            'page_index' => $pageIndex,
+        ];
+
+        return $this->httpPostJson('shakearound/statistics/pagelist', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Store/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Store/Client.php
new file mode 100644
index 0000000..3643096
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Store/Client.php
@@ -0,0 +1,209 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Store;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author bigface <saybye720@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get WXA supported categories.
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function categories()
+    {
+        return $this->httpGet('wxa/get_merchant_category');
+    }
+
+    /**
+     * Get district from tencent map .
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function districts()
+    {
+        return $this->httpGet('wxa/get_district');
+    }
+
+    /**
+     * Search store from tencent map.
+     *
+     * @param int    $districtId
+     * @param string $keyword
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function searchFromMap(int $districtId, string $keyword)
+    {
+        $params = [
+            'districtid' => $districtId,
+            'keyword' => $keyword,
+        ];
+
+        return $this->httpPostJson('wxa/search_map_poi', $params);
+    }
+
+    /**
+     * Get store check status.
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getStatus()
+    {
+        return $this->httpPostJson('wxa/get_merchant_audit_info');
+    }
+
+    /**
+     * Create a merchant.
+     *
+     * @param array $baseInfo
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function createMerchant(array $baseInfo)
+    {
+        return $this->httpPostJson('wxa/apply_merchant', $baseInfo);
+    }
+
+    /**
+     * Update a merchant.
+     *
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function updateMerchant(array $params)
+    {
+        return $this->httpPostJson('wxa/modify_merchant', $params);
+    }
+
+    /**
+     * Create a store from tencent map.
+     *
+     * @param array $baseInfo
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function createFromMap(array $baseInfo)
+    {
+        return $this->httpPostJson('wxa/create_map_poi', $baseInfo);
+    }
+
+    /**
+     * Create a store.
+     *
+     * @param array $baseInfo
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create(array $baseInfo)
+    {
+        return $this->httpPostJson('wxa/add_store', $baseInfo);
+    }
+
+    /**
+     * Update a store.
+     *
+     * @param int   $poiId
+     * @param array $baseInfo
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(int $poiId, array $baseInfo)
+    {
+        $params = array_merge($baseInfo, ['poi_id' => $poiId]);
+
+        return $this->httpPostJson('wxa/update_store', $params);
+    }
+
+    /**
+     * Get store by ID.
+     *
+     * @param int $poiId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(int $poiId)
+    {
+        return $this->httpPostJson('wxa/get_store_info', ['poi_id' => $poiId]);
+    }
+
+    /**
+     * List store.
+     *
+     * @param int $offset
+     * @param int $limit
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list(int $offset = 0, int $limit = 10)
+    {
+        $params = [
+            'offset' => $offset,
+            'limit' => $limit,
+        ];
+
+        return $this->httpPostJson('wxa/get_store_list', $params);
+    }
+
+    /**
+     * Delete a store.
+     *
+     * @param int $poiId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete(int $poiId)
+    {
+        return $this->httpPostJson('wxa/del_store', ['poi_id' => $poiId]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Store/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Store/ServiceProvider.php
new file mode 100644
index 0000000..f5c48d7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Store/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Store;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author bigface <saybye720@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['store'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/Client.php
new file mode 100644
index 0000000..be1fa4d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/Client.php
@@ -0,0 +1,234 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\TemplateMessage;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use ReflectionClass;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    const API_SEND = 'cgi-bin/message/template/send';
+
+    /**
+     * Attributes.
+     *
+     * @var array
+     */
+    protected $message = [
+        'touser' => '',
+        'template_id' => '',
+        'url' => '',
+        'data' => [],
+        'miniprogram' => '',
+    ];
+
+    /**
+     * Required attributes.
+     *
+     * @var array
+     */
+    protected $required = ['touser', 'template_id'];
+
+    /**
+     * Set industry.
+     *
+     * @param int $industryOne
+     * @param int $industryTwo
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setIndustry($industryOne, $industryTwo)
+    {
+        $params = [
+            'industry_id1' => $industryOne,
+            'industry_id2' => $industryTwo,
+        ];
+
+        return $this->httpPostJson('cgi-bin/template/api_set_industry', $params);
+    }
+
+    /**
+     * Get industry.
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getIndustry()
+    {
+        return $this->httpPostJson('cgi-bin/template/get_industry');
+    }
+
+    /**
+     * Add a template and get template ID.
+     *
+     * @param string $shortId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function addTemplate($shortId)
+    {
+        $params = ['template_id_short' => $shortId];
+
+        return $this->httpPostJson('cgi-bin/template/api_add_template', $params);
+    }
+
+    /**
+     * Get private templates.
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getPrivateTemplates()
+    {
+        return $this->httpPostJson('cgi-bin/template/get_all_private_template');
+    }
+
+    /**
+     * Delete private template.
+     *
+     * @param string $templateId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function deletePrivateTemplate($templateId)
+    {
+        $params = ['template_id' => $templateId];
+
+        return $this->httpPostJson('cgi-bin/template/del_private_template', $params);
+    }
+
+    /**
+     * Send a template message.
+     *
+     * @param array $data
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function send(array $data = [])
+    {
+        $params = $this->formatMessage($data);
+
+        $this->restoreMessage();
+
+        return $this->httpPostJson(static::API_SEND, $params);
+    }
+
+    /**
+     * Send template-message for subscription.
+     *
+     * @param array $data
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function sendSubscription(array $data = [])
+    {
+        $params = $this->formatMessage($data);
+
+        $this->restoreMessage();
+
+        return $this->httpPostJson('cgi-bin/message/template/subscribe', $params);
+    }
+
+    /**
+     * @param array $data
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    protected function formatMessage(array $data = [])
+    {
+        $params = array_merge($this->message, $data);
+
+        foreach ($params as $key => $value) {
+            if (in_array($key, $this->required, true) && empty($value) && empty($this->message[$key])) {
+                throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key));
+            }
+
+            $params[$key] = empty($value) ? $this->message[$key] : $value;
+        }
+
+        $params['data'] = $this->formatData($params['data'] ?? []);
+
+        return $params;
+    }
+
+    /**
+     * @param array $data
+     *
+     * @return array
+     */
+    protected function formatData(array $data)
+    {
+        $formatted = [];
+
+        foreach ($data as $key => $value) {
+            if (is_array($value)) {
+                if (isset($value['value'])) {
+                    $formatted[$key] = $value;
+
+                    continue;
+                }
+
+                if (count($value) >= 2) {
+                    $value = [
+                        'value' => $value[0],
+                        'color' => $value[1],
+                    ];
+                }
+            } else {
+                $value = [
+                    'value' => strval($value),
+                ];
+            }
+
+            $formatted[$key] = $value;
+        }
+
+        return $formatted;
+    }
+
+    /**
+     * Restore message.
+     */
+    protected function restoreMessage()
+    {
+        $this->message = (new ReflectionClass(static::class))->getDefaultProperties()['message'];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/ServiceProvider.php
new file mode 100644
index 0000000..98476fc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\TemplateMessage;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['template_message'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/User/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/User/ServiceProvider.php
new file mode 100644
index 0000000..c11d8b3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/User/ServiceProvider.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\User;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['user'] = function ($app) {
+            return new UserClient($app);
+        };
+
+        $app['user_tag'] = function ($app) {
+            return new TagClient($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/User/TagClient.php b/vendor/overtrue/wechat/src/OfficialAccount/User/TagClient.php
new file mode 100644
index 0000000..d41e363
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/User/TagClient.php
@@ -0,0 +1,175 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\User;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class TagClient.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class TagClient extends BaseClient
+{
+    /**
+     * Create tag.
+     *
+     * @param string $name
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create(string $name)
+    {
+        $params = [
+            'tag' => ['name' => $name],
+        ];
+
+        return $this->httpPostJson('cgi-bin/tags/create', $params);
+    }
+
+    /**
+     * List all tags.
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function list()
+    {
+        return $this->httpGet('cgi-bin/tags/get');
+    }
+
+    /**
+     * Update a tag name.
+     *
+     * @param int    $tagId
+     * @param string $name
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(int $tagId, string $name)
+    {
+        $params = [
+            'tag' => [
+                'id' => $tagId,
+                'name' => $name,
+            ],
+        ];
+
+        return $this->httpPostJson('cgi-bin/tags/update', $params);
+    }
+
+    /**
+     * Delete tag.
+     *
+     * @param int $tagId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete(int $tagId)
+    {
+        $params = [
+            'tag' => ['id' => $tagId],
+        ];
+
+        return $this->httpPostJson('cgi-bin/tags/delete', $params);
+    }
+
+    /**
+     * Get user tags.
+     *
+     * @param string $openid
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function userTags(string $openid)
+    {
+        $params = ['openid' => $openid];
+
+        return $this->httpPostJson('cgi-bin/tags/getidlist', $params);
+    }
+
+    /**
+     * Get users from a tag.
+     *
+     * @param int    $tagId
+     * @param string $nextOpenId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function usersOfTag(int $tagId, string $nextOpenId = '')
+    {
+        $params = [
+            'tagid' => $tagId,
+            'next_openid' => $nextOpenId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/user/tag/get', $params);
+    }
+
+    /**
+     * Batch tag users.
+     *
+     * @param array $openids
+     * @param int   $tagId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function tagUsers(array $openids, int $tagId)
+    {
+        $params = [
+            'openid_list' => $openids,
+            'tagid' => $tagId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/tags/members/batchtagging', $params);
+    }
+
+    /**
+     * Untag users from a tag.
+     *
+     * @param array $openids
+     * @param int   $tagId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function untagUsers(array $openids, int $tagId)
+    {
+        $params = [
+            'openid_list' => $openids,
+            'tagid' => $tagId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/tags/members/batchuntagging', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/User/UserClient.php b/vendor/overtrue/wechat/src/OfficialAccount/User/UserClient.php
new file mode 100644
index 0000000..6b9491f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/User/UserClient.php
@@ -0,0 +1,172 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\User;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class UserClient.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class UserClient extends BaseClient
+{
+    /**
+     * Fetch a user by open id.
+     *
+     * @param string $openid
+     * @param string $lang
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function get(string $openid, string $lang = 'zh_CN')
+    {
+        $params = [
+            'openid' => $openid,
+            'lang' => $lang,
+        ];
+
+        return $this->httpGet('cgi-bin/user/info', $params);
+    }
+
+    /**
+     * Batch get users.
+     *
+     * @param array  $openids
+     * @param string $lang
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function select(array $openids, string $lang = 'zh_CN')
+    {
+        return $this->httpPostJson('cgi-bin/user/info/batchget', [
+            'user_list' => array_map(function ($openid) use ($lang) {
+                return [
+                    'openid' => $openid,
+                    'lang' => $lang,
+                ];
+            }, $openids),
+        ]);
+    }
+
+    /**
+     * List users.
+     *
+     * @param string $nextOpenId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function list(string $nextOpenId = null)
+    {
+        $params = ['next_openid' => $nextOpenId];
+
+        return $this->httpGet('cgi-bin/user/get', $params);
+    }
+
+    /**
+     * Set user remark.
+     *
+     * @param string $openid
+     * @param string $remark
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function remark(string $openid, string $remark)
+    {
+        $params = [
+            'openid' => $openid,
+            'remark' => $remark,
+        ];
+
+        return $this->httpPostJson('cgi-bin/user/info/updateremark', $params);
+    }
+
+    /**
+     * Get black list.
+     *
+     * @param string|null $beginOpenid
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function blacklist(string $beginOpenid = null)
+    {
+        $params = ['begin_openid' => $beginOpenid];
+
+        return $this->httpPostJson('cgi-bin/tags/members/getblacklist', $params);
+    }
+
+    /**
+     * Batch block user.
+     *
+     * @param array|string $openidList
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function block($openidList)
+    {
+        $params = ['openid_list' => (array) $openidList];
+
+        return $this->httpPostJson('cgi-bin/tags/members/batchblacklist', $params);
+    }
+
+    /**
+     * Batch unblock user.
+     *
+     * @param array $openidList
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function unblock($openidList)
+    {
+        $params = ['openid_list' => (array) $openidList];
+
+        return $this->httpPostJson('cgi-bin/tags/members/batchunblacklist', $params);
+    }
+
+    /**
+     * @param string $oldAppId
+     * @param array  $openidList
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function changeOpenid(string $oldAppId, array $openidList)
+    {
+        $params = [
+            'from_appid' => $oldAppId,
+            'openid_list' => $openidList,
+        ];
+
+        return $this->httpPostJson('cgi-bin/changeopenid', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/CardClient.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/CardClient.php
new file mode 100644
index 0000000..76a4e6a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/CardClient.php
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\WiFi;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class CardClient.
+ *
+ * @author her-cat <i@her-cat.com>
+ */
+class CardClient extends BaseClient
+{
+    /**
+     * Set shop card coupon delivery information.
+     *
+     * @param array $data
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function set(array $data)
+    {
+        return $this->httpPostJson('bizwifi/couponput/set', $data);
+    }
+
+    /**
+     * Get shop card coupon delivery information.
+     *
+     * @param int $shopId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(int $shopId = 0)
+    {
+        return $this->httpPostJson('bizwifi/couponput/get', ['shop_id' => $shopId]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/Client.php
new file mode 100644
index 0000000..076957d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/Client.php
@@ -0,0 +1,98 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\WiFi;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author her-cat <i@her-cat.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get Wi-Fi statistics.
+     *
+     * @param string $beginDate
+     * @param string $endDate
+     * @param int    $shopId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function summary(string $beginDate, string $endDate, int $shopId = -1)
+    {
+        $data = [
+            'begin_date' => $beginDate,
+            'end_date' => $endDate,
+            'shop_id' => $shopId,
+        ];
+
+        return $this->httpPostJson('bizwifi/statistics/list', $data);
+    }
+
+    /**
+     * Get the material QR code.
+     *
+     * @param int    $shopId
+     * @param string $ssid
+     * @param int    $type
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getQrCodeUrl(int $shopId, string $ssid, int $type = 0)
+    {
+        $data = [
+            'shop_id' => $shopId,
+            'ssid' => $ssid,
+            'img_id' => $type,
+        ];
+
+        return $this->httpPostJson('bizwifi/qrcode/get', $data);
+    }
+
+    /**
+     * Wi-Fi completion page jump applet.
+     *
+     * @param array $data
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setFinishPage(array $data)
+    {
+        return $this->httpPostJson('bizwifi/finishpage/set', $data);
+    }
+
+    /**
+     * Set the top banner jump applet.
+     *
+     * @param array $data
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setHomePage(array $data)
+    {
+        return $this->httpPostJson('bizwifi/homepage/set', $data);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/DeviceClient.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/DeviceClient.php
new file mode 100644
index 0000000..8fecbf7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/DeviceClient.php
@@ -0,0 +1,127 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\WiFi;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class DeviceClient.
+ *
+ * @author her-cat <i@her-cat.com>
+ */
+class DeviceClient extends BaseClient
+{
+    /**
+     * Add a password device.
+     *
+     * @param int    $shopId
+     * @param string $ssid
+     * @param string $password
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function addPasswordDevice(int $shopId, string $ssid, string $password)
+    {
+        $data = [
+            'shop_id' => $shopId,
+            'ssid' => $ssid,
+            'password' => $password,
+        ];
+
+        return $this->httpPostJson('bizwifi/device/add', $data);
+    }
+
+    /**
+     * Add a portal device.
+     *
+     * @param int    $shopId
+     * @param string $ssid
+     * @param bool   $reset
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function addPortalDevice(int $shopId, string $ssid, bool $reset = false)
+    {
+        $data = [
+            'shop_id' => $shopId,
+            'ssid' => $ssid,
+            'reset' => $reset,
+        ];
+
+        return $this->httpPostJson('bizwifi/apportal/register', $data);
+    }
+
+    /**
+     * Delete device by MAC address.
+     *
+     * @param string $macAddress
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete(string $macAddress)
+    {
+        return $this->httpPostJson('bizwifi/device/delete', ['bssid' => $macAddress]);
+    }
+
+    /**
+     * Get a list of devices.
+     *
+     * @param int $page
+     * @param int $size
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list(int $page = 1, int $size = 10)
+    {
+        $data = [
+            'pageindex' => $page,
+            'pagesize' => $size,
+        ];
+
+        return $this->httpPostJson('bizwifi/device/list', $data);
+    }
+
+    /**
+     * Get a list of devices by shop ID.
+     *
+     * @param int $shopId
+     * @param int $page
+     * @param int $size
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function listByShopId(int $shopId, int $page = 1, int $size = 10)
+    {
+        $data = [
+            'shop_id' => $shopId,
+            'pageindex' => $page,
+            'pagesize' => $size,
+        ];
+
+        return $this->httpPostJson('bizwifi/device/list', $data);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ServiceProvider.php
new file mode 100644
index 0000000..7a28b51
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ServiceProvider.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\WiFi;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author her-cat <i@her-cat.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function register(Container $app)
+    {
+        $app['wifi'] = function ($app) {
+            return new Client($app);
+        };
+
+        $app['wifi_card'] = function ($app) {
+            return new CardClient($app);
+        };
+
+        $app['wifi_device'] = function ($app) {
+            return new DeviceClient($app);
+        };
+
+        $app['wifi_shop'] = function ($app) {
+            return new ShopClient($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ShopClient.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ShopClient.php
new file mode 100644
index 0000000..34f1c97
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ShopClient.php
@@ -0,0 +1,100 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\WiFi;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class ShopClient.
+ *
+ * @author her-cat <i@her-cat.com>
+ */
+class ShopClient extends BaseClient
+{
+    /**
+     * Get shop Wi-Fi information.
+     *
+     * @param int $shopId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(int $shopId)
+    {
+        return $this->httpPostJson('bizwifi/shop/get', ['shop_id' => $shopId]);
+    }
+
+    /**
+     * Get a list of Wi-Fi shops.
+     *
+     * @param int $page
+     * @param int $size
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list(int $page = 1, int $size = 10)
+    {
+        $data = [
+            'pageindex' => $page,
+            'pagesize' => $size,
+        ];
+
+        return $this->httpPostJson('bizwifi/shop/list', $data);
+    }
+
+    /**
+     * Update shop Wi-Fi information.
+     *
+     * @param int   $shopId
+     * @param array $data
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(int $shopId, array $data)
+    {
+        $data = array_merge(['shop_id' => $shopId], $data);
+
+        return $this->httpPostJson('bizwifi/shop/update', $data);
+    }
+
+    /**
+     * Clear shop network and equipment.
+     *
+     * @param int         $shopId
+     * @param string|null $ssid
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function clearDevice(int $shopId, string $ssid = null)
+    {
+        $data = [
+            'shop_id' => $shopId,
+        ];
+
+        if (!is_null($ssid)) {
+            $data['ssid'] = $ssid;
+        }
+
+        return $this->httpPostJson('bizwifi/shop/clean', $data);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Application.php b/vendor/overtrue/wechat/src/OpenPlatform/Application.php
new file mode 100644
index 0000000..459bb96
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Application.php
@@ -0,0 +1,220 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform;
+
+use EasyWeChat\Kernel\ServiceContainer;
+use EasyWeChat\MiniProgram\Encryptor;
+use EasyWeChat\OpenPlatform\Authorizer\Auth\AccessToken;
+use EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Application as MiniProgram;
+use EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Auth\Client;
+use EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Account\Client as AccountClient;
+use EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Application as OfficialAccount;
+use EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\OAuth\ComponentDelegate;
+use EasyWeChat\OpenPlatform\Authorizer\Server\Guard;
+
+/**
+ * Class Application.
+ *
+ * @property \EasyWeChat\OpenPlatform\Server\Guard        $server
+ * @property \EasyWeChat\OpenPlatform\Auth\AccessToken    $access_token
+ * @property \EasyWeChat\OpenPlatform\CodeTemplate\Client $code_template
+ * @property \EasyWeChat\OpenPlatform\Component\Client    $component
+ *
+ * @method mixed handleAuthorize(string $authCode = null)
+ * @method mixed getAuthorizer(string $appId)
+ * @method mixed getAuthorizerOption(string $appId, string $name)
+ * @method mixed setAuthorizerOption(string $appId, string $name, string $value)
+ * @method mixed getAuthorizers(int $offset = 0, int $count = 500)
+ * @method mixed createPreAuthorizationCode()
+ */
+class Application extends ServiceContainer
+{
+    /**
+     * @var array
+     */
+    protected $providers = [
+        Auth\ServiceProvider::class,
+        Base\ServiceProvider::class,
+        Server\ServiceProvider::class,
+        CodeTemplate\ServiceProvider::class,
+        Component\ServiceProvider::class,
+    ];
+
+    /**
+     * @var array
+     */
+    protected $defaultConfig = [
+        'http' => [
+            'timeout' => 5.0,
+            'base_uri' => 'https://api.weixin.qq.com/',
+        ],
+    ];
+
+    /**
+     * Creates the officialAccount application.
+     *
+     * @param string                                                    $appId
+     * @param string|null                                               $refreshToken
+     * @param \EasyWeChat\OpenPlatform\Authorizer\Auth\AccessToken|null $accessToken
+     *
+     * @return \EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Application
+     */
+    public function officialAccount(string $appId, string $refreshToken = null, AccessToken $accessToken = null): OfficialAccount
+    {
+        $application = new OfficialAccount($this->getAuthorizerConfig($appId, $refreshToken), $this->getReplaceServices($accessToken) + [
+            'encryptor' => $this['encryptor'],
+
+            'account' => function ($app) {
+                return new AccountClient($app, $this);
+            },
+        ]);
+
+        $application->extend('oauth', function ($socialite) {
+            /* @var \Overtrue\Socialite\Providers\WeChatProvider $socialite */
+            return $socialite->component(new ComponentDelegate($this));
+        });
+
+        return $application;
+    }
+
+    /**
+     * Creates the miniProgram application.
+     *
+     * @param string                                                    $appId
+     * @param string|null                                               $refreshToken
+     * @param \EasyWeChat\OpenPlatform\Authorizer\Auth\AccessToken|null $accessToken
+     *
+     * @return \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Application
+     */
+    public function miniProgram(string $appId, string $refreshToken = null, AccessToken $accessToken = null): MiniProgram
+    {
+        return new MiniProgram($this->getAuthorizerConfig($appId, $refreshToken), $this->getReplaceServices($accessToken) + [
+            'encryptor' => function () {
+                return new Encryptor($this['config']['app_id'], $this['config']['token'], $this['config']['aes_key']);
+            },
+
+            'auth' => function ($app) {
+                return new Client($app, $this);
+            },
+        ]);
+    }
+
+    /**
+     * Return the pre-authorization login page url.
+     *
+     * @param string            $callbackUrl
+     * @param string|array|null $optional
+     *
+     * @return string
+     */
+    public function getPreAuthorizationUrl(string $callbackUrl, $optional = []): string
+    {
+        // 兼容旧版 API 设计
+        if (\is_string($optional)) {
+            $optional = [
+                'pre_auth_code' => $optional,
+            ];
+        } else {
+            $optional['pre_auth_code'] = $this->createPreAuthorizationCode()['pre_auth_code'];
+        }
+
+        $queries = \array_merge($optional, [
+            'component_appid' => $this['config']['app_id'],
+            'redirect_uri' => $callbackUrl,
+        ]);
+
+        return 'https://mp.weixin.qq.com/cgi-bin/componentloginpage?'.http_build_query($queries);
+    }
+
+    /**
+     * Return the pre-authorization login page url (mobile).
+     *
+     * @param string            $callbackUrl
+     * @param string|array|null $optional
+     *
+     * @return string
+     */
+    public function getMobilePreAuthorizationUrl(string $callbackUrl, $optional = []): string
+    {
+        // 兼容旧版 API 设计
+        if (\is_string($optional)) {
+            $optional = [
+                'pre_auth_code' => $optional,
+            ];
+        } else {
+            $optional['pre_auth_code'] = $this->createPreAuthorizationCode()['pre_auth_code'];
+        }
+
+        $queries = \array_merge($optional, [
+            'component_appid' => $this['config']['app_id'],
+            'redirect_uri' => $callbackUrl,
+            'action' => 'bindcomponent',
+            'no_scan' => 1,
+        ]);
+
+        return 'https://mp.weixin.qq.com/safe/bindcomponent?'.http_build_query($queries).'#wechat_redirect';
+    }
+
+    /**
+     * @param string      $appId
+     * @param string|null $refreshToken
+     *
+     * @return array
+     */
+    protected function getAuthorizerConfig(string $appId, string $refreshToken = null): array
+    {
+        return $this['config']->merge([
+            'component_app_id' => $this['config']['app_id'],
+            'app_id' => $appId,
+            'refresh_token' => $refreshToken,
+        ])->toArray();
+    }
+
+    /**
+     * @param \EasyWeChat\OpenPlatform\Authorizer\Auth\AccessToken|null $accessToken
+     *
+     * @return array
+     */
+    protected function getReplaceServices(AccessToken $accessToken = null): array
+    {
+        $services = [
+            'access_token' => $accessToken ?: function ($app) {
+                return new AccessToken($app, $this);
+            },
+
+            'server' => function ($app) {
+                return new Guard($app);
+            },
+        ];
+
+        foreach (['cache', 'http_client', 'log', 'logger', 'request'] as $reuse) {
+            if (isset($this[$reuse])) {
+                $services[$reuse] = $this[$reuse];
+            }
+        }
+
+        return $services;
+    }
+
+    /**
+     * Handle dynamic calls.
+     *
+     * @param string $method
+     * @param array  $args
+     *
+     * @return mixed
+     */
+    public function __call($method, $args)
+    {
+        return $this->base->$method(...$args);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenPlatform/Auth/AccessToken.php
new file mode 100644
index 0000000..c15d78d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Auth/AccessToken.php
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Auth;
+
+use EasyWeChat\Kernel\AccessToken as BaseAccessToken;
+
+/**
+ * Class AccessToken.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class AccessToken extends BaseAccessToken
+{
+    /**
+     * @var string
+     */
+    protected $requestMethod = 'POST';
+
+    /**
+     * @var string
+     */
+    protected $tokenKey = 'component_access_token';
+
+    /**
+     * @var string
+     */
+    protected $endpointToGetToken = 'cgi-bin/component/api_component_token';
+
+    /**
+     * @return array
+     */
+    protected function getCredentials(): array
+    {
+        return [
+            'component_appid' => $this->app['config']['app_id'],
+            'component_appsecret' => $this->app['config']['secret'],
+            'component_verify_ticket' => $this->app['verify_ticket']->getTicket(),
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Auth/ServiceProvider.php
new file mode 100644
index 0000000..c60784b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Auth/ServiceProvider.php
@@ -0,0 +1,37 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Auth;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['verify_ticket'] = function ($app) {
+            return new VerifyTicket($app);
+        };
+
+        $app['access_token'] = function ($app) {
+            return new AccessToken($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Auth/VerifyTicket.php b/vendor/overtrue/wechat/src/OpenPlatform/Auth/VerifyTicket.php
new file mode 100644
index 0000000..9ad04c7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Auth/VerifyTicket.php
@@ -0,0 +1,91 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Auth;
+
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Traits\InteractsWithCache;
+use EasyWeChat\OpenPlatform\Application;
+
+/**
+ * Class VerifyTicket.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class VerifyTicket
+{
+    use InteractsWithCache;
+
+    /**
+     * @var \EasyWeChat\OpenPlatform\Application
+     */
+    protected $app;
+
+    /**
+     * Constructor.
+     *
+     * @param \EasyWeChat\OpenPlatform\Application $app
+     */
+    public function __construct(Application $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * Put the credential `component_verify_ticket` in cache.
+     *
+     * @param string $ticket
+     *
+     * @return $this
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function setTicket(string $ticket)
+    {
+        $this->getCache()->set($this->getCacheKey(), $ticket, 3600);
+
+        if (!$this->getCache()->has($this->getCacheKey())) {
+            throw new RuntimeException('Failed to cache verify ticket.');
+        }
+
+        return $this;
+    }
+
+    /**
+     * Get the credential `component_verify_ticket` from cache.
+     *
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function getTicket(): string
+    {
+        if ($cached = $this->getCache()->get($this->getCacheKey())) {
+            return $cached;
+        }
+
+        throw new RuntimeException('Credential "component_verify_ticket" does not exist in cache.');
+    }
+
+    /**
+     * Get cache key.
+     *
+     * @return string
+     */
+    protected function getCacheKey(): string
+    {
+        return 'easywechat.open_platform.verify_ticket.'.$this->app['config']['app_id'];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/Account/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/Account/Client.php
new file mode 100644
index 0000000..b062e3a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/Account/Client.php
@@ -0,0 +1,96 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\Aggregate\Account;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author Scholer <scholer_l@live.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * 创建开放平台帐号并绑定公众号/小程序.
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create()
+    {
+        $params = [
+            'appid' => $this->app['config']['app_id'],
+        ];
+
+        return $this->httpPostJson('cgi-bin/open/create', $params);
+    }
+
+    /**
+     * 将公众号/小程序绑定到开放平台帐号下.
+     *
+     * @param string $openAppId 开放平台帐号appid
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function bindTo(string $openAppId)
+    {
+        $params = [
+            'appid' => $this->app['config']['app_id'],
+            'open_appid' => $openAppId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/open/bind', $params);
+    }
+
+    /**
+     * 将公众号/小程序从开放平台帐号下解绑.
+     *
+     * @param string $openAppId 开放平台帐号appid
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function unbindFrom(string $openAppId)
+    {
+        $params = [
+            'appid' => $this->app['config']['app_id'],
+            'open_appid' => $openAppId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/open/unbind', $params);
+    }
+
+    /**
+     * 获取公众号/小程序所绑定的开放平台帐号.
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getBinding()
+    {
+        $params = [
+            'appid' => $this->app['config']['app_id'],
+        ];
+
+        return $this->httpPostJson('cgi-bin/open/get', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/AggregateServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/AggregateServiceProvider.php
new file mode 100644
index 0000000..d93293d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/AggregateServiceProvider.php
@@ -0,0 +1,22 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\Aggregate;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class AggregateServiceProvider implements ServiceProviderInterface
+{
+    public function register(Container $app)
+    {
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Auth/AccessToken.php
new file mode 100644
index 0000000..07b808c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Auth/AccessToken.php
@@ -0,0 +1,79 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\Auth;
+
+use EasyWeChat\Kernel\AccessToken as BaseAccessToken;
+use EasyWeChat\OpenPlatform\Application;
+use Pimple\Container;
+
+/**
+ * Class AccessToken.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class AccessToken extends BaseAccessToken
+{
+    /**
+     * @var string
+     */
+    protected $requestMethod = 'POST';
+
+    /**
+     * @var string
+     */
+    protected $queryName = 'access_token';
+
+    /**
+     * {@inheritdoc}.
+     */
+    protected $tokenKey = 'authorizer_access_token';
+
+    /**
+     * @var \EasyWeChat\OpenPlatform\Application
+     */
+    protected $component;
+
+    /**
+     * AuthorizerAccessToken constructor.
+     *
+     * @param \Pimple\Container                    $app
+     * @param \EasyWeChat\OpenPlatform\Application $component
+     */
+    public function __construct(Container $app, Application $component)
+    {
+        parent::__construct($app);
+
+        $this->component = $component;
+    }
+
+    /**
+     * {@inheritdoc}.
+     */
+    protected function getCredentials(): array
+    {
+        return [
+            'component_appid' => $this->component['config']['app_id'],
+            'authorizer_appid' => $this->app['config']['app_id'],
+            'authorizer_refresh_token' => $this->app['config']['refresh_token'],
+        ];
+    }
+
+    /**
+     * @return string
+     */
+    public function getEndpoint(): string
+    {
+        return 'cgi-bin/component/api_authorizer_token?'.http_build_query([
+            'component_access_token' => $this->component['access_token']->getToken()['component_access_token'],
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/Client.php
new file mode 100644
index 0000000..c23772d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/Client.php
@@ -0,0 +1,76 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Account;
+
+use EasyWeChat\OpenPlatform\Authorizer\Aggregate\Account\Client as BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author ClouderSky <clouder.flow@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * 获取账号基本信息.
+     */
+    public function getBasicInfo()
+    {
+        return $this->httpPostJson('cgi-bin/account/getaccountbasicinfo');
+    }
+
+    /**
+     * 修改头像.
+     *
+     * @param string $mediaId 头像素材mediaId
+     * @param float  $left    剪裁框左上角x坐标(取值范围:[0, 1])
+     * @param float  $top     剪裁框左上角y坐标(取值范围:[0, 1])
+     * @param float  $right   剪裁框右下角x坐标(取值范围:[0, 1])
+     * @param float  $bottom  剪裁框右下角y坐标(取值范围:[0, 1])
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function updateAvatar(
+        string $mediaId,
+        float $left = 0.0,
+        float $top = 0.0,
+        float $right = 1.0,
+        float $bottom = 1.0
+    ) {
+        $params = [
+            'head_img_media_id' => $mediaId,
+            'x1' => $left, 'y1' => $top, 'x2' => $right, 'y2' => $bottom,
+        ];
+
+        return $this->httpPostJson('cgi-bin/account/modifyheadimage', $params);
+    }
+
+    /**
+     * 修改功能介绍.
+     *
+     * @param string $signature 功能介绍(简介)
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function updateSignature(string $signature)
+    {
+        $params = ['signature' => $signature];
+
+        return $this->httpPostJson('cgi-bin/account/modifysignature', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/ServiceProvider.php
new file mode 100644
index 0000000..f062954
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/ServiceProvider.php
@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Account;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    public function register(Container $app)
+    {
+        $app['account'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Application.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Application.php
new file mode 100644
index 0000000..b4b8c13
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Application.php
@@ -0,0 +1,53 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram;
+
+use EasyWeChat\MiniProgram\Application as MiniProgram;
+use EasyWeChat\OpenPlatform\Authorizer\Aggregate\AggregateServiceProvider;
+
+/**
+ * Class Application.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ *
+ * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Account\Client $account
+ * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Code\Client    $code
+ * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Domain\Client  $domain
+ * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Setting\Client $setting
+ * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Tester\Client  $tester
+ */
+class Application extends MiniProgram
+{
+    /**
+     * Application constructor.
+     *
+     * @param array $config
+     * @param array $prepends
+     */
+    public function __construct(array $config = [], array $prepends = [])
+    {
+        parent::__construct($config, $prepends);
+
+        $providers = [
+            AggregateServiceProvider::class,
+            Code\ServiceProvider::class,
+            Domain\ServiceProvider::class,
+            Account\ServiceProvider::class,
+            Setting\ServiceProvider::class,
+            Tester\ServiceProvider::class,
+        ];
+
+        foreach ($providers as $provider) {
+            $this->register(new $provider());
+        }
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Auth/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Auth/Client.php
new file mode 100644
index 0000000..05bb704
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Auth/Client.php
@@ -0,0 +1,64 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Auth;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\ServiceContainer;
+use EasyWeChat\OpenPlatform\Application;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @var \EasyWeChat\OpenPlatform\Application
+     */
+    protected $component;
+
+    /**
+     * Client constructor.
+     *
+     * @param \EasyWeChat\Kernel\ServiceContainer  $app
+     * @param \EasyWeChat\OpenPlatform\Application $component
+     */
+    public function __construct(ServiceContainer $app, Application $component)
+    {
+        parent::__construct($app);
+
+        $this->component = $component;
+    }
+
+    /**
+     * Get session info by code.
+     *
+     * @param string $code
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function session(string $code)
+    {
+        $params = [
+            'appid' => $this->app['config']['app_id'],
+            'js_code' => $code,
+            'grant_type' => 'authorization_code',
+            'component_appid' => $this->component['config']['app_id'],
+            'component_access_token' => $this->component['access_token']->getToken()['component_access_token'],
+        ];
+
+        return $this->httpGet('sns/component/jscode2session', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/Client.php
new file mode 100644
index 0000000..e1e3fc6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/Client.php
@@ -0,0 +1,239 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Code;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @param int    $templateId
+     * @param string $extJson
+     * @param string $version
+     * @param string $description
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function commit(int $templateId, string $extJson, string $version, string $description)
+    {
+        return $this->httpPostJson('wxa/commit', [
+            'template_id' => $templateId,
+            'ext_json' => $extJson,
+            'user_version' => $version,
+            'user_desc' => $description,
+        ]);
+    }
+
+    /**
+     * @param string|null $path
+     *
+     * @return \EasyWeChat\Kernel\Http\Response
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getQrCode(string $path = null)
+    {
+        return $this->requestRaw('wxa/get_qrcode', 'GET', [
+            'query' => ['path' => $path],
+        ]);
+    }
+
+    /**
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function getCategory()
+    {
+        return $this->httpGet('wxa/get_category');
+    }
+
+    /**
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function getPage()
+    {
+        return $this->httpGet('wxa/get_page');
+    }
+
+    /**
+     * @param array       $itemList
+     * @param string|null $feedbackInfo
+     * @param string|null $feedbackStuff
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function submitAudit(array $itemList, string $feedbackInfo = null, string $feedbackStuff = null)
+    {
+        return $this->httpPostJson('wxa/submit_audit', [
+            'item_list' => $itemList,
+            'feedback_info' => $feedbackInfo,
+            'feedback_stuff' => $feedbackStuff,
+        ]);
+    }
+
+    /**
+     * @param int $auditId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getAuditStatus(int $auditId)
+    {
+        return $this->httpPostJson('wxa/get_auditstatus', [
+            'auditid' => $auditId,
+        ]);
+    }
+
+    /**
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function getLatestAuditStatus()
+    {
+        return $this->httpGet('wxa/get_latest_auditstatus');
+    }
+
+    /**
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function release()
+    {
+        return $this->httpPostJson('wxa/release');
+    }
+
+    /**
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function withdrawAudit()
+    {
+        return $this->httpGet('wxa/undocodeaudit');
+    }
+
+    /**
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function rollbackRelease()
+    {
+        return $this->httpGet('wxa/revertcoderelease');
+    }
+
+    /**
+     * @param string $action
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function changeVisitStatus(string $action)
+    {
+        return $this->httpPostJson('wxa/change_visitstatus', [
+            'action' => $action,
+        ]);
+    }
+
+    /**
+     * 分阶段发布.
+     *
+     * @param int $grayPercentage
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function grayRelease(int $grayPercentage)
+    {
+        return $this->httpPostJson('wxa/grayrelease', [
+            'gray_percentage' => $grayPercentage,
+        ]);
+    }
+
+    /**
+     * 取消分阶段发布.
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function revertGrayRelease()
+    {
+        return $this->httpGet('wxa/revertgrayrelease');
+    }
+
+    /**
+     * 查询当前分阶段发布详情.
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function getGrayRelease()
+    {
+        return $this->httpGet('wxa/getgrayreleaseplan');
+    }
+
+    /**
+     * 查询当前设置的最低基础库版本及各版本用户占比.
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getSupportVersion()
+    {
+        return $this->httpPostJson('cgi-bin/wxopen/getweappsupportversion');
+    }
+
+    /**
+     * 设置最低基础库版本.
+     *
+     * @param string $version
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setSupportVersion(string $version)
+    {
+        return $this->httpPostJson('cgi-bin/wxopen/setweappsupportversion', [
+            'version' => $version,
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/ServiceProvider.php
new file mode 100644
index 0000000..bce8611
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/ServiceProvider.php
@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Code;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    public function register(Container $app)
+    {
+        $app['code'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/Client.php
new file mode 100644
index 0000000..2a6547c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/Client.php
@@ -0,0 +1,54 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Domain;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @param array $params
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function modify(array $params)
+    {
+        return $this->httpPostJson('wxa/modify_domain', $params);
+    }
+
+    /**
+     * 设置小程序业务域名.
+     *
+     * @param array  $domains
+     * @param string $action
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setWebviewDomain(array $domains, $action = 'add')
+    {
+        return $this->httpPostJson('wxa/setwebviewdomain', [
+            'action' => $action,
+            'webviewdomain' => $domains,
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/ServiceProvider.php
new file mode 100644
index 0000000..4eaef13
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/ServiceProvider.php
@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Domain;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    public function register(Container $app)
+    {
+        $app['domain'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/Client.php
new file mode 100644
index 0000000..77be099
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/Client.php
@@ -0,0 +1,248 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Setting;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author ClouderSky <clouder.flow@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * 获取账号可以设置的所有类目.
+     */
+    public function getAllCategories()
+    {
+        return $this->httpPostJson('cgi-bin/wxopen/getallcategories');
+    }
+
+    /**
+     * 添加类目.
+     *
+     * @param array $categories 类目数组
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function addCategories(array $categories)
+    {
+        $params = ['categories' => $categories];
+
+        return $this->httpPostJson('cgi-bin/wxopen/addcategory', $params);
+    }
+
+    /**
+     * 删除类目.
+     *
+     * @param int $firstId  一级类目ID
+     * @param int $secondId 二级类目ID
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function deleteCategories(int $firstId, int $secondId)
+    {
+        $params = ['first' => $firstId, 'second' => $secondId];
+
+        return $this->httpPostJson('cgi-bin/wxopen/deletecategory', $params);
+    }
+
+    /**
+     * 获取账号已经设置的所有类目.
+     */
+    public function getCategories()
+    {
+        return $this->httpPostJson('cgi-bin/wxopen/getcategory');
+    }
+
+    /**
+     * 修改类目.
+     *
+     * @param array $category 单个类目
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function updateCategory(array $category)
+    {
+        return $this->httpPostJson('cgi-bin/wxopen/modifycategory', $category);
+    }
+
+    /**
+     * 小程序名称设置及改名.
+     *
+     * @param string $nickname       昵称
+     * @param string $idCardMediaId  身份证照片素材ID
+     * @param string $licenseMediaId 组织机构代码证或营业执照素材ID
+     * @param array  $otherStuffs    其他证明材料素材ID
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setNickname(
+        string $nickname,
+        string $idCardMediaId = '',
+        string $licenseMediaId = '',
+        array $otherStuffs = []
+    ) {
+        $params = [
+            'nick_name' => $nickname,
+            'id_card' => $idCardMediaId,
+            'license' => $licenseMediaId,
+        ];
+
+        for ($i = \count($otherStuffs) - 1; $i >= 0; --$i) {
+            $params['naming_other_stuff_'.($i + 1)] = $otherStuffs[$i];
+        }
+
+        return $this->httpPostJson('wxa/setnickname', $params);
+    }
+
+    /**
+     * 小程序改名审核状态查询.
+     *
+     * @param int $auditId 审核单id
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getNicknameAuditStatus($auditId)
+    {
+        $params = ['audit_id' => $auditId];
+
+        return $this->httpPostJson('wxa/api_wxa_querynickname', $params);
+    }
+
+    /**
+     * 微信认证名称检测.
+     *
+     * @param string $nickname 名称(昵称)
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function isAvailableNickname($nickname)
+    {
+        $params = ['nick_name' => $nickname];
+
+        return $this->httpPostJson(
+            'cgi-bin/wxverify/checkwxverifynickname', $params);
+    }
+
+    /**
+     * 查询小程序是否可被搜索.
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getSearchStatus()
+    {
+        return $this->httpGet('wxa/getwxasearchstatus');
+    }
+
+    /**
+     * 设置小程序可被搜素.
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setSearchable()
+    {
+        return $this->httpPostJson('wxa/changewxasearchstatus', [
+            'status' => 0,
+        ]);
+    }
+
+    /**
+     * 设置小程序不可被搜素.
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setUnsearchable()
+    {
+        return $this->httpPostJson('wxa/changewxasearchstatus', [
+            'status' => 1,
+        ]);
+    }
+
+    /**
+     * 获取展示的公众号信息.
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getDisplayedOfficialAccount()
+    {
+        return $this->httpGet('wxa/getshowwxaitem');
+    }
+
+    /**
+     * 设置展示的公众号.
+     *
+     * @param string|bool $appid
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setDisplayedOfficialAccount($appid)
+    {
+        return $this->httpPostJson('wxa/updateshowwxaitem', [
+            'appid' => $appid ?: null,
+            'wxa_subscribe_biz_flag' => $appid ? 1 : 0,
+        ]);
+    }
+
+    /**
+     * 获取可以用来设置的公众号列表.
+     *
+     * @param int $page
+     * @param int $num
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getDisplayableOfficialAccounts(int $page, int $num)
+    {
+        return $this->httpGet('wxa/getwxamplinkforshow', [
+            'page' => $page,
+            'num' => $num,
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/ServiceProvider.php
new file mode 100644
index 0000000..917aec5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/ServiceProvider.php
@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Setting;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    public function register(Container $app)
+    {
+        $app['setting'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/Client.php
new file mode 100644
index 0000000..011c933
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/Client.php
@@ -0,0 +1,72 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Tester;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author caikeal <caiyuezhang@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * 绑定小程序体验者.
+     *
+     * @param string $wechatId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function bind(string $wechatId)
+    {
+        return $this->httpPostJson('wxa/bind_tester', [
+            'wechatid' => $wechatId,
+        ]);
+    }
+
+    /**
+     * 解绑小程序体验者.
+     *
+     * @param string $wechatId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function unbind(string $wechatId)
+    {
+        return $this->httpPostJson('wxa/unbind_tester', [
+            'wechatid' => $wechatId,
+        ]);
+    }
+
+    /**
+     * 获取体验者列表.
+     *
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list()
+    {
+        return $this->httpPostJson('wxa/memberauth', [
+            'action' => 'get_experiencer',
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/ServiceProvider.php
new file mode 100644
index 0000000..ff1ffb3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/ServiceProvider.php
@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Tester;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    public function register(Container $app)
+    {
+        $app['tester'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Account/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Account/Client.php
new file mode 100644
index 0000000..9e7b38b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Account/Client.php
@@ -0,0 +1,81 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Account;
+
+use EasyWeChat\Kernel\ServiceContainer;
+use EasyWeChat\OpenPlatform\Application;
+use EasyWeChat\OpenPlatform\Authorizer\Aggregate\Account\Client as BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author Keal <caiyuezhang@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @var \EasyWeChat\OpenPlatform\Application
+     */
+    protected $component;
+
+    /**
+     * Client constructor.
+     *
+     * @param \EasyWeChat\Kernel\ServiceContainer  $app
+     * @param \EasyWeChat\OpenPlatform\Application $component
+     */
+    public function __construct(ServiceContainer $app, Application $component)
+    {
+        parent::__construct($app);
+
+        $this->component = $component;
+    }
+
+    /**
+     * 从第三方平台跳转至微信公众平台授权注册页面, 授权注册小程序.
+     *
+     * @param string $callbackUrl
+     * @param bool   $copyWxVerify
+     *
+     * @return string
+     */
+    public function getFastRegistrationUrl(string $callbackUrl, bool $copyWxVerify = true): string
+    {
+        $queries = [
+            'copy_wx_verify' => $copyWxVerify,
+            'component_appid' => $this->component['config']['app_id'],
+            'appid' => $this->app['config']['app_id'],
+            'redirect_uri' => $callbackUrl,
+        ];
+
+        return 'https://mp.weixin.qq.com/cgi-bin/fastregisterauth?'.http_build_query($queries);
+    }
+
+    /**
+     * 小程序快速注册.
+     *
+     * @param string $ticket
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function register(string $ticket)
+    {
+        $params = [
+            'ticket' => $ticket,
+        ];
+
+        return $this->httpPostJson('cgi-bin/account/fastregister', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Application.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Application.php
new file mode 100644
index 0000000..08509d2
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Application.php
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount;
+
+use EasyWeChat\OfficialAccount\Application as OfficialAccount;
+use EasyWeChat\OpenPlatform\Authorizer\Aggregate\AggregateServiceProvider;
+
+/**
+ * Class Application.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ *
+ * @property \EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Account\Client     $account
+ * @property \EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\MiniProgram\Client $mini_program
+ */
+class Application extends OfficialAccount
+{
+    /**
+     * Application constructor.
+     *
+     * @param array $config
+     * @param array $prepends
+     */
+    public function __construct(array $config = [], array $prepends = [])
+    {
+        parent::__construct($config, $prepends);
+
+        $providers = [
+            AggregateServiceProvider::class,
+            MiniProgram\ServiceProvider::class,
+        ];
+
+        foreach ($providers as $provider) {
+            $this->register(new $provider());
+        }
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/Client.php
new file mode 100644
index 0000000..d32b51b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/Client.php
@@ -0,0 +1,77 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\MiniProgram;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author Keal <caiyuezhang@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * 获取公众号关联的小程序.
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list()
+    {
+        return $this->httpPostJson('cgi-bin/wxopen/wxamplinkget');
+    }
+
+    /**
+     * 关联小程序.
+     *
+     * @param string $appId
+     * @param bool   $notifyUsers
+     * @param bool   $showProfile
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function link(string $appId, bool $notifyUsers = true, bool $showProfile = false)
+    {
+        $params = [
+            'appid' => $appId,
+            'notify_users' => (string) $notifyUsers,
+            'show_profile' => (string) $showProfile,
+        ];
+
+        return $this->httpPostJson('cgi-bin/wxopen/wxamplink', $params);
+    }
+
+    /**
+     * 解除已关联的小程序.
+     *
+     * @param string $appId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function unlink(string $appId)
+    {
+        $params = [
+            'appid' => $appId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/wxopen/wxampunlink', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/ServiceProvider.php
new file mode 100644
index 0000000..31ce10b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/ServiceProvider.php
@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\MiniProgram;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    public function register(Container $app)
+    {
+        $app['mini_program'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/OAuth/ComponentDelegate.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/OAuth/ComponentDelegate.php
new file mode 100644
index 0000000..a79d177
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/OAuth/ComponentDelegate.php
@@ -0,0 +1,54 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\OAuth;
+
+use EasyWeChat\OpenPlatform\Application;
+use Overtrue\Socialite\WeChatComponentInterface;
+
+/**
+ * Class ComponentDelegate.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ComponentDelegate implements WeChatComponentInterface
+{
+    /**
+     * @var \EasyWeChat\OpenPlatform\Application
+     */
+    protected $app;
+
+    /**
+     * ComponentDelegate Constructor.
+     *
+     * @param \EasyWeChat\OpenPlatform\Application $app
+     */
+    public function __construct(Application $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * @return string
+     */
+    public function getAppId()
+    {
+        return $this->app['config']['app_id'];
+    }
+
+    /**
+     * @return string
+     */
+    public function getToken()
+    {
+        return $this->app['access_token']->getToken()['component_access_token'];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Server/Guard.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Server/Guard.php
new file mode 100644
index 0000000..d7efbd3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Server/Guard.php
@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\Server;
+
+use EasyWeChat\Kernel\ServerGuard;
+
+/**
+ * Class Guard.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Guard extends ServerGuard
+{
+    /**
+     * Get token from OpenPlatform encryptor.
+     *
+     * @return string
+     */
+    protected function getToken()
+    {
+        return $this->app['encryptor']->getToken();
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Base/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Base/Client.php
new file mode 100644
index 0000000..ec78a5f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Base/Client.php
@@ -0,0 +1,166 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Base;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get authorization info.
+     *
+     * @param string|null $authCode
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function handleAuthorize(string $authCode = null)
+    {
+        $params = [
+            'component_appid' => $this->app['config']['app_id'],
+            'authorization_code' => $authCode ?? $this->app['request']->get('auth_code'),
+        ];
+
+        return $this->httpPostJson('cgi-bin/component/api_query_auth', $params);
+    }
+
+    /**
+     * Get authorizer info.
+     *
+     * @param string $appId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getAuthorizer(string $appId)
+    {
+        $params = [
+            'component_appid' => $this->app['config']['app_id'],
+            'authorizer_appid' => $appId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/component/api_get_authorizer_info', $params);
+    }
+
+    /**
+     * Get options.
+     *
+     * @param string $appId
+     * @param string $name
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getAuthorizerOption(string $appId, string $name)
+    {
+        $params = [
+            'component_appid' => $this->app['config']['app_id'],
+            'authorizer_appid' => $appId,
+            'option_name' => $name,
+        ];
+
+        return $this->httpPostJson('cgi-bin/component/api_get_authorizer_option', $params);
+    }
+
+    /**
+     * Set authorizer option.
+     *
+     * @param string $appId
+     * @param string $name
+     * @param string $value
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setAuthorizerOption(string $appId, string $name, string $value)
+    {
+        $params = [
+            'component_appid' => $this->app['config']['app_id'],
+            'authorizer_appid' => $appId,
+            'option_name' => $name,
+            'option_value' => $value,
+        ];
+
+        return $this->httpPostJson('cgi-bin/component/api_set_authorizer_option', $params);
+    }
+
+    /**
+     * Get authorizer list.
+     *
+     * @param int $offset
+     * @param int $count
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getAuthorizers($offset = 0, $count = 500)
+    {
+        $params = [
+            'component_appid' => $this->app['config']['app_id'],
+            'offset' => $offset,
+            'count' => $count,
+        ];
+
+        return $this->httpPostJson('cgi-bin/component/api_get_authorizer_list', $params);
+    }
+
+    /**
+     * Create pre-authorization code.
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function createPreAuthorizationCode()
+    {
+        $params = [
+            'component_appid' => $this->app['config']['app_id'],
+        ];
+
+        return $this->httpPostJson('cgi-bin/component/api_create_preauthcode', $params);
+    }
+
+    /**
+     * OpenPlatform Clear quota.
+     *
+     * @see https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318587
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function clearQuota()
+    {
+        $params = [
+            'component_appid' => $this->app['config']['app_id'],
+        ];
+
+        return $this->httpPostJson('cgi-bin/component/clear_quota', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Base/ServiceProvider.php
new file mode 100644
index 0000000..e647c41
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Base/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Base;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['base'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/Client.php
new file mode 100644
index 0000000..2929cb6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/Client.php
@@ -0,0 +1,86 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\CodeTemplate;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author caikeal <caiyuezhang@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * 获取草稿箱内的所有临时代码草稿
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getDrafts()
+    {
+        return $this->httpGet('wxa/gettemplatedraftlist');
+    }
+
+    /**
+     * 将草稿箱的草稿选为小程序代码模版.
+     *
+     * @param int $draftId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function createFromDraft(int $draftId)
+    {
+        $params = [
+            'draft_id' => $draftId,
+        ];
+
+        return $this->httpPostJson('wxa/addtotemplate', $params);
+    }
+
+    /**
+     * 获取代码模版库中的所有小程序代码模版.
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function list()
+    {
+        return $this->httpGet('wxa/gettemplatelist');
+    }
+
+    /**
+     * 删除指定小程序代码模版.
+     *
+     * @param string $templateId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete($templateId)
+    {
+        $params = [
+            'template_id' => $templateId,
+        ];
+
+        return $this->httpPostJson('wxa/deletetemplate', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/ServiceProvider.php
new file mode 100644
index 0000000..ab543a7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/ServiceProvider.php
@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\CodeTemplate;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    public function register(Container $app)
+    {
+        $app['code_template'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Component/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Component/Client.php
new file mode 100644
index 0000000..eafb3f5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Component/Client.php
@@ -0,0 +1,60 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Component;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author dudashuang <dudashuang1222@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * 通过法人微信快速创建小程序.
+     *
+     * @param array $params
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function registerMiniProgram(array $params)
+    {
+        return $this->httpPostJson('cgi-bin/component/fastregisterweapp', $params, ['action' => 'create']);
+    }
+
+    /**
+     * 查询创建任务状态.
+     *
+     * @param string $companyName
+     * @param string $legalPersonaWechat
+     * @param string $legalPersonaName
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getRegistrationStatus(string $companyName, string $legalPersonaWechat, string $legalPersonaName)
+    {
+        $params = [
+            'name' => $companyName,
+            'legal_persona_wechat' => $legalPersonaWechat,
+            'legal_persona_name' => $legalPersonaName,
+        ];
+
+        return $this->httpPostJson('cgi-bin/component/fastregisterweapp', $params, ['action' => 'search']);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Component/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Component/ServiceProvider.php
new file mode 100644
index 0000000..83e309f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Component/ServiceProvider.php
@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Component;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    public function register(Container $app)
+    {
+        $app['component'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Guard.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Guard.php
new file mode 100644
index 0000000..17e6766
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Guard.php
@@ -0,0 +1,64 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Server;
+
+use EasyWeChat\Kernel\ServerGuard;
+use EasyWeChat\OpenPlatform\Server\Handlers\Authorized;
+use EasyWeChat\OpenPlatform\Server\Handlers\Unauthorized;
+use EasyWeChat\OpenPlatform\Server\Handlers\UpdateAuthorized;
+use EasyWeChat\OpenPlatform\Server\Handlers\VerifyTicketRefreshed;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Class Guard.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Guard extends ServerGuard
+{
+    const EVENT_AUTHORIZED = 'authorized';
+    const EVENT_UNAUTHORIZED = 'unauthorized';
+    const EVENT_UPDATE_AUTHORIZED = 'updateauthorized';
+    const EVENT_COMPONENT_VERIFY_TICKET = 'component_verify_ticket';
+    const EVENT_THIRD_FAST_REGISTERED = 'notify_third_fasteregister';
+
+    /**
+     * @return \Symfony\Component\HttpFoundation\Response
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    protected function resolve(): Response
+    {
+        $this->registerHandlers();
+
+        $message = $this->getMessage();
+
+        if (isset($message['InfoType'])) {
+            $this->dispatch($message['InfoType'], $message);
+        }
+
+        return new Response(static::SUCCESS_EMPTY_RESPONSE);
+    }
+
+    /**
+     * Register event handlers.
+     */
+    protected function registerHandlers()
+    {
+        $this->on(self::EVENT_AUTHORIZED, Authorized::class);
+        $this->on(self::EVENT_UNAUTHORIZED, Unauthorized::class);
+        $this->on(self::EVENT_UPDATE_AUTHORIZED, UpdateAuthorized::class);
+        $this->on(self::EVENT_COMPONENT_VERIFY_TICKET, VerifyTicketRefreshed::class);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Authorized.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Authorized.php
new file mode 100644
index 0000000..fe7f9c6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Authorized.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Server\Handlers;
+
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+
+/**
+ * Class Authorized.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Authorized implements EventHandlerInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function handle($payload = null)
+    {
+        // Do nothing for the time being.
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Unauthorized.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Unauthorized.php
new file mode 100644
index 0000000..158228b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Unauthorized.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Server\Handlers;
+
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+
+/**
+ * Class Unauthorized.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Unauthorized implements EventHandlerInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function handle($payload = null)
+    {
+        // Do nothing for the time being.
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/UpdateAuthorized.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/UpdateAuthorized.php
new file mode 100644
index 0000000..e73caa5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/UpdateAuthorized.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Server\Handlers;
+
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+
+/**
+ * Class UpdateAuthorized.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class UpdateAuthorized implements EventHandlerInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function handle($payload = null)
+    {
+        // Do nothing for the time being.
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/VerifyTicketRefreshed.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/VerifyTicketRefreshed.php
new file mode 100644
index 0000000..e5ce58d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/VerifyTicketRefreshed.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Server\Handlers;
+
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+use EasyWeChat\OpenPlatform\Application;
+
+/**
+ * Class VerifyTicketRefreshed.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class VerifyTicketRefreshed implements EventHandlerInterface
+{
+    /**
+     * @var \EasyWeChat\OpenPlatform\Application
+     */
+    protected $app;
+
+    /**
+     * Constructor.
+     *
+     * @param \EasyWeChat\OpenPlatform\Application $app
+     */
+    public function __construct(Application $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * {@inheritdoc}.
+     */
+    public function handle($payload = null)
+    {
+        if (!empty($payload['ComponentVerifyTicket'])) {
+            $this->app['verify_ticket']->setTicket($payload['ComponentVerifyTicket']);
+        }
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/ServiceProvider.php
new file mode 100644
index 0000000..7747061
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/ServiceProvider.php
@@ -0,0 +1,34 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Server;
+
+use EasyWeChat\Kernel\Encryptor;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    public function register(Container $app)
+    {
+        $app['encryptor'] = function ($app) {
+            return new Encryptor(
+                $app['config']['app_id'],
+                $app['config']['token'],
+                $app['config']['aes_key']
+            );
+        };
+
+        $app['server'] = function ($app) {
+            return new Guard($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Application.php b/vendor/overtrue/wechat/src/OpenWork/Application.php
new file mode 100644
index 0000000..bde1421
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Application.php
@@ -0,0 +1,85 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork;
+
+use EasyWeChat\Kernel\ServiceContainer;
+use EasyWeChat\OpenWork\Work\Application as Work;
+
+/**
+ * Application.
+ *
+ * @author xiaomin <keacefull@gmail.com>
+ *
+ * @property \EasyWeChat\OpenWork\Server\Guard            $server
+ * @property \EasyWeChat\OpenWork\Corp\Client             $corp
+ * @property \EasyWeChat\OpenWork\Provider\Client         $provider
+ * @property \EasyWeChat\OpenWork\SuiteAuth\AccessToken   $suite_access_token
+ * @property \EasyWeChat\OpenWork\Auth\AccessToken        $provider_access_token
+ * @property \EasyWeChat\OpenWork\SuiteAuth\SuiteTicket   $suite_ticket
+ * @property \EasyWeChat\OpenWork\MiniProgram\Auth\Client $mini_program
+ */
+class Application extends ServiceContainer
+{
+    /**
+     * @var array
+     */
+    protected $providers = [
+        Auth\ServiceProvider::class,
+        SuiteAuth\ServiceProvider::class,
+        Server\ServiceProvider::class,
+        Corp\ServiceProvider::class,
+        Provider\ServiceProvider::class,
+        MiniProgram\ServiceProvider::class,
+    ];
+
+    /**
+     * @var array
+     */
+    protected $defaultConfig = [
+        // http://docs.guzzlephp.org/en/stable/request-options.html
+        'http' => [
+            'base_uri' => 'https://qyapi.weixin.qq.com/',
+        ],
+    ];
+
+    /**
+     * Creates the miniProgram application.
+     *
+     * @return \EasyWeChat\Work\MiniProgram\Application
+     */
+    public function miniProgram(): \EasyWeChat\Work\MiniProgram\Application
+    {
+        return new \EasyWeChat\Work\MiniProgram\Application($this->getConfig());
+    }
+
+    /**
+     * @param string $authCorpId    企业 corp_id
+     * @param string $permanentCode 企业永久授权码
+     *
+     * @return Work
+     */
+    public function work(string $authCorpId, string $permanentCode): Work
+    {
+        return new Work($authCorpId, $permanentCode, $this);
+    }
+
+    /**
+     * @param string $method
+     * @param array  $arguments
+     *
+     * @return mixed
+     */
+    public function __call($method, $arguments)
+    {
+        return $this['base']->$method(...$arguments);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenWork/Auth/AccessToken.php
new file mode 100644
index 0000000..3f00a25
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Auth/AccessToken.php
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Auth;
+
+use  EasyWeChat\Kernel\AccessToken as BaseAccessToken;
+
+/**
+ * AccessToken.
+ *
+ * @author xiaomin <keacefull@gmail.com>
+ */
+class AccessToken extends BaseAccessToken
+{
+    protected $requestMethod = 'POST';
+
+    /**
+     * @var string
+     */
+    protected $endpointToGetToken = 'cgi-bin/service/get_provider_token';
+
+    /**
+     * @var string
+     */
+    protected $tokenKey = 'provider_access_token';
+
+    /**
+     * @var string
+     */
+    protected $cachePrefix = 'easywechat.kernel.provider_access_token.';
+
+    /**
+     * Credential for get token.
+     *
+     * @return array
+     */
+    protected function getCredentials(): array
+    {
+        return [
+            'corpid' => $this->app['config']['corp_id'], //服务商的corpid
+            'provider_secret' => $this->app['config']['secret'],
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Auth/ServiceProvider.php
new file mode 100644
index 0000000..78aba05
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Auth/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Auth;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ *
+ * @author xiaomin <keacefull@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        isset($app['provider_access_token']) || $app['provider_access_token'] = function ($app) {
+            return new AccessToken($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Corp/Client.php b/vendor/overtrue/wechat/src/OpenWork/Corp/Client.php
new file mode 100644
index 0000000..4ecd941
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Corp/Client.php
@@ -0,0 +1,217 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Corp;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Client.
+ *
+ * @author xiaomin <keacefull@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Client constructor.
+     * 三方接口有三个access_token,这里用的是suite_access_token.
+     *
+     * @param \EasyWeChat\Kernel\ServiceContainer $app
+     */
+    public function __construct(ServiceContainer $app)
+    {
+        parent::__construct($app, $app['suite_access_token']);
+    }
+
+    /**
+     * 企业微信安装应用授权 url.
+     *
+     * @param string $preAuthCode 预授权码
+     * @param string $redirectUri 回调地址
+     * @param string $state
+     *
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function getPreAuthorizationUrl(string $preAuthCode = '', string $redirectUri = '', string $state = '')
+    {
+        $redirectUri || $redirectUri = $this->app->config['redirect_uri_install'];
+        $preAuthCode || $preAuthCode = $this->getPreAuthCode()['pre_auth_code'];
+        $state || $state = rand();
+
+        $params = [
+            'suite_id' => $this->app['config']['suite_id'],
+            'redirect_uri' => $redirectUri,
+            'pre_auth_code' => $preAuthCode,
+            'state' => $state,
+        ];
+
+        return 'https://open.work.weixin.qq.com/3rdapp/install?'.http_build_query($params);
+    }
+
+    /**
+     * 获取预授权码.
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function getPreAuthCode()
+    {
+        return $this->httpGet('cgi-bin/service/get_pre_auth_code');
+    }
+
+    /**
+     * 设置授权配置.
+     * 该接口可对某次授权进行配置.
+     *
+     * @param string $preAuthCode
+     * @param array  $sessionInfo
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function setSession(string $preAuthCode, array $sessionInfo)
+    {
+        $params = [
+            'pre_auth_code' => $preAuthCode,
+            'session_info' => $sessionInfo,
+        ];
+
+        return $this->httpPostJson('cgi-bin/service/set_session_info', $params);
+    }
+
+    /**
+     * 获取企业永久授权码.
+     *
+     * @param string $authCode 临时授权码,会在授权成功时附加在redirect_uri中跳转回第三方服务商网站,或通过回调推送给服务商。长度为64至512个字节
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getPermanentByCode(string $authCode)
+    {
+        $params = [
+            'auth_code' => $authCode,
+        ];
+
+        return $this->httpPostJson('cgi-bin/service/get_permanent_code', $params);
+    }
+
+    /**
+     * 获取企业授权信息.
+     *
+     * @param string $authCorpId
+     * @param string $permanentCode
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getAuthorization(string $authCorpId, string $permanentCode)
+    {
+        $params = [
+            'auth_corpid' => $authCorpId,
+            'permanent_code' => $permanentCode,
+        ];
+
+        return $this->httpPostJson('cgi-bin/service/get_auth_info', $params);
+    }
+
+    /**
+     * 获取应用的管理员列表.
+     *
+     * @param string $authCorpId
+     * @param string $agentId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getManagers(string $authCorpId, string $agentId)
+    {
+        $params = [
+            'auth_corpid' => $authCorpId,
+            'agentid' => $agentId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/service/get_admin_lis', $params);
+    }
+
+    /**
+     * 获取登录url.
+     *
+     * @param string      $redirectUri
+     * @param string      $scope
+     * @param string|null $state
+     *
+     * @return string
+     */
+    public function getOAuthRedirectUrl(string $redirectUri = '', string $scope = 'snsapi_userinfo', string $state = null)
+    {
+        $redirectUri || $redirectUri = $this->app->config['redirect_uri_oauth'];
+        $state || $state = rand();
+        $params = [
+            'appid' => $this->app['config']['suite_id'],
+            'redirect_uri' => $redirectUri,
+            'response_type' => 'code',
+            'scope' => $scope,
+            'state' => $state,
+        ];
+
+        return 'https://open.weixin.qq.com/connect/oauth2/authorize?'.http_build_query($params).'#wechat_redirect';
+    }
+
+    /**
+     * 第三方根据code获取企业成员信息.
+     *
+     * @param string $code
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function getUserByCode(string $code)
+    {
+        $params = [
+            'code' => $code,
+        ];
+
+        return $this->httpGet('cgi-bin/service/getuserinfo3rd', $params);
+    }
+
+    /**
+     * 第三方使用user_ticket获取成员详情.
+     *
+     * @param string $userTicket
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getUserByTicket(string $userTicket)
+    {
+        $params = [
+            'user_ticket' => $userTicket,
+        ];
+
+        return $this->httpPostJson('cgi-bin/service/getuserdetail3rd', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Corp/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Corp/ServiceProvider.php
new file mode 100644
index 0000000..0cc8ea9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Corp/ServiceProvider.php
@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Corp;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ *
+ * @author xiaomin <keacefull@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * Registers services on the given container.
+     *
+     * This method should only be used to configure services and parameters.
+     * It should not get services.
+     *
+     * @param \Pimple\Container $app
+     */
+    public function register(Container $app)
+    {
+        isset($app['corp']) || $app['corp'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/MiniProgram/Client.php b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/Client.php
new file mode 100644
index 0000000..29dae19
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/Client.php
@@ -0,0 +1,50 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\MiniProgram;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Class Client.
+ */
+class Client extends BaseClient
+{
+    /**
+     * Client constructor.
+     *
+     * @param \EasyWeChat\Kernel\ServiceContainer $app
+     */
+    public function __construct(ServiceContainer $app)
+    {
+        parent::__construct($app, $app['suite_access_token']);
+    }
+
+    /**
+     * Get session info by code.
+     *
+     * @param string $code
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function session(string $code)
+    {
+        $params = [
+            'js_code' => $code,
+            'grant_type' => 'authorization_code',
+        ];
+
+        return $this->httpGet('cgi-bin/service/miniprogram/jscode2session', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/MiniProgram/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/ServiceProvider.php
new file mode 100644
index 0000000..8e7dba2
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/ServiceProvider.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\MiniProgram;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        !isset($app['mini_program']) && $app['mini_program'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Provider/Client.php b/vendor/overtrue/wechat/src/OpenWork/Provider/Client.php
new file mode 100644
index 0000000..690d988
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Provider/Client.php
@@ -0,0 +1,206 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Provider;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Client.
+ *
+ * @author xiaomin <keacefull@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Client constructor.
+     *
+     *
+     * @param ServiceContainer $app
+     */
+    public function __construct(ServiceContainer $app)
+    {
+        parent::__construct($app, $app['provider_access_token']);
+    }
+
+    /**
+     * 单点登录 - 获取登录的地址.
+     *
+     * @param string $redirectUri
+     * @param string $userType
+     * @param string $state
+     *
+     * @return string
+     */
+    public function getLoginUrl(string $redirectUri = '', string $userType = 'admin', string $state = '')
+    {
+        $redirectUri || $redirectUri = $this->app->config['redirect_uri_single'];
+        $state || $state = rand();
+        $params = [
+            'appid' => $this->app['config']['corp_id'],
+            'redirect_uri' => $redirectUri,
+            'usertype' => $userType,
+            'state' => $state,
+        ];
+
+        return 'https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect?'.http_build_query($params);
+    }
+
+    /**
+     * 单点登录 - 获取登录用户信息.
+     *
+     * @param string $authCode
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getLoginInfo(string $authCode)
+    {
+        $params = [
+            'auth_code' => $authCode,
+        ];
+
+        return $this->httpPostJson('cgi-bin/service/get_login_info', $params);
+    }
+
+    /**
+     * 获取注册定制化URL.
+     *
+     * @param string $registerCode
+     *
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function getRegisterUri(string $registerCode = '')
+    {
+        if (!$registerCode) {
+            /** @var array $response */
+            $response = $this->detectAndCastResponseToType($this->getRegisterCode(), 'array');
+
+            $registerCode = $response['register_code'];
+        }
+
+        $params = ['register_code' => $registerCode];
+
+        return 'https://open.work.weixin.qq.com/3rdservice/wework/register?'.http_build_query($params);
+    }
+
+    /**
+     * 获取注册码.
+     *
+     * @param string $corpName
+     * @param string $adminName
+     * @param string $adminMobile
+     * @param string $state
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getRegisterCode(
+        string $corpName = '',
+        string $adminName = '',
+        string $adminMobile = '',
+        string $state = ''
+    ) {
+        $params = [];
+        $params['template_id'] = $this->app['config']['reg_template_id'];
+        !empty($corpName) && $params['corp_name'] = $corpName;
+        !empty($adminName) && $params['admin_name'] = $adminName;
+        !empty($adminMobile) && $params['admin_mobile'] = $adminMobile;
+        !empty($state) && $params['state'] = $state;
+
+        return $this->httpPostJson('cgi-bin/service/get_register_code', $params);
+    }
+
+    /**
+     * 查询注册状态.
+     *
+     * Desc:该API用于查询企业注册状态,企业注册成功返回注册信息.
+     *
+     * @param string $registerCode
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getRegisterInfo(string $registerCode)
+    {
+        $params = [
+            'register_code' => $registerCode,
+        ];
+
+        return $this->httpPostJson('cgi-bin/service/get_register_info', $params);
+    }
+
+    /**
+     * 设置授权应用可见范围.
+     *
+     * Desc:调用该接口前提是开启通讯录迁移,收到授权成功通知后可调用。
+     *      企业注册初始化安装应用后,应用默认可见范围为根部门。
+     *      如需修改应用可见范围,服务商可以调用该接口设置授权应用的可见范围。
+     *      该接口只能使用注册完成回调事件或者查询注册状态返回的access_token。
+     *      调用设置通讯录同步完成后或者access_token超过30分钟失效(即解除通讯录锁定状态)则不能继续调用该接口。
+     *
+     * @param string $accessToken
+     * @param string $agentId
+     * @param array  $allowUser
+     * @param array  $allowParty
+     * @param array  $allowTag
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function setAgentScope(
+        string $accessToken,
+        string $agentId,
+        array $allowUser = [],
+        array $allowParty = [],
+        array $allowTag = []
+    ) {
+        $params = [
+            'agentid' => $agentId,
+            'allow_user' => $allowUser,
+            'allow_party' => $allowParty,
+            'allow_tag' => $allowTag,
+            'access_token' => $accessToken,
+        ];
+
+        return $this->httpGet('cgi-bin/agent/set_scope', $params);
+    }
+
+    /**
+     * 设置通讯录同步完成.
+     *
+     * Desc:该API用于设置通讯录同步完成,解除通讯录锁定状态,同时使通讯录迁移access_token失效。
+     *
+     * @param string $accessToken
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function contactSyncSuccess(string $accessToken)
+    {
+        $params = ['access_token' => $accessToken];
+
+        return $this->httpGet('cgi-bin/sync/contact_sync_success', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Provider/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Provider/ServiceProvider.php
new file mode 100644
index 0000000..2185fec
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Provider/ServiceProvider.php
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Provider;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ *
+ * @author xiaomin <keacefull@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    protected $app;
+
+    /**
+     * @param Container $app
+     */
+    public function register(Container $app)
+    {
+        $this->app = $app;
+        isset($app['provider']) || $app['provider'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Server/Guard.php b/vendor/overtrue/wechat/src/OpenWork/Server/Guard.php
new file mode 100644
index 0000000..5700a77
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Server/Guard.php
@@ -0,0 +1,68 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Server;
+
+use EasyWeChat\Kernel\Encryptor;
+use EasyWeChat\Kernel\ServerGuard;
+
+/**
+ * Guard.
+ *
+ * @author xiaomin <keacefull@gmail.com>
+ */
+class Guard extends ServerGuard
+{
+    /**
+     * @var bool
+     */
+    protected $alwaysValidate = true;
+
+    /**
+     * @return $this
+     */
+    public function validate()
+    {
+        return $this;
+    }
+
+    /**
+     * @return bool
+     */
+    protected function shouldReturnRawResponse(): bool
+    {
+        return !is_null($this->app['request']->get('echostr'));
+    }
+
+    protected function isSafeMode(): bool
+    {
+        return true;
+    }
+
+    /**
+     * @param array $message
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     */
+    protected function decryptMessage(array $message)
+    {
+        $encryptor = new Encryptor($message['ToUserName'], $this->app['config']->get('token'), $this->app['config']->get('aes_key'));
+
+        return $message = $encryptor->decrypt(
+            $message['Encrypt'],
+            $this->app['request']->get('msg_signature'),
+            $this->app['request']->get('nonce'),
+            $this->app['request']->get('timestamp')
+        );
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Server/Handlers/EchoStrHandler.php b/vendor/overtrue/wechat/src/OpenWork/Server/Handlers/EchoStrHandler.php
new file mode 100644
index 0000000..78af701
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Server/Handlers/EchoStrHandler.php
@@ -0,0 +1,62 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Server\Handlers;
+
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+use EasyWeChat\Kernel\Decorators\FinallyResult;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * EchoStrHandler.
+ *
+ * @author xiaomin <keacefull@gmail.com>
+ */
+class EchoStrHandler implements EventHandlerInterface
+{
+    /**
+     * @var ServiceContainer
+     */
+    protected $app;
+
+    /**
+     * EchoStrHandler constructor.
+     *
+     * @param ServiceContainer $app
+     */
+    public function __construct(ServiceContainer $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * @param mixed $payload
+     *
+     * @return FinallyResult|null
+     */
+    public function handle($payload = null)
+    {
+        if ($decrypted = $this->app['request']->get('echostr')) {
+            $str = $this->app['encryptor_corp']->decrypt(
+                $decrypted,
+                $this->app['request']->get('msg_signature'),
+                $this->app['request']->get('nonce'),
+                $this->app['request']->get('timestamp')
+            );
+
+            return new FinallyResult($str);
+        }
+        //把SuiteTicket缓存起来
+        if (!empty($payload['SuiteTicket'])) {
+            $this->app['suite_ticket']->setTicket($payload['SuiteTicket']);
+        }
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Server/ServiceProvider.php
new file mode 100644
index 0000000..d33d5c8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Server/ServiceProvider.php
@@ -0,0 +1,56 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Server;
+
+use EasyWeChat\Kernel\Encryptor;
+use EasyWeChat\OpenWork\Server\Handlers\EchoStrHandler;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ *
+ * @author xiaomin <keacefull@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        //微信第三方在校验url是使用的是GET方式请求和corp_id进行加密
+        !isset($app['encryptor_corp']) && $app['encryptor_corp'] = function ($app) {
+            return new Encryptor(
+                $app['config']['corp_id'],
+                $app['config']['token'],
+                $app['config']['aes_key']
+            );
+        };
+
+        //微信第三方推送数据时使用的是suite_id进行加密
+        !isset($app['encryptor']) && $app['encryptor'] = function ($app) {
+            return new Encryptor(
+                $app['config']['suite_id'],
+                $app['config']['token'],
+                $app['config']['aes_key']
+            );
+        };
+
+        !isset($app['server']) && $app['server'] = function ($app) {
+            $guard = new Guard($app);
+            $guard->push(new EchoStrHandler($app));
+
+            return $guard;
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/AccessToken.php b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/AccessToken.php
new file mode 100644
index 0000000..e4e248d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/AccessToken.php
@@ -0,0 +1,56 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\SuiteAuth;
+
+use  EasyWeChat\Kernel\AccessToken as BaseAccessToken;
+
+/**
+ * AccessToken.
+ *
+ * @author xiaomin <keacefull@gmail.com>
+ */
+class AccessToken extends BaseAccessToken
+{
+    /**
+     * @var string
+     */
+    protected $requestMethod = 'POST';
+
+    /**
+     * @var string
+     */
+    protected $endpointToGetToken = 'cgi-bin/service/get_suite_token';
+
+    /**
+     * @var string
+     */
+    protected $tokenKey = 'suite_access_token';
+
+    /**
+     * @var string
+     */
+    protected $cachePrefix = 'easywechat.kernel.suite_access_token.';
+
+    /**
+     * Credential for get token.
+     *
+     * @return array
+     */
+    protected function getCredentials(): array
+    {
+        return [
+            'suite_id' => $this->app['config']['suite_id'],
+            'suite_secret' => $this->app['config']['suite_secret'],
+            'suite_ticket' => $this->app['suite_ticket']->getTicket(),
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/ServiceProvider.php
new file mode 100644
index 0000000..a4f5386
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/ServiceProvider.php
@@ -0,0 +1,37 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\SuiteAuth;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ *
+ * @author xiaomin <keacefull@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['suite_ticket'] = function ($app) {
+            return new SuiteTicket($app);
+        };
+
+        isset($app['suite_access_token']) || $app['suite_access_token'] = function ($app) {
+            return new AccessToken($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/SuiteTicket.php b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/SuiteTicket.php
new file mode 100644
index 0000000..4c01710
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/SuiteTicket.php
@@ -0,0 +1,85 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\SuiteAuth;
+
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Traits\InteractsWithCache;
+use EasyWeChat\OpenWork\Application;
+
+/**
+ * SuiteTicket.
+ *
+ * @author xiaomin <keacefull@gmail.com>
+ */
+class SuiteTicket
+{
+    use InteractsWithCache;
+
+    /**
+     * @var Application
+     */
+    protected $app;
+
+    /**
+     * SuiteTicket constructor.
+     *
+     * @param Application $app
+     */
+    public function __construct(Application $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * @param string $ticket
+     *
+     * @return $this
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function setTicket(string $ticket)
+    {
+        $this->getCache()->set($this->getCacheKey(), $ticket, 1800);
+
+        if (!$this->getCache()->has($this->getCacheKey())) {
+            throw new RuntimeException('Failed to cache suite ticket.');
+        }
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function getTicket(): string
+    {
+        if ($cached = $this->getCache()->get($this->getCacheKey())) {
+            return $cached;
+        }
+
+        throw new RuntimeException('Credential "suite_ticket" does not exist in cache.');
+    }
+
+    /**
+     * @return string
+     */
+    protected function getCacheKey(): string
+    {
+        return 'easywechat.open_work.suite_ticket.'.$this->app['config']['suite_id'];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Work/Application.php b/vendor/overtrue/wechat/src/OpenWork/Work/Application.php
new file mode 100644
index 0000000..676013d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Work/Application.php
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Work;
+
+use EasyWeChat\OpenWork\Application as OpenWork;
+use EasyWeChat\OpenWork\Work\Auth\AccessToken;
+use EasyWeChat\Work\Application as Work;
+
+/**
+ * Application.
+ *
+ * @author xiaomin <keacefull@gmail.com>
+ */
+class Application extends Work
+{
+    /**
+     * Application constructor.
+     *
+     * @param string   $authCorpId
+     * @param string   $permanentCode
+     * @param OpenWork $component
+     * @param array    $prepends
+     */
+    public function __construct(string $authCorpId, string $permanentCode, OpenWork $component, array $prepends = [])
+    {
+        parent::__construct($component->getConfig(), $prepends + [
+                'access_token' => function ($app) use ($authCorpId, $permanentCode, $component) {
+                    return new AccessToken($app, $authCorpId, $permanentCode, $component);
+                },
+            ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Work/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenWork/Work/Auth/AccessToken.php
new file mode 100644
index 0000000..e80758a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Work/Auth/AccessToken.php
@@ -0,0 +1,80 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Work\Auth;
+
+use EasyWeChat\Kernel\AccessToken as BaseAccessToken;
+use EasyWeChat\OpenWork\Application;
+use Pimple\Container;
+
+/**
+ * AccessToken.
+ *
+ * @author xiaomin <keacefull@gmail.com>
+ */
+class AccessToken extends BaseAccessToken
+{
+    /**
+     * @var string
+     */
+    protected $requestMethod = 'POST';
+
+    /**
+     * @var string 授权方企业ID
+     */
+    protected $authCorpid;
+
+    /**
+     * @var string 授权方企业永久授权码,通过get_permanent_code获取
+     */
+    protected $permanentCode;
+
+    protected $component;
+
+    /**
+     * AccessToken constructor.
+     *
+     * @param Container   $app
+     * @param string      $authCorpId
+     * @param string      $permanentCode
+     * @param Application $component
+     */
+    public function __construct(Container $app, string $authCorpId, string $permanentCode, Application $component)
+    {
+        $this->authCorpid = $authCorpId;
+        $this->permanentCode = $permanentCode;
+        $this->component = $component;
+        parent::__construct($app);
+    }
+
+    /**
+     * Credential for get token.
+     *
+     * @return array
+     */
+    protected function getCredentials(): array
+    {
+        return [
+            'auth_corpid' => $this->authCorpid,
+            'permanent_code' => $this->permanentCode,
+        ];
+    }
+
+    /**
+     * @return string
+     */
+    public function getEndpoint(): string
+    {
+        return 'cgi-bin/service/get_corp_token?'.http_build_query([
+                'suite_access_token' => $this->component['suite_access_token']->getToken()['suite_access_token'],
+            ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Application.php b/vendor/overtrue/wechat/src/Payment/Application.php
new file mode 100644
index 0000000..7013f74
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Application.php
@@ -0,0 +1,206 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment;
+
+use Closure;
+use EasyWeChat\BasicService;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\ServiceContainer;
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\OfficialAccount;
+
+/**
+ * Class Application.
+ *
+ * @property \EasyWeChat\Payment\Bill\Client              $bill
+ * @property \EasyWeChat\Payment\Jssdk\Client             $jssdk
+ * @property \EasyWeChat\Payment\Order\Client             $order
+ * @property \EasyWeChat\Payment\Refund\Client            $refund
+ * @property \EasyWeChat\Payment\Coupon\Client            $coupon
+ * @property \EasyWeChat\Payment\Reverse\Client           $reverse
+ * @property \EasyWeChat\Payment\Redpack\Client           $redpack
+ * @property \EasyWeChat\BasicService\Url\Client          $url
+ * @property \EasyWeChat\Payment\Transfer\Client          $transfer
+ * @property \EasyWeChat\Payment\Security\Client          $security
+ * @property \EasyWeChat\Payment\ProfitSharing\Client     $profit_sharing
+ * @property \EasyWeChat\OfficialAccount\Auth\AccessToken $access_token
+ *
+ * @method mixed pay(array $attributes)
+ * @method mixed authCodeToOpenid(string $authCode)
+ */
+class Application extends ServiceContainer
+{
+    /**
+     * @var array
+     */
+    protected $providers = [
+        OfficialAccount\Auth\ServiceProvider::class,
+        BasicService\Url\ServiceProvider::class,
+        Base\ServiceProvider::class,
+        Bill\ServiceProvider::class,
+        Coupon\ServiceProvider::class,
+        Jssdk\ServiceProvider::class,
+        Merchant\ServiceProvider::class,
+        Order\ServiceProvider::class,
+        Redpack\ServiceProvider::class,
+        Refund\ServiceProvider::class,
+        Reverse\ServiceProvider::class,
+        Sandbox\ServiceProvider::class,
+        Transfer\ServiceProvider::class,
+        Security\ServiceProvider::class,
+        ProfitSharing\ServiceProvider::class,
+    ];
+
+    /**
+     * @var array
+     */
+    protected $defaultConfig = [
+        'http' => [
+            'base_uri' => 'https://api.mch.weixin.qq.com/',
+        ],
+    ];
+
+    /**
+     * Build payment scheme for product.
+     *
+     * @param string $productId
+     *
+     * @return string
+     */
+    public function scheme(string $productId): string
+    {
+        $params = [
+            'appid' => $this['config']->app_id,
+            'mch_id' => $this['config']->mch_id,
+            'time_stamp' => time(),
+            'nonce_str' => uniqid(),
+            'product_id' => $productId,
+        ];
+
+        $params['sign'] = Support\generate_sign($params, $this['config']->key);
+
+        return 'weixin://wxpay/bizpayurl?'.http_build_query($params);
+    }
+
+    /**
+     * @param string $codeUrl
+     *
+     * @return string
+     */
+    public function codeUrlScheme(string $codeUrl)
+    {
+        return \sprintf('weixin://wxpay/bizpayurl?sr=%s', $codeUrl);
+    }
+
+    /**
+     * @param \Closure $closure
+     *
+     * @return \Symfony\Component\HttpFoundation\Response
+     *
+     * @codeCoverageIgnore
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\Exception
+     */
+    public function handlePaidNotify(Closure $closure)
+    {
+        return (new Notify\Paid($this))->handle($closure);
+    }
+
+    /**
+     * @param \Closure $closure
+     *
+     * @return \Symfony\Component\HttpFoundation\Response
+     *
+     * @codeCoverageIgnore
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\Exception
+     */
+    public function handleRefundedNotify(Closure $closure)
+    {
+        return (new Notify\Refunded($this))->handle($closure);
+    }
+
+    /**
+     * @param \Closure $closure
+     *
+     * @return \Symfony\Component\HttpFoundation\Response
+     *
+     * @codeCoverageIgnore
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\Exception
+     */
+    public function handleScannedNotify(Closure $closure)
+    {
+        return (new Notify\Scanned($this))->handle($closure);
+    }
+
+    /**
+     * Set sub-merchant.
+     *
+     * @param string      $mchId
+     * @param string|null $appId
+     *
+     * @return $this
+     */
+    public function setSubMerchant(string $mchId, string $appId = null)
+    {
+        $this['config']->set('sub_mch_id', $mchId);
+        $this['config']->set('sub_appid', $appId);
+
+        return $this;
+    }
+
+    /**
+     * @return bool
+     */
+    public function inSandbox(): bool
+    {
+        return (bool) $this['config']->get('sandbox');
+    }
+
+    /**
+     * @param string|null $endpoint
+     *
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function getKey(string $endpoint = null)
+    {
+        if ('sandboxnew/pay/getsignkey' === $endpoint) {
+            return $this['config']->key;
+        }
+
+        $key = $this->inSandbox() ? $this['sandbox']->getKey() : $this['config']->key;
+
+        if (empty($key)) {
+            throw new InvalidArgumentException('config key should not empty.');
+        }
+
+        if (32 !== strlen($key)) {
+            throw new InvalidArgumentException(sprintf("'%s' should be 32 chars length.", $key));
+        }
+
+        return $key;
+    }
+
+    /**
+     * @param string $name
+     * @param array  $arguments
+     *
+     * @return mixed
+     */
+    public function __call($name, $arguments)
+    {
+        return call_user_func_array([$this['base'], $name], $arguments);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Base/Client.php b/vendor/overtrue/wechat/src/Payment/Base/Client.php
new file mode 100644
index 0000000..75d4c9a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Base/Client.php
@@ -0,0 +1,54 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Base;
+
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+class Client extends BaseClient
+{
+    /**
+     * Pay the order.
+     *
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function pay(array $params)
+    {
+        $params['appid'] = $this->app['config']->app_id;
+
+        return $this->request($this->wrap('pay/micropay'), $params);
+    }
+
+    /**
+     * Get openid by auth code.
+     *
+     * @param string $authCode
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function authCodeToOpenid(string $authCode)
+    {
+        return $this->request('tools/authcodetoopenid', [
+            'appid' => $this->app['config']->app_id,
+            'auth_code' => $authCode,
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Base/ServiceProvider.php
new file mode 100644
index 0000000..71aebd9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Base/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Base;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['base'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Bill/Client.php b/vendor/overtrue/wechat/src/Payment/Bill/Client.php
new file mode 100644
index 0000000..20a9b63
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Bill/Client.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Bill;
+
+use EasyWeChat\Kernel\Http\StreamResponse;
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+class Client extends BaseClient
+{
+    /**
+     * Download bill history as a table file.
+     *
+     * @param string $date
+     * @param string $type
+     * @param array  $optional
+     *
+     * @return \EasyWeChat\Kernel\Http\StreamResponse|\Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(string $date, string $type = 'ALL', array $optional = [])
+    {
+        $params = [
+            'appid' => $this->app['config']->app_id,
+            'bill_date' => $date,
+            'bill_type' => $type,
+        ] + $optional;
+
+        $response = $this->requestRaw($this->wrap('pay/downloadbill'), $params);
+
+        if (0 === strpos($response->getBody()->getContents(), '<xml>')) {
+            return $this->castResponseToType($response, $this->app['config']->get('response_type'));
+        }
+
+        return StreamResponse::buildFromPsrResponse($response);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Bill/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Bill/ServiceProvider.php
new file mode 100644
index 0000000..3fd98d4
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Bill/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Bill;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['bill'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Coupon/Client.php b/vendor/overtrue/wechat/src/Payment/Coupon/Client.php
new file mode 100644
index 0000000..101a42b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Coupon/Client.php
@@ -0,0 +1,77 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Coupon;
+
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author tianyong90 <412039588@qq.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * send a cash coupon.
+     *
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function send(array $params)
+    {
+        $params['appid'] = $this->app['config']->app_id;
+        $params['openid_count'] = 1;
+
+        return $this->safeRequest('mmpaymkttransfers/send_coupon', $params);
+    }
+
+    /**
+     * query a coupon stock.
+     *
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function stock(array $params)
+    {
+        $params['appid'] = $this->app['config']->app_id;
+
+        return $this->request('mmpaymkttransfers/query_coupon_stock', $params);
+    }
+
+    /**
+     * query a info of coupon.
+     *
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function info(array $params)
+    {
+        $params['appid'] = $this->app['config']->app_id;
+
+        return $this->request('mmpaymkttransfers/querycouponsinfo', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Coupon/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Coupon/ServiceProvider.php
new file mode 100644
index 0000000..513734b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Coupon/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Coupon;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['coupon'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Jssdk/Client.php b/vendor/overtrue/wechat/src/Payment/Jssdk/Client.php
new file mode 100644
index 0000000..c507453
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Jssdk/Client.php
@@ -0,0 +1,135 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Jssdk;
+
+use EasyWeChat\BasicService\Jssdk\Client as JssdkClient;
+use EasyWeChat\Kernel\Support;
+use Overtrue\Socialite\AccessTokenInterface;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends JssdkClient
+{
+    /**
+     * [WeixinJSBridge] Generate js config for payment.
+     *
+     * <pre>
+     * WeixinJSBridge.invoke(
+     *  'getBrandWCPayRequest',
+     *  ...
+     * );
+     * </pre>
+     *
+     * @param string $prepayId
+     * @param bool   $json
+     *
+     * @return string|array
+     */
+    public function bridgeConfig(string $prepayId, bool $json = true)
+    {
+        $params = [
+            'appId' => $this->app['config']->sub_appid ?: $this->app['config']->app_id,
+            'timeStamp' => strval(time()),
+            'nonceStr' => uniqid(),
+            'package' => "prepay_id=$prepayId",
+            'signType' => 'MD5',
+        ];
+
+        $params['paySign'] = Support\generate_sign($params, $this->app['config']->key, 'md5');
+
+        return $json ? json_encode($params) : $params;
+    }
+
+    /**
+     * [JSSDK] Generate js config for payment.
+     *
+     * <pre>
+     * wx.chooseWXPay({...});
+     * </pre>
+     *
+     * @param string $prepayId
+     *
+     * @return array
+     */
+    public function sdkConfig(string $prepayId): array
+    {
+        $config = $this->bridgeConfig($prepayId, false);
+
+        $config['timestamp'] = $config['timeStamp'];
+        unset($config['timeStamp']);
+
+        return $config;
+    }
+
+    /**
+     * Generate app payment parameters.
+     *
+     * @param string $prepayId
+     *
+     * @return array
+     */
+    public function appConfig(string $prepayId): array
+    {
+        $params = [
+            'appid' => $this->app['config']->app_id,
+            'partnerid' => $this->app['config']->mch_id,
+            'prepayid' => $prepayId,
+            'noncestr' => uniqid(),
+            'timestamp' => time(),
+            'package' => 'Sign=WXPay',
+        ];
+
+        $params['sign'] = Support\generate_sign($params, $this->app['config']->key);
+
+        return $params;
+    }
+
+    /**
+     * Generate js config for share user address.
+     *
+     * @param string|\Overtrue\Socialite\AccessTokenInterface $accessToken
+     * @param bool                                            $json
+     *
+     * @return string|array
+     */
+    public function shareAddressConfig($accessToken, bool $json = true)
+    {
+        if ($accessToken instanceof AccessTokenInterface) {
+            $accessToken = $accessToken->getToken();
+        }
+
+        $params = [
+            'appId' => $this->app['config']->app_id,
+            'scope' => 'jsapi_address',
+            'timeStamp' => strval(time()),
+            'nonceStr' => uniqid(),
+            'signType' => 'SHA1',
+        ];
+
+        $signParams = [
+            'appid' => $params['appId'],
+            'url' => $this->getUrl(),
+            'timestamp' => $params['timeStamp'],
+            'noncestr' => $params['nonceStr'],
+            'accesstoken' => strval($accessToken),
+        ];
+
+        ksort($signParams);
+
+        $params['addrSign'] = sha1(urldecode(http_build_query($signParams)));
+
+        return $json ? json_encode($params) : $params;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Jssdk/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Jssdk/ServiceProvider.php
new file mode 100644
index 0000000..24f2a76
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Jssdk/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Jssdk;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['jssdk'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Kernel/BaseClient.php b/vendor/overtrue/wechat/src/Payment/Kernel/BaseClient.php
new file mode 100644
index 0000000..b06d648
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Kernel/BaseClient.php
@@ -0,0 +1,194 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Kernel;
+
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\Kernel\Traits\HasHttpRequests;
+use EasyWeChat\Payment\Application;
+use GuzzleHttp\MessageFormatter;
+use GuzzleHttp\Middleware;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Class BaseClient.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class BaseClient
+{
+    use HasHttpRequests { request as performRequest; }
+
+    /**
+     * @var \EasyWeChat\Payment\Application
+     */
+    protected $app;
+
+    /**
+     * Constructor.
+     *
+     * @param \EasyWeChat\Payment\Application $app
+     */
+    public function __construct(Application $app)
+    {
+        $this->app = $app;
+
+        $this->setHttpClient($this->app['http_client']);
+    }
+
+    /**
+     * Extra request params.
+     *
+     * @return array
+     */
+    protected function prepends()
+    {
+        return [];
+    }
+
+    /**
+     * Make a API request.
+     *
+     * @param string $endpoint
+     * @param array  $params
+     * @param string $method
+     * @param array  $options
+     * @param bool   $returnResponse
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function request(string $endpoint, array $params = [], $method = 'post', array $options = [], $returnResponse = false)
+    {
+        $base = [
+            'mch_id' => $this->app['config']['mch_id'],
+            'nonce_str' => uniqid(),
+            'sub_mch_id' => $this->app['config']['sub_mch_id'],
+            'sub_appid' => $this->app['config']['sub_appid'],
+        ];
+
+        $params = array_filter(array_merge($base, $this->prepends(), $params), 'strlen');
+
+        $secretKey = $this->app->getKey($endpoint);
+        if ('HMAC-SHA256' === ($params['sign_type'] ?? 'MD5')) {
+            $encryptMethod = function ($str) use ($secretKey) {
+                return hash_hmac('sha256', $str, $secretKey);
+            };
+        } else {
+            $encryptMethod = 'md5';
+        }
+        $params['sign'] = Support\generate_sign($params, $secretKey, $encryptMethod);
+
+        $options = array_merge([
+            'body' => Support\XML::build($params),
+        ], $options);
+
+        $this->pushMiddleware($this->logMiddleware(), 'log');
+
+        $response = $this->performRequest($endpoint, $method, $options);
+
+        return $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
+    }
+
+    /**
+     * Log the request.
+     *
+     * @return \Closure
+     */
+    protected function logMiddleware()
+    {
+        $formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG);
+
+        return Middleware::log($this->app['logger'], $formatter);
+    }
+
+    /**
+     * Make a request and return raw response.
+     *
+     * @param string $endpoint
+     * @param array  $params
+     * @param string $method
+     * @param array  $options
+     *
+     * @return ResponseInterface
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function requestRaw(string $endpoint, array $params = [], $method = 'post', array $options = [])
+    {
+        /** @var ResponseInterface $response */
+        $response = $this->request($endpoint, $params, $method, $options, true);
+
+        return $response;
+    }
+
+    /**
+     * Make a request and return an array.
+     *
+     * @param string $endpoint
+     * @param array  $params
+     * @param string $method
+     * @param array  $options
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function requestArray(string $endpoint, array $params = [], $method = 'post', array $options = []): array
+    {
+        $response = $this->requestRaw($endpoint, $params, $method, $options);
+
+        return $this->castResponseToType($response, 'array');
+    }
+
+    /**
+     * Request with SSL.
+     *
+     * @param string $endpoint
+     * @param array  $params
+     * @param string $method
+     * @param array  $options
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function safeRequest($endpoint, array $params, $method = 'post', array $options = [])
+    {
+        $options = array_merge([
+            'cert' => $this->app['config']->get('cert_path'),
+            'ssl_key' => $this->app['config']->get('key_path'),
+        ], $options);
+
+        return $this->request($endpoint, $params, $method, $options);
+    }
+
+    /**
+     * Wrapping an API endpoint.
+     *
+     * @param string $endpoint
+     *
+     * @return string
+     */
+    protected function wrap(string $endpoint): string
+    {
+        return $this->app->inSandbox() ? "sandboxnew/{$endpoint}" : $endpoint;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/InvalidSignException.php b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/InvalidSignException.php
new file mode 100644
index 0000000..cdd25ba
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/InvalidSignException.php
@@ -0,0 +1,18 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Kernel\Exceptions;
+
+use EasyWeChat\Kernel\Exceptions\Exception;
+
+class InvalidSignException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/SandboxException.php b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/SandboxException.php
new file mode 100644
index 0000000..01f9dd5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/SandboxException.php
@@ -0,0 +1,18 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Kernel\Exceptions;
+
+use EasyWeChat\Kernel\Exceptions\Exception;
+
+class SandboxException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Merchant/Client.php b/vendor/overtrue/wechat/src/Payment/Merchant/Client.php
new file mode 100644
index 0000000..f31a7e2
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Merchant/Client.php
@@ -0,0 +1,94 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Merchant;
+
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Add sub-merchant.
+     *
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function addSubMerchant(array $params)
+    {
+        return $this->manage($params, ['action' => 'add']);
+    }
+
+    /**
+     * Query sub-merchant by merchant id.
+     *
+     * @param string $id
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function querySubMerchantByMerchantId(string $id)
+    {
+        $params = [
+            'micro_mch_id' => $id,
+        ];
+
+        return $this->manage($params, ['action' => 'query']);
+    }
+
+    /**
+     * Query sub-merchant by wechat id.
+     *
+     * @param string $id
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function querySubMerchantByWeChatId(string $id)
+    {
+        $params = [
+            'recipient_wechatid' => $id,
+        ];
+
+        return $this->manage($params, ['action' => 'query']);
+    }
+
+    /**
+     * @param array $params
+     * @param array $query
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function manage(array $params, array $query)
+    {
+        $params = array_merge($params, [
+            'appid' => $this->app['config']->app_id,
+            'nonce_str' => '',
+            'sub_mch_id' => '',
+            'sub_appid' => '',
+        ]);
+
+        return $this->safeRequest('secapi/mch/submchmanage', $params, 'post', compact('query'));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Merchant/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Merchant/ServiceProvider.php
new file mode 100644
index 0000000..5d05c95
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Merchant/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Merchant;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['merchant'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Handler.php b/vendor/overtrue/wechat/src/Payment/Notify/Handler.php
new file mode 100644
index 0000000..c657433
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Notify/Handler.php
@@ -0,0 +1,201 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Notify;
+
+use Closure;
+use EasyWeChat\Kernel\Exceptions\Exception;
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\Kernel\Support\XML;
+use EasyWeChat\Payment\Kernel\Exceptions\InvalidSignException;
+use Symfony\Component\HttpFoundation\Response;
+
+abstract class Handler
+{
+    const SUCCESS = 'SUCCESS';
+    const FAIL = 'FAIL';
+
+    /**
+     * @var \EasyWeChat\Payment\Application
+     */
+    protected $app;
+
+    /**
+     * @var array
+     */
+    protected $message;
+
+    /**
+     * @var string|null
+     */
+    protected $fail;
+
+    /**
+     * @var array
+     */
+    protected $attributes = [];
+
+    /**
+     * Check sign.
+     * If failed, throws an exception.
+     *
+     * @var bool
+     */
+    protected $check = true;
+
+    /**
+     * Respond with sign.
+     *
+     * @var bool
+     */
+    protected $sign = false;
+
+    /**
+     * @param \EasyWeChat\Payment\Application $app
+     */
+    public function __construct($app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * Handle incoming notify.
+     *
+     * @param \Closure $closure
+     *
+     * @return \Symfony\Component\HttpFoundation\Response
+     */
+    abstract public function handle(Closure $closure);
+
+    /**
+     * @param string $message
+     */
+    public function fail(string $message)
+    {
+        $this->fail = $message;
+    }
+
+    /**
+     * @param array $attributes
+     * @param bool  $sign
+     *
+     * @return $this
+     */
+    public function respondWith(array $attributes, bool $sign = false)
+    {
+        $this->attributes = $attributes;
+        $this->sign = $sign;
+
+        return $this;
+    }
+
+    /**
+     * Build xml and return the response to WeChat.
+     *
+     * @return \Symfony\Component\HttpFoundation\Response
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function toResponse(): Response
+    {
+        $base = [
+            'return_code' => is_null($this->fail) ? static::SUCCESS : static::FAIL,
+            'return_msg' => $this->fail,
+        ];
+
+        $attributes = array_merge($base, $this->attributes);
+
+        if ($this->sign) {
+            $attributes['sign'] = Support\generate_sign($attributes, $this->app->getKey());
+        }
+
+        return new Response(XML::build($attributes));
+    }
+
+    /**
+     * Return the notify message from request.
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\Exception
+     */
+    public function getMessage(): array
+    {
+        if (!empty($this->message)) {
+            return $this->message;
+        }
+
+        try {
+            $message = XML::parse(strval($this->app['request']->getContent()));
+        } catch (\Throwable $e) {
+            throw new Exception('Invalid request XML: '.$e->getMessage(), 400);
+        }
+
+        if (!is_array($message) || empty($message)) {
+            throw new Exception('Invalid request XML.', 400);
+        }
+
+        if ($this->check) {
+            $this->validate($message);
+        }
+
+        return $this->message = $message;
+    }
+
+    /**
+     * Decrypt message.
+     *
+     * @param string $key
+     *
+     * @return string|null
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\Exception
+     */
+    public function decryptMessage(string $key)
+    {
+        $message = $this->getMessage();
+        if (empty($message[$key])) {
+            return null;
+        }
+
+        return Support\AES::decrypt(
+            base64_decode($message[$key], true), md5($this->app['config']->key), '', OPENSSL_RAW_DATA, 'AES-256-ECB'
+        );
+    }
+
+    /**
+     * Validate the request params.
+     *
+     * @param array $message
+     *
+     * @throws \EasyWeChat\Payment\Kernel\Exceptions\InvalidSignException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    protected function validate(array $message)
+    {
+        $sign = $message['sign'];
+        unset($message['sign']);
+
+        if (Support\generate_sign($message, $this->app->getKey()) !== $sign) {
+            throw new InvalidSignException();
+        }
+    }
+
+    /**
+     * @param mixed $result
+     */
+    protected function strict($result)
+    {
+        if (true !== $result && is_null($this->fail)) {
+            $this->fail(strval($result));
+        }
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Paid.php b/vendor/overtrue/wechat/src/Payment/Notify/Paid.php
new file mode 100644
index 0000000..c54b9cd
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Notify/Paid.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Notify;
+
+use Closure;
+
+class Paid extends Handler
+{
+    /**
+     * @param \Closure $closure
+     *
+     * @return \Symfony\Component\HttpFoundation\Response
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\Exception
+     */
+    public function handle(Closure $closure)
+    {
+        $this->strict(
+            \call_user_func($closure, $this->getMessage(), [$this, 'fail'])
+        );
+
+        return $this->toResponse();
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Refunded.php b/vendor/overtrue/wechat/src/Payment/Notify/Refunded.php
new file mode 100644
index 0000000..8241892
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Notify/Refunded.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Notify;
+
+use Closure;
+use EasyWeChat\Kernel\Support\XML;
+
+class Refunded extends Handler
+{
+    protected $check = false;
+
+    /**
+     * @param \Closure $closure
+     *
+     * @return \Symfony\Component\HttpFoundation\Response
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\Exception
+     */
+    public function handle(Closure $closure)
+    {
+        $this->strict(
+            \call_user_func($closure, $this->getMessage(), $this->reqInfo(), [$this, 'fail'])
+        );
+
+        return $this->toResponse();
+    }
+
+    /**
+     * Decrypt the `req_info` from request message.
+     *
+     * @return array
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\Exception
+     */
+    public function reqInfo()
+    {
+        return XML::parse($this->decryptMessage('req_info'));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Scanned.php b/vendor/overtrue/wechat/src/Payment/Notify/Scanned.php
new file mode 100644
index 0000000..023a905
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Notify/Scanned.php
@@ -0,0 +1,60 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Notify;
+
+use Closure;
+
+class Scanned extends Handler
+{
+    protected $check = false;
+
+    /**
+     * @var string|null
+     */
+    protected $alert;
+
+    /**
+     * @param string $message
+     */
+    public function alert(string $message)
+    {
+        $this->alert = $message;
+    }
+
+    /**
+     * @param \Closure $closure
+     *
+     * @return \Symfony\Component\HttpFoundation\Response
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\Exception
+     */
+    public function handle(Closure $closure)
+    {
+        $result = \call_user_func($closure, $this->getMessage(), [$this, 'fail'], [$this, 'alert']);
+
+        $attributes = [
+            'result_code' => is_null($this->alert) && is_null($this->fail) ? static::SUCCESS : static::FAIL,
+            'err_code_des' => $this->alert,
+        ];
+
+        if (is_null($this->alert) && is_string($result)) {
+            $attributes += [
+                'appid' => $this->app['config']->app_id,
+                'mch_id' => $this->app['config']->mch_id,
+                'nonce_str' => uniqid(),
+                'prepay_id' => $result,
+            ];
+        }
+
+        return $this->respondWith($attributes, true)->toResponse();
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Order/Client.php b/vendor/overtrue/wechat/src/Payment/Order/Client.php
new file mode 100644
index 0000000..ff2ca2b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Order/Client.php
@@ -0,0 +1,126 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Order;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\Kernel\Support\Collection;
+use EasyWeChat\Payment\Kernel\BaseClient;
+use Psr\Http\Message\ResponseInterface;
+
+class Client extends BaseClient
+{
+    /**
+     * Unify order.
+     *
+     * @param array $params
+     * @param bool  $isContract
+     *
+     * @return ResponseInterface|Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function unify(array $params, $isContract = false)
+    {
+        if (empty($params['spbill_create_ip'])) {
+            $params['spbill_create_ip'] = ('NATIVE' === $params['trade_type']) ? Support\get_server_ip() : Support\get_client_ip();
+        }
+
+        $params['appid'] = $this->app['config']->app_id;
+        $params['notify_url'] = $params['notify_url'] ?? $this->app['config']['notify_url'];
+
+        if ($isContract) {
+            $params['contract_appid'] = $this->app['config']['app_id'];
+            $params['contract_mchid'] = $this->app['config']['mch_id'];
+            $params['request_serial'] = $params['request_serial'] ?? time();
+            $params['contract_notify_url'] = $params['contract_notify_url'] ?? $this->app['config']['contract_notify_url'];
+
+            return $this->request($this->wrap('pay/contractorder'), $params);
+        }
+
+        return $this->request($this->wrap('pay/unifiedorder'), $params);
+    }
+
+    /**
+     * Query order by out trade number.
+     *
+     * @param string $number
+     *
+     * @return ResponseInterface|Collection|array|object|string
+     *
+     * @throws InvalidArgumentException
+     * @throws InvalidConfigException
+     */
+    public function queryByOutTradeNumber(string $number)
+    {
+        return $this->query([
+            'out_trade_no' => $number,
+        ]);
+    }
+
+    /**
+     * Query order by transaction id.
+     *
+     * @param string $transactionId
+     *
+     * @return ResponseInterface|Collection|array|object|string
+     *
+     * @throws InvalidArgumentException
+     * @throws InvalidConfigException
+     */
+    public function queryByTransactionId(string $transactionId)
+    {
+        return $this->query([
+            'transaction_id' => $transactionId,
+        ]);
+    }
+
+    /**
+     * @param array $params
+     *
+     * @return ResponseInterface|Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function query(array $params)
+    {
+        $params['appid'] = $this->app['config']->app_id;
+
+        return $this->request($this->wrap('pay/orderquery'), $params);
+    }
+
+    /**
+     * Close order by out_trade_no.
+     *
+     * @param string $tradeNo
+     *
+     * @return ResponseInterface|Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function close(string $tradeNo)
+    {
+        $params = [
+            'appid' => $this->app['config']->app_id,
+            'out_trade_no' => $tradeNo,
+        ];
+
+        return $this->request($this->wrap('pay/closeorder'), $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Order/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Order/ServiceProvider.php
new file mode 100644
index 0000000..9e781c0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Order/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Order;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['order'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/ProfitSharing/Client.php b/vendor/overtrue/wechat/src/Payment/ProfitSharing/Client.php
new file mode 100644
index 0000000..d16ca2b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/ProfitSharing/Client.php
@@ -0,0 +1,201 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\ProfitSharing;
+
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author ClouderSky <clouder.flow@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * {@inheritdoc}.
+     */
+    protected function prepends()
+    {
+        return [
+            'sign_type' => 'HMAC-SHA256',
+        ];
+    }
+
+    /**
+     * Add profit sharing receiver.
+     * 服务商代子商户发起添加分账接收方请求.
+     * 后续可通过发起分账请求将结算后的钱分到该分账接收方.
+     *
+     * @param array $receiver 分账接收方对象,json格式
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function addReceiver(array $receiver)
+    {
+        $params = [
+            'appid' => $this->app['config']->app_id,
+            'receiver' => json_encode(
+                $receiver, JSON_UNESCAPED_UNICODE
+            ),
+        ];
+
+        return $this->request(
+            'pay/profitsharingaddreceiver', $params
+        );
+    }
+
+    /**
+     * Delete profit sharing receiver.
+     * 服务商代子商户发起删除分账接收方请求.
+     * 删除后不支持将结算后的钱分到该分账接收方.
+     *
+     * @param array $receiver 分账接收方对象,json格式
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function deleteReceiver(array $receiver)
+    {
+        $params = [
+            'appid' => $this->app['config']->app_id,
+            'receiver' => json_encode(
+                $receiver, JSON_UNESCAPED_UNICODE
+            ),
+        ];
+
+        return $this->request(
+            'pay/profitsharingremovereceiver', $params
+        );
+    }
+
+    /**
+     * Single profit sharing.
+     * 请求单次分账.
+     *
+     * @param string $transactionId 微信支付订单号
+     * @param string $outOrderNo    商户系统内部的分账单号
+     * @param array  $receivers     分账接收方列表
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function share(
+        string $transactionId,
+        string $outOrderNo,
+        array $receivers
+    ) {
+        $params = [
+            'appid' => $this->app['config']->app_id,
+            'transaction_id' => $transactionId,
+            'out_order_no' => $outOrderNo,
+            'receivers' => json_encode(
+                $receivers, JSON_UNESCAPED_UNICODE
+            ),
+        ];
+
+        return $this->safeRequest(
+            'secapi/pay/profitsharing', $params
+        );
+    }
+
+    /**
+     * Multi profit sharing.
+     * 请求多次分账.
+     *
+     * @param string $transactionId 微信支付订单号
+     * @param string $outOrderNo    商户系统内部的分账单号
+     * @param array  $receivers     分账接收方列表
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function multiShare(
+        string $transactionId,
+        string $outOrderNo,
+        array $receivers
+    ) {
+        $params = [
+            'appid' => $this->app['config']->app_id,
+            'transaction_id' => $transactionId,
+            'out_order_no' => $outOrderNo,
+            'receivers' => json_encode(
+                $receivers, JSON_UNESCAPED_UNICODE
+            ),
+        ];
+
+        return $this->safeRequest(
+            'secapi/pay/multiprofitsharing', $params
+        );
+    }
+
+    /**
+     * Finish profit sharing.
+     * 完结分账.
+     *
+     * @param array $params
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function markOrderAsFinished(array $params)
+    {
+        $params['appid'] = $this->app['config']->app_id;
+        $params['sub_appid'] = null;
+
+        return $this->safeRequest(
+            'secapi/pay/profitsharingfinish', $params
+        );
+    }
+
+    /**
+     * Query profit sharing result.
+     * 查询分账结果.
+     *
+     * @param string $transactionId 微信支付订单号
+     * @param string $outOrderNo    商户系统内部的分账单号
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function query(
+        string $transactionId, string $outOrderNo
+    ) {
+        $params = [
+            'sub_appid' => null,
+            'transaction_id' => $transactionId,
+            'out_order_no' => $outOrderNo,
+        ];
+
+        return $this->request(
+            'pay/profitsharingquery', $params
+        );
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/ProfitSharing/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/ProfitSharing/ServiceProvider.php
new file mode 100644
index 0000000..d247d53
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/ProfitSharing/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\ProfitSharing;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author ClouderSky <clouder.flow@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['profit_sharing'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Redpack/Client.php b/vendor/overtrue/wechat/src/Payment/Redpack/Client.php
new file mode 100644
index 0000000..a764c39
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Redpack/Client.php
@@ -0,0 +1,88 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Redpack;
+
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author tianyong90 <412039588@qq.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Query redpack.
+     *
+     * @param mixed $mchBillno
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function info($mchBillno)
+    {
+        $params = is_array($mchBillno) ? $mchBillno : ['mch_billno' => $mchBillno];
+        $base = [
+            'appid' => $this->app['config']->app_id,
+            'bill_type' => 'MCHT',
+        ];
+
+        return $this->safeRequest('mmpaymkttransfers/gethbinfo', array_merge($base, $params));
+    }
+
+    /**
+     * Send normal redpack.
+     *
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function sendNormal(array $params)
+    {
+        $base = [
+            'total_num' => 1,
+            'client_ip' => $params['client_ip'] ?? Support\get_server_ip(),
+            'wxappid' => $this->app['config']->app_id,
+        ];
+
+        return $this->safeRequest('mmpaymkttransfers/sendredpack', array_merge($base, $params));
+    }
+
+    /**
+     * Send group redpack.
+     *
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function sendGroup(array $params)
+    {
+        $base = [
+            'amt_type' => 'ALL_RAND',
+            'wxappid' => $this->app['config']->app_id,
+        ];
+
+        return $this->safeRequest('mmpaymkttransfers/sendgroupredpack', array_merge($base, $params));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Redpack/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Redpack/ServiceProvider.php
new file mode 100644
index 0000000..af36f35
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Redpack/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Redpack;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['redpack'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Refund/Client.php b/vendor/overtrue/wechat/src/Payment/Refund/Client.php
new file mode 100644
index 0000000..128c6e7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Refund/Client.php
@@ -0,0 +1,159 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Refund;
+
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+class Client extends BaseClient
+{
+    /**
+     * Refund by out trade number.
+     *
+     * @param string $number
+     * @param string $refundNumber
+     * @param int    $totalFee
+     * @param int    $refundFee
+     * @param array  $optional
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function byOutTradeNumber(string $number, string $refundNumber, int $totalFee, int $refundFee, array $optional = [])
+    {
+        return $this->refund($refundNumber, $totalFee, $refundFee, array_merge($optional, ['out_trade_no' => $number]));
+    }
+
+    /**
+     * Refund by transaction id.
+     *
+     * @param string $transactionId
+     * @param string $refundNumber
+     * @param int    $totalFee
+     * @param int    $refundFee
+     * @param array  $optional
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function byTransactionId(string $transactionId, string $refundNumber, int $totalFee, int $refundFee, array $optional = [])
+    {
+        return $this->refund($refundNumber, $totalFee, $refundFee, array_merge($optional, ['transaction_id' => $transactionId]));
+    }
+
+    /**
+     * Query refund by transaction id.
+     *
+     * @param string $transactionId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function queryByTransactionId(string $transactionId)
+    {
+        return $this->query($transactionId, 'transaction_id');
+    }
+
+    /**
+     * Query refund by out trade number.
+     *
+     * @param string $outTradeNumber
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function queryByOutTradeNumber(string $outTradeNumber)
+    {
+        return $this->query($outTradeNumber, 'out_trade_no');
+    }
+
+    /**
+     * Query refund by out refund number.
+     *
+     * @param string $outRefundNumber
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function queryByOutRefundNumber(string $outRefundNumber)
+    {
+        return $this->query($outRefundNumber, 'out_refund_no');
+    }
+
+    /**
+     * Query refund by refund id.
+     *
+     * @param string $refundId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function queryByRefundId(string $refundId)
+    {
+        return $this->query($refundId, 'refund_id');
+    }
+
+    /**
+     * Refund.
+     *
+     * @param string $refundNumber
+     * @param int    $totalFee
+     * @param int    $refundFee
+     * @param array  $optional
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function refund(string $refundNumber, int $totalFee, int $refundFee, $optional = [])
+    {
+        $params = array_merge([
+            'out_refund_no' => $refundNumber,
+            'total_fee' => $totalFee,
+            'refund_fee' => $refundFee,
+            'appid' => $this->app['config']->app_id,
+        ], $optional);
+
+        return $this->safeRequest($this->wrap(
+            $this->app->inSandbox() ? 'pay/refund' : 'secapi/pay/refund'
+        ), $params);
+    }
+
+    /**
+     * Query refund.
+     *
+     * @param string $number
+     * @param string $type
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function query(string $number, string $type)
+    {
+        $params = [
+            'appid' => $this->app['config']->app_id,
+            $type => $number,
+        ];
+
+        return $this->request($this->wrap('pay/refundquery'), $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Refund/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Refund/ServiceProvider.php
new file mode 100644
index 0000000..faa4e89
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Refund/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Refund;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['refund'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Reverse/Client.php b/vendor/overtrue/wechat/src/Payment/Reverse/Client.php
new file mode 100644
index 0000000..990e6e6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Reverse/Client.php
@@ -0,0 +1,67 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Reverse;
+
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+class Client extends BaseClient
+{
+    /**
+     * Reverse order by out trade number.
+     *
+     * @param string $outTradeNumber
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function byOutTradeNumber(string $outTradeNumber)
+    {
+        return $this->reverse($outTradeNumber, 'out_trade_no');
+    }
+
+    /**
+     * Reverse order by transaction_id.
+     *
+     * @param string $transactionId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function byTransactionId(string $transactionId)
+    {
+        return $this->reverse($transactionId, 'transaction_id');
+    }
+
+    /**
+     * Reverse order.
+     *
+     * @param string $number
+     * @param string $type
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function reverse(string $number, string $type)
+    {
+        $params = [
+            'appid' => $this->app['config']->app_id,
+            $type => $number,
+        ];
+
+        return $this->safeRequest($this->wrap('secapi/pay/reverse'), $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Reverse/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Reverse/ServiceProvider.php
new file mode 100644
index 0000000..2417874
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Reverse/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Reverse;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['reverse'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Sandbox/Client.php b/vendor/overtrue/wechat/src/Payment/Sandbox/Client.php
new file mode 100644
index 0000000..9f1ec7c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Sandbox/Client.php
@@ -0,0 +1,60 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Sandbox;
+
+use EasyWeChat\Kernel\Traits\InteractsWithCache;
+use EasyWeChat\Payment\Kernel\BaseClient;
+use EasyWeChat\Payment\Kernel\Exceptions\SandboxException;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    use InteractsWithCache;
+
+    /**
+     * @return string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Payment\Kernel\Exceptions\SandboxException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getKey(): string
+    {
+        if ($cache = $this->getCache()->get($this->getCacheKey())) {
+            return $cache;
+        }
+
+        $response = $this->requestArray('sandboxnew/pay/getsignkey');
+
+        if ('SUCCESS' === $response['return_code']) {
+            $this->getCache()->set($this->getCacheKey(), $key = $response['sandbox_signkey'], 24 * 3600);
+
+            return $key;
+        }
+
+        throw new SandboxException($response['retmsg'] ?? $response['return_msg']);
+    }
+
+    /**
+     * @return string
+     */
+    protected function getCacheKey(): string
+    {
+        return 'easywechat.payment.sandbox.'.md5($this->app['config']->app_id.$this->app['config']['mch_id']);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Sandbox/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Sandbox/ServiceProvider.php
new file mode 100644
index 0000000..d0b5d6e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Sandbox/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Sandbox;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * @param \Pimple\Container $app
+     */
+    public function register(Container $app)
+    {
+        $app['sandbox'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Security/Client.php b/vendor/overtrue/wechat/src/Payment/Security/Client.php
new file mode 100644
index 0000000..01aa752
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Security/Client.php
@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Security;
+
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getPublicKey()
+    {
+        $params = [
+            'sign_type' => 'MD5',
+        ];
+
+        return $this->safeRequest('https://fraud.mch.weixin.qq.com/risk/getpublickey', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Security/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Security/ServiceProvider.php
new file mode 100644
index 0000000..e0e31f8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Security/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Security;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['security'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Transfer/Client.php b/vendor/overtrue/wechat/src/Payment/Transfer/Client.php
new file mode 100644
index 0000000..b8a7c44
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Transfer/Client.php
@@ -0,0 +1,122 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Transfer;
+
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use function EasyWeChat\Kernel\Support\get_server_ip;
+use function EasyWeChat\Kernel\Support\rsa_public_encrypt;
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author AC <alexever@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Query MerchantPay to balance.
+     *
+     * @param string $partnerTradeNo
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function queryBalanceOrder(string $partnerTradeNo)
+    {
+        $params = [
+            'appid' => $this->app['config']->app_id,
+            'mch_id' => $this->app['config']->mch_id,
+            'partner_trade_no' => $partnerTradeNo,
+        ];
+
+        return $this->safeRequest('mmpaymkttransfers/gettransferinfo', $params);
+    }
+
+    /**
+     * Send MerchantPay to balance.
+     *
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function toBalance(array $params)
+    {
+        $base = [
+            'mch_id' => null,
+            'mchid' => $this->app['config']->mch_id,
+            'mch_appid' => $this->app['config']->app_id,
+        ];
+
+        if (empty($params['spbill_create_ip'])) {
+            $params['spbill_create_ip'] = get_server_ip();
+        }
+
+        return $this->safeRequest('mmpaymkttransfers/promotion/transfers', array_merge($base, $params));
+    }
+
+    /**
+     * Query MerchantPay order to BankCard.
+     *
+     * @param string $partnerTradeNo
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function queryBankCardOrder(string $partnerTradeNo)
+    {
+        $params = [
+            'mch_id' => $this->app['config']->mch_id,
+            'partner_trade_no' => $partnerTradeNo,
+        ];
+
+        return $this->safeRequest('mmpaysptrans/query_bank', $params);
+    }
+
+    /**
+     * Send MerchantPay to BankCard.
+     *
+     * @param array $params
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function toBankCard(array $params)
+    {
+        foreach (['bank_code', 'partner_trade_no', 'enc_bank_no', 'enc_true_name', 'amount'] as $key) {
+            if (empty($params[$key])) {
+                throw new RuntimeException(\sprintf('"%s" is required.', $key));
+            }
+        }
+
+        $publicKey = file_get_contents($this->app['config']->get('rsa_public_key_path'));
+
+        $params['enc_bank_no'] = rsa_public_encrypt($params['enc_bank_no'], $publicKey);
+        $params['enc_true_name'] = rsa_public_encrypt($params['enc_true_name'], $publicKey);
+
+        return $this->safeRequest('mmpaysptrans/pay_bank', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Transfer/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Transfer/ServiceProvider.php
new file mode 100644
index 0000000..0e27aad
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Transfer/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Transfer;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['transfer'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Agent/Client.php b/vendor/overtrue/wechat/src/Work/Agent/Client.php
new file mode 100644
index 0000000..9e11aac
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Agent/Client.php
@@ -0,0 +1,68 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Agent;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * This is WeWork Agent Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get agent.
+     *
+     * @param int $agentId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function get(int $agentId)
+    {
+        $params = [
+            'agentid' => $agentId,
+        ];
+
+        return $this->httpGet('cgi-bin/agent/get', $params);
+    }
+
+    /**
+     * Set agent.
+     *
+     * @param int   $agentId
+     * @param array $attributes
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function set(int $agentId, array $attributes)
+    {
+        return $this->httpPostJson('cgi-bin/agent/set', array_merge(['agentid' => $agentId], $attributes));
+    }
+
+    /**
+     * Get agent list.
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function list()
+    {
+        return $this->httpGet('cgi-bin/agent/list');
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Agent/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Agent/ServiceProvider.php
new file mode 100644
index 0000000..b85a6f5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Agent/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Agent;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['agent'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Application.php b/vendor/overtrue/wechat/src/Work/Application.php
new file mode 100644
index 0000000..ccd7c48
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Application.php
@@ -0,0 +1,100 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work;
+
+use EasyWeChat\Kernel\ServiceContainer;
+use EasyWeChat\Work\MiniProgram\Application as MiniProgram;
+
+/**
+ * Application.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ *
+ * @property \EasyWeChat\Work\OA\Client                        $oa
+ * @property \EasyWeChat\Work\Auth\AccessToken                 $access_token
+ * @property \EasyWeChat\Work\Agent\Client                     $agent
+ * @property \EasyWeChat\Work\Department\Client                $department
+ * @property \EasyWeChat\Work\Media\Client                     $media
+ * @property \EasyWeChat\Work\Menu\Client                      $menu
+ * @property \EasyWeChat\Work\Message\Client                   $message
+ * @property \EasyWeChat\Work\Message\Messenger                $messenger
+ * @property \EasyWeChat\Work\User\Client                      $user
+ * @property \EasyWeChat\Work\User\TagClient                   $tag
+ * @property \EasyWeChat\Work\Server\ServiceProvider           $server
+ * @property \EasyWeChat\Work\Jssdk\Client                     $jssdk
+ * @property \Overtrue\Socialite\Providers\WeWorkProvider      $oauth
+ * @property \EasyWeChat\Work\Invoice\Client                   $invoice
+ * @property \EasyWeChat\Work\Chat\Client                      $chat
+ * @property \EasyWeChat\Work\ExternalContact\Client           $external_contact
+ * @property \EasyWeChat\Work\ExternalContact\ContactWayClient $contact_way
+ * @property \EasyWeChat\Work\ExternalContact\StatisticsClient $external_contact_statistics
+ * @property \EasyWeChat\Work\ExternalContact\MessageClient    $external_contact_message
+ * @property \EasyWeChat\Work\GroupRobot\Client                $group_robot
+ * @property \EasyWeChat\Work\GroupRobot\Messenger             $group_robot_messenger
+ *
+ * @method mixed getCallbackIp()
+ */
+class Application extends ServiceContainer
+{
+    /**
+     * @var array
+     */
+    protected $providers = [
+        OA\ServiceProvider::class,
+        Auth\ServiceProvider::class,
+        Base\ServiceProvider::class,
+        Menu\ServiceProvider::class,
+        OAuth\ServiceProvider::class,
+        User\ServiceProvider::class,
+        Agent\ServiceProvider::class,
+        Media\ServiceProvider::class,
+        Message\ServiceProvider::class,
+        Department\ServiceProvider::class,
+        Server\ServiceProvider::class,
+        Jssdk\ServiceProvider::class,
+        Invoice\ServiceProvider::class,
+        Chat\ServiceProvider::class,
+        ExternalContact\ServiceProvider::class,
+        GroupRobot\ServiceProvider::class,
+    ];
+
+    /**
+     * @var array
+     */
+    protected $defaultConfig = [
+        // http://docs.guzzlephp.org/en/stable/request-options.html
+        'http' => [
+            'base_uri' => 'https://qyapi.weixin.qq.com/',
+        ],
+    ];
+
+    /**
+     * Creates the miniProgram application.
+     *
+     * @return \EasyWeChat\Work\MiniProgram\Application
+     */
+    public function miniProgram(): MiniProgram
+    {
+        return new MiniProgram($this->getConfig());
+    }
+
+    /**
+     * @param string $method
+     * @param array  $arguments
+     *
+     * @return mixed
+     */
+    public function __call($method, $arguments)
+    {
+        return $this['base']->$method(...$arguments);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Auth/AccessToken.php b/vendor/overtrue/wechat/src/Work/Auth/AccessToken.php
new file mode 100644
index 0000000..56d5218
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Auth/AccessToken.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Auth;
+
+use EasyWeChat\Kernel\AccessToken as BaseAccessToken;
+
+/**
+ * Class AccessToken.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class AccessToken extends BaseAccessToken
+{
+    /**
+     * @var string
+     */
+    protected $endpointToGetToken = 'cgi-bin/gettoken';
+
+    /**
+     * @var int
+     */
+    protected $safeSeconds = 0;
+
+    /**
+     * Credential for get token.
+     *
+     * @return array
+     */
+    protected function getCredentials(): array
+    {
+        return [
+            'corpid' => $this->app['config']['corp_id'],
+            'corpsecret' => $this->app['config']['secret'],
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Auth/ServiceProvider.php
new file mode 100644
index 0000000..5f0dba9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Auth/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Auth;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        isset($app['access_token']) || $app['access_token'] = function ($app) {
+            return new AccessToken($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Base/Client.php b/vendor/overtrue/wechat/src/Work/Base/Client.php
new file mode 100644
index 0000000..97b5445
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Base/Client.php
@@ -0,0 +1,34 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Base;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get callback ip.
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function getCallbackIp()
+    {
+        return $this->httpGet('cgi-bin/getcallbackip');
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Base/ServiceProvider.php
new file mode 100644
index 0000000..5e28fd1
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Base/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Base;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * @param Container $app
+     */
+    public function register(Container $app)
+    {
+        $app['base'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Chat/Client.php b/vendor/overtrue/wechat/src/Work/Chat/Client.php
new file mode 100644
index 0000000..0d4fdec
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Chat/Client.php
@@ -0,0 +1,82 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Chat;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author XiaolonY <xiaolony@hotmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get chat.
+     *
+     * @param string $chatId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function get(string $chatId)
+    {
+        return $this->httpGet('cgi-bin/appchat/get', ['chatid' => $chatId]);
+    }
+
+    /**
+     * Create chat.
+     *
+     * @param array $data
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create(array $data)
+    {
+        return $this->httpPostJson('cgi-bin/appchat/create', $data);
+    }
+
+    /**
+     * Update chat.
+     *
+     * @param string $chatId
+     * @param array  $data
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(string $chatId, array $data)
+    {
+        return $this->httpPostJson('cgi-bin/appchat/update', array_merge(['chatid' => $chatId], $data));
+    }
+
+    /**
+     * Send a message.
+     *
+     * @param array $message
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function send(array $message)
+    {
+        return $this->httpPostJson('cgi-bin/appchat/send', $message);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Chat/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Chat/ServiceProvider.php
new file mode 100644
index 0000000..5bf9b2c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Chat/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Chat;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author XiaolonY <xiaolony@hotmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['chat'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Department/Client.php b/vendor/overtrue/wechat/src/Work/Department/Client.php
new file mode 100644
index 0000000..c34cc86
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Department/Client.php
@@ -0,0 +1,81 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Department;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * This is WeWork Department Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Create a department.
+     *
+     * @param array $data
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create(array $data)
+    {
+        return $this->httpPostJson('cgi-bin/department/create', $data);
+    }
+
+    /**
+     * Update a department.
+     *
+     * @param int   $id
+     * @param array $data
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(int $id, array $data)
+    {
+        return $this->httpPostJson('cgi-bin/department/update', array_merge(compact('id'), $data));
+    }
+
+    /**
+     * Delete a department.
+     *
+     * @param int $id
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function delete($id)
+    {
+        return $this->httpGet('cgi-bin/department/delete', compact('id'));
+    }
+
+    /**
+     * Get department lists.
+     *
+     * @param int|null $id
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function list($id = null)
+    {
+        return $this->httpGet('cgi-bin/department/list', compact('id'));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Department/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Department/ServiceProvider.php
new file mode 100644
index 0000000..2fe5bc6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Department/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Department;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['department'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/Client.php b/vendor/overtrue/wechat/src/Work/ExternalContact/Client.php
new file mode 100644
index 0000000..477686f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/ExternalContact/Client.php
@@ -0,0 +1,118 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\ExternalContact;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * 获取配置了客户联系功能的成员列表.
+     *
+     * @see https://work.weixin.qq.com/api/doc#90000/90135/91554
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function getFollowUsers()
+    {
+        return $this->httpGet('cgi-bin/externalcontact/get_follow_user_list');
+    }
+
+    /**
+     * 获取外部联系人列表.
+     *
+     * @see https://work.weixin.qq.com/api/doc#90000/90135/91555
+     *
+     * @param string $userId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function list(string $userId)
+    {
+        return $this->httpGet('cgi-bin/externalcontact/list', [
+            'userid' => $userId,
+        ]);
+    }
+
+    /**
+     * 获取外部联系人详情.
+     *
+     * @see https://work.weixin.qq.com/api/doc#90000/90135/91556
+     *
+     * @param string $externalUserId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function get(string $externalUserId)
+    {
+        return $this->httpGet('cgi-bin/externalcontact/get', [
+            'external_userid' => $externalUserId,
+        ]);
+    }
+
+    /**
+     * 获取离职成员的客户列表.
+     *
+     * @see https://work.weixin.qq.com/api/doc#90000/90135/91563
+     *
+     * @param int $pageId
+     * @param int $pageSize
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function getUnassigned(int $pageId = 0, int $pageSize = 1000)
+    {
+        return $this->httpPostJson('cgi-bin/externalcontact/get_unassigned_list', [
+            'page_id' => $pageId,
+            'page_size' => $pageSize,
+        ]);
+    }
+
+    /**
+     * 离职成员的外部联系人再分配.
+     *
+     * @see https://work.weixin.qq.com/api/doc#90000/90135/91564
+     *
+     * @param string $externalUserId
+     * @param string $handoverUserId
+     * @param string $takeoverUserId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function transfer(string $externalUserId, string $handoverUserId, string $takeoverUserId)
+    {
+        $params = [
+            'external_userid' => $externalUserId,
+            'handover_userid' => $handoverUserId,
+            'takeover_userid' => $takeoverUserId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/externalcontact/transfer', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/ContactWayClient.php b/vendor/overtrue/wechat/src/Work/ExternalContact/ContactWayClient.php
new file mode 100644
index 0000000..7cfc2bb
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/ExternalContact/ContactWayClient.php
@@ -0,0 +1,98 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\ExternalContact;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class ContactWayClient.
+ *
+ * @author milkmeowo <milkmeowo@gmail.com>
+ */
+class ContactWayClient extends BaseClient
+{
+    /**
+     * 配置客户联系「联系我」方式.
+     *
+     * @param int   $type
+     * @param int   $scene
+     * @param array $config
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create(int $type, int $scene, array $config = [])
+    {
+        $params = array_merge([
+            'type' => $type,
+            'scene' => $scene,
+        ], $config);
+
+        return $this->httpPostJson('cgi-bin/externalcontact/add_contact_way', $params);
+    }
+
+    /**
+     * 获取企业已配置的「联系我」方式.
+     *
+     * @param string $configId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(string $configId)
+    {
+        return $this->httpPostJson('cgi-bin/externalcontact/get_contact_way', [
+            'config_id' => $configId,
+        ]);
+    }
+
+    /**
+     * 更新企业已配置的「联系我」方式.
+     *
+     * @param string $configId
+     * @param array  $config
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(string $configId, array $config = [])
+    {
+        $params = array_merge([
+            'config_id' => $configId,
+        ], $config);
+
+        return $this->httpPostJson('cgi-bin/externalcontact/update_contact_way', $params);
+    }
+
+    /**
+     * 删除企业已配置的「联系我」方式.
+     *
+     * @param string $configId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete(string $configId)
+    {
+        return $this->httpPostJson('cgi-bin/externalcontact/del_contact_way', [
+            'config_id' => $configId,
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/MessageClient.php b/vendor/overtrue/wechat/src/Work/ExternalContact/MessageClient.php
new file mode 100644
index 0000000..ce00f80
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/ExternalContact/MessageClient.php
@@ -0,0 +1,168 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\ExternalContact;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class MessageClient.
+ *
+ * @author milkmeowo <milkmeowo@gmail.com>
+ */
+class MessageClient extends BaseClient
+{
+    /**
+     * Required attributes.
+     *
+     * @var array
+     */
+    protected $required = ['content', 'media_id', 'title', 'url', 'pic_media_id', 'appid', 'page'];
+
+    protected $textMessage = [
+        'content' => '',
+    ];
+
+    protected $imageMessage = [
+        'media_id' => '',
+    ];
+
+    protected $linkMessage = [
+        'title' => '',
+        'picurl' => '',
+        'desc' => '',
+        'url' => '',
+    ];
+
+    protected $miniprogramMessage = [
+        'title' => '',
+        'pic_media_id' => '',
+        'appid' => '',
+        'page' => '',
+    ];
+
+    /**
+     * 添加企业群发消息模板
+     *
+     * @see https://work.weixin.qq.com/api/doc#90000/90135/91560
+     *
+     * @param array $msg
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function submit(array $msg)
+    {
+        $params = $this->formatMessage($msg);
+
+        return $this->httpPostJson('cgi-bin/externalcontact/add_msg_template', $params);
+    }
+
+    /**
+     * 获取企业群发消息发送结果.
+     *
+     * @see https://work.weixin.qq.com/api/doc#90000/90135/91561
+     *
+     * @param string $msgId
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(string $msgId)
+    {
+        return $this->httpPostJson('cgi-bin/externalcontact/get_group_msg_result', [
+            'msgid' => $msgId,
+        ]);
+    }
+
+    /**
+     * 发送新客户欢迎语.
+     *
+     * @see https://work.weixin.qq.com/api/doc#90000/90135/91688
+     *
+     * @param string $welcomeCode
+     * @param array  $msg
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function sendWelcome(string $welcomeCode, array $msg)
+    {
+        $formattedMsg = $this->formatMessage($msg);
+
+        $params = array_merge($formattedMsg, [
+            'welcome_code' => $welcomeCode,
+        ]);
+
+        return $this->httpPostJson('cgi-bin/externalcontact/send_welcome_msg', $params);
+    }
+
+    /**
+     * @param array $data
+     *
+     * @return array
+     *
+     * @throws InvalidArgumentException
+     */
+    protected function formatMessage(array $data = [])
+    {
+        $params = $data;
+
+        if (!empty($params['text'])) {
+            $params['text'] = $this->formatFields($params['text'], $this->textMessage);
+        }
+
+        if (!empty($params['image'])) {
+            $params['image'] = $this->formatFields($params['image'], $this->imageMessage);
+        }
+
+        if (!empty($params['link'])) {
+            $params['link'] = $this->formatFields($params['link'], $this->linkMessage);
+        }
+
+        if (!empty($params['miniprogram'])) {
+            $params['miniprogram'] = $this->formatFields($params['miniprogram'], $this->miniprogramMessage);
+        }
+
+        return $params;
+    }
+
+    /**
+     * @param array $data
+     * @param array $default
+     *
+     * @return array
+     *
+     * @throws InvalidArgumentException
+     */
+    protected function formatFields(array $data = [], array $default = [])
+    {
+        $params = array_merge($default, $data);
+        foreach ($params as $key => $value) {
+            if (in_array($key, $this->required, true) && empty($value) && empty($default[$key])) {
+                throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key));
+            }
+
+            $params[$key] = empty($value) ? $default[$key] : $value;
+        }
+
+        return $params;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/ExternalContact/ServiceProvider.php
new file mode 100644
index 0000000..b76630a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/ExternalContact/ServiceProvider.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\ExternalContact;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['external_contact'] = function ($app) {
+            return new Client($app);
+        };
+
+        $app['contact_way'] = function ($app) {
+            return new ContactWayClient($app);
+        };
+
+        $app['external_contact_statistics'] = function ($app) {
+            return new StatisticsClient($app);
+        };
+
+        $app['external_contact_message'] = function ($app) {
+            return new MessageClient($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/StatisticsClient.php b/vendor/overtrue/wechat/src/Work/ExternalContact/StatisticsClient.php
new file mode 100644
index 0000000..c6922df
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/ExternalContact/StatisticsClient.php
@@ -0,0 +1,47 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\ExternalContact;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class StatisticsClient.
+ *
+ * @author milkmeowo <milkmeowo@gmail.com>
+ */
+class StatisticsClient extends BaseClient
+{
+    /**
+     * 获取员工行为数据.
+     *
+     * @see https://work.weixin.qq.com/api/doc#90000/90135/91580
+     *
+     * @param array  $userIds
+     * @param string $from
+     * @param string $to
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function userBehavior(array $userIds, string $from, string $to)
+    {
+        $params = [
+            'userid' => $userIds,
+            'start_time' => $from,
+            'end_time' => $to,
+        ];
+
+        return $this->httpPostJson('cgi-bin/externalcontact/get_user_behavior_data', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Client.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Client.php
new file mode 100644
index 0000000..dcc1a8f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Client.php
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\GroupRobot;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Work\GroupRobot\Messages\Message;
+
+/**
+ * Class Client.
+ *
+ * @author her-cat <i@her-cat.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @param string|Message $message
+     *
+     * @return Messenger
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function message($message)
+    {
+        return (new Messenger($this))->message($message);
+    }
+
+    /**
+     * @param string $key
+     * @param array  $message
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function send(string $key, array $message)
+    {
+        return $this->httpPostJson('cgi-bin/webhook/send', $message, ['key' => $key]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Image.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Image.php
new file mode 100644
index 0000000..14beb81
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Image.php
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\GroupRobot\Messages;
+
+/**
+ * Class Image.
+ *
+ * @author her-cat <i@her-cat.com>
+ */
+class Image extends Message
+{
+    /**
+     * @var string
+     */
+    protected $type = 'image';
+
+    /**
+     * @var array
+     */
+    protected $properties = ['base64', 'md5'];
+
+    /**
+     * Image constructor.
+     *
+     * @param string $base64
+     * @param string $md5
+     */
+    public function __construct(string $base64, string $md5)
+    {
+        parent::__construct(compact('base64', 'md5'));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Markdown.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Markdown.php
new file mode 100644
index 0000000..dfe6980
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Markdown.php
@@ -0,0 +1,40 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\GroupRobot\Messages;
+
+/**
+ * Class Markdown.
+ *
+ * @author her-cat <i@her-cat.com>
+ */
+class Markdown extends Message
+{
+    /**
+     * @var string
+     */
+    protected $type = 'markdown';
+
+    /**
+     * @var array
+     */
+    protected $properties = ['content'];
+
+    /**
+     * Markdown constructor.
+     *
+     * @param string $content
+     */
+    public function __construct(string $content)
+    {
+        parent::__construct(compact('content'));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Message.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Message.php
new file mode 100644
index 0000000..50201b9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Message.php
@@ -0,0 +1,23 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\GroupRobot\Messages;
+
+use EasyWeChat\Kernel\Messages\Message as BaseMessage;
+
+/**
+ * Class Message.
+ *
+ * @author her-cat <i@her-cat.com>
+ */
+class Message extends BaseMessage
+{
+}
diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/News.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/News.php
new file mode 100644
index 0000000..835023e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/News.php
@@ -0,0 +1,55 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\GroupRobot\Messages;
+
+/**
+ * Class News.
+ *
+ * @author her-cat <i@her-cat.com>
+ */
+class News extends Message
+{
+    /**
+     * @var string
+     */
+    protected $type = 'news';
+
+    /**
+     * @var array
+     */
+    protected $properties = ['items'];
+
+    /**
+     * News constructor.
+     *
+     * @param array $items
+     */
+    public function __construct(array $items = [])
+    {
+        parent::__construct(compact('items'));
+    }
+
+    /**
+     * @param array $data
+     * @param array $aliases
+     *
+     * @return array
+     */
+    public function propertiesToArray(array $data, array $aliases = []): array
+    {
+        return ['articles' => array_map(function ($item) {
+            if ($item instanceof NewsItem) {
+                return $item->toJsonArray();
+            }
+        }, $this->get('items'))];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/NewsItem.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/NewsItem.php
new file mode 100644
index 0000000..2d206de
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/NewsItem.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\GroupRobot\Messages;
+
+/**
+ * Class NewsItem.
+ *
+ * @author her-cat <i@her-cat.com>
+ */
+class NewsItem extends Message
+{
+    /**
+     * @var string
+     */
+    protected $type = 'news';
+
+    /**
+     * @var array
+     */
+    protected $properties = [
+        'title',
+        'description',
+        'url',
+        'image',
+    ];
+
+    public function toJsonArray()
+    {
+        return [
+            'title' => $this->get('title'),
+            'description' => $this->get('description'),
+            'url' => $this->get('url'),
+            'picurl' => $this->get('image'),
+        ];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Text.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Text.php
new file mode 100644
index 0000000..a1e6dda
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Text.php
@@ -0,0 +1,70 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\GroupRobot\Messages;
+
+/**
+ * Class Text.
+ *
+ * @author her-cat <i@her-cat.com>
+ */
+class Text extends Message
+{
+    /**
+     * @var string
+     */
+    protected $type = 'text';
+
+    /**
+     * @var array
+     */
+    protected $properties = ['content', 'mentioned_list', 'mentioned_mobile_list'];
+
+    /**
+     * Text constructor.
+     *
+     * @param string       $content
+     * @param string|array $userIds
+     * @param string|array $mobiles
+     */
+    public function __construct(string $content, $userIds = [], $mobiles = [])
+    {
+        parent::__construct([
+            'content' => $content,
+            'mentioned_list' => (array) $userIds,
+            'mentioned_mobile_list' => (array) $mobiles,
+        ]);
+    }
+
+    /**
+     * @param array $userIds
+     *
+     * @return Text
+     */
+    public function mention($userIds)
+    {
+        $this->set('mentioned_list', (array) $userIds);
+
+        return $this;
+    }
+
+    /**
+     * @param array $mobiles
+     *
+     * @return Text
+     */
+    public function mentionByMobile($mobiles)
+    {
+        $this->set('mentioned_mobile_list', (array) $mobiles);
+
+        return $this;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messenger.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messenger.php
new file mode 100644
index 0000000..573296a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messenger.php
@@ -0,0 +1,129 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\GroupRobot;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Work\GroupRobot\Messages\Message;
+use EasyWeChat\Work\GroupRobot\Messages\Text;
+
+/**
+ * Class Messenger.
+ *
+ * @author her-cat <i@her-cat.com>
+ */
+class Messenger
+{
+    /**
+     * @var Client
+     */
+    protected $client;
+
+    /**
+     * @var Message|null
+     */
+    protected $message;
+
+    /**
+     * @var string|null
+     */
+    protected $groupKey;
+
+    /**
+     * Messenger constructor.
+     *
+     * @param Client $client
+     */
+    public function __construct(Client $client)
+    {
+        $this->client = $client;
+    }
+
+    /**
+     * @param string|Message $message
+     *
+     * @return Messenger
+     *
+     * @throws InvalidArgumentException
+     */
+    public function message($message)
+    {
+        if (is_string($message) || is_numeric($message)) {
+            $message = new Text($message);
+        }
+
+        if (!($message instanceof  Message)) {
+            throw new InvalidArgumentException('Invalid message.');
+        }
+
+        $this->message = $message;
+
+        return $this;
+    }
+
+    /**
+     * @param string $groupKey
+     *
+     * @return Messenger
+     */
+    public function toGroup(string $groupKey)
+    {
+        $this->groupKey = $groupKey;
+
+        return $this;
+    }
+
+    /**
+     * @param string|Message|null $message
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws RuntimeException
+     * @throws InvalidArgumentException
+     * @throws InvalidConfigException
+     */
+    public function send($message = null)
+    {
+        if ($message) {
+            $this->message($message);
+        }
+
+        if (empty($this->message)) {
+            throw new RuntimeException('No message to send.');
+        }
+
+        if (is_null($this->groupKey)) {
+            throw new RuntimeException('No group key specified.');
+        }
+
+        $message = $this->message->transformForJsonRequest();
+
+        return $this->client->send($this->groupKey, $message);
+    }
+
+    /**
+     * @param string $property
+     *
+     * @return mixed
+     *
+     * @throws InvalidArgumentException
+     */
+    public function __get($property)
+    {
+        if (property_exists($this, $property)) {
+            return $this->$property;
+        }
+
+        throw new InvalidArgumentException(sprintf('No property named "%s"', $property));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/GroupRobot/ServiceProvider.php
new file mode 100644
index 0000000..d72d630
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/GroupRobot/ServiceProvider.php
@@ -0,0 +1,37 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\GroupRobot;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author her-cat <i@her-cat.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function register(Container $app)
+    {
+        $app['group_robot'] = function ($app) {
+            return new Client($app);
+        };
+
+        $app['group_robot_messenger'] = function ($app) {
+            return new Messenger($app['group_robot']);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Invoice/Client.php b/vendor/overtrue/wechat/src/Work/Invoice/Client.php
new file mode 100644
index 0000000..edbd34b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Invoice/Client.php
@@ -0,0 +1,100 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Invoice;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @param string $cardId
+     * @param string $encryptCode
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(string $cardId, string $encryptCode)
+    {
+        $params = [
+            'card_id' => $cardId,
+            'encrypt_code' => $encryptCode,
+        ];
+
+        return $this->httpPostJson('cgi-bin/card/invoice/reimburse/getinvoiceinfo', $params);
+    }
+
+    /**
+     * @param array $invoices
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function select(array $invoices)
+    {
+        $params = [
+            'item_list' => $invoices,
+        ];
+
+        return $this->httpPostJson('cgi-bin/card/invoice/reimburse/getinvoiceinfobatch', $params);
+    }
+
+    /**
+     * @param string $cardId
+     * @param string $encryptCode
+     * @param string $status
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(string $cardId, string $encryptCode, string $status)
+    {
+        $params = [
+            'card_id' => $cardId,
+            'encrypt_code' => $encryptCode,
+            'reimburse_status' => $status,
+        ];
+
+        return $this->httpPostJson('cgi-bin/card/invoice/reimburse/updateinvoicestatus', $params);
+    }
+
+    /**
+     * @param array  $invoices
+     * @param string $openid
+     * @param string $status
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function batchUpdate(array $invoices, string $openid, string $status)
+    {
+        $params = [
+            'openid' => $openid,
+            'reimburse_status' => $status,
+            'invoice_list' => $invoices,
+        ];
+
+        return $this->httpPostJson('cgi-bin/card/invoice/reimburse/updatestatusbatch', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Invoice/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Invoice/ServiceProvider.php
new file mode 100644
index 0000000..3259d80
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Invoice/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Invoice;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['invoice'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Jssdk/Client.php b/vendor/overtrue/wechat/src/Work/Jssdk/Client.php
new file mode 100644
index 0000000..40fc368
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Jssdk/Client.php
@@ -0,0 +1,68 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Jssdk;
+
+use EasyWeChat\BasicService\Jssdk\Client as BaseClient;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    protected $ticketEndpoint = 'https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket';
+
+    /**
+     * @return string
+     */
+    protected function getAppId()
+    {
+        return $this->app['config']->get('corp_id');
+    }
+
+    /**
+     * @param bool   $refresh
+     * @param string $type
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|mixed|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws RuntimeException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function getAgentTicket(bool $refresh = false, string $type = 'agent_config')
+    {
+        $cacheKey = sprintf('easywechat.work.jssdk.ticket.%s.%s', $type, $this->getAppId());
+
+        if (!$refresh && $this->getCache()->has($cacheKey)) {
+            return $this->getCache()->get($cacheKey);
+        }
+
+        /** @var array<string, mixed> $result */
+        $result = $this->castResponseToType(
+            $this->requestRaw('cgi-bin/ticket/get', 'GET', ['query' => ['type' => $type]]),
+            'array'
+        );
+
+        $this->getCache()->set($cacheKey, $result, $result['expires_in'] - 500);
+
+        if (!$this->getCache()->has($cacheKey)) {
+            throw new RuntimeException('Failed to cache jssdk ticket.');
+        }
+
+        return $result;
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Jssdk/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Jssdk/ServiceProvider.php
new file mode 100644
index 0000000..efb509c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Jssdk/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Jssdk;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['jssdk'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Media/Client.php b/vendor/overtrue/wechat/src/Work/Media/Client.php
new file mode 100644
index 0000000..16e8a94
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Media/Client.php
@@ -0,0 +1,116 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Media;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Http\StreamResponse;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get media.
+     *
+     * @param string $mediaId
+     *
+     * @return array|\EasyWeChat\Kernel\Http\Response|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function get(string $mediaId)
+    {
+        $response = $this->requestRaw('cgi-bin/media/get', 'GET', [
+            'query' => [
+                'media_id' => $mediaId,
+            ],
+        ]);
+
+        if (false !== stripos($response->getHeaderLine('Content-Type'), 'text/plain')) {
+            return $this->castResponseToType($response, $this->app['config']->get('response_type'));
+        }
+
+        return StreamResponse::buildFromPsrResponse($response);
+    }
+
+    /**
+     * Upload Image.
+     *
+     * @param string $path
+     *
+     * @return mixed
+     */
+    public function uploadImage(string $path)
+    {
+        return $this->upload('image', $path);
+    }
+
+    /**
+     * Upload Voice.
+     *
+     * @param string $path
+     *
+     * @return mixed
+     */
+    public function uploadVoice(string $path)
+    {
+        return $this->upload('voice', $path);
+    }
+
+    /**
+     * Upload Video.
+     *
+     * @param string $path
+     *
+     * @return mixed
+     */
+    public function uploadVideo(string $path)
+    {
+        return $this->upload('video', $path);
+    }
+
+    /**
+     * Upload File.
+     *
+     * @param string $path
+     *
+     * @return mixed
+     */
+    public function uploadFile(string $path)
+    {
+        return $this->upload('file', $path);
+    }
+
+    /**
+     * Upload media.
+     *
+     * @param string $type
+     * @param string $path
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function upload(string $type, string $path)
+    {
+        $files = [
+            'media' => $path,
+        ];
+
+        return $this->httpUpload('cgi-bin/media/upload', $files, [], compact('type'));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Media/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Media/ServiceProvider.php
new file mode 100644
index 0000000..450d156
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Media/ServiceProvider.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Media;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['media'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Menu/Client.php b/vendor/overtrue/wechat/src/Work/Menu/Client.php
new file mode 100644
index 0000000..e57a3a1
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Menu/Client.php
@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Menu;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get menu.
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function get()
+    {
+        return $this->httpGet('cgi-bin/menu/get', ['agentid' => $this->app['config']['agent_id']]);
+    }
+
+    /**
+     * Create menu for the given agent.
+     *
+     * @param array $data
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create(array $data)
+    {
+        return $this->httpPostJson('cgi-bin/menu/create', $data, ['agentid' => $this->app['config']['agent_id']]);
+    }
+
+    /**
+     * Delete menu.
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function delete()
+    {
+        return $this->httpGet('cgi-bin/menu/delete', ['agentid' => $this->app['config']['agent_id']]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Menu/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Menu/ServiceProvider.php
new file mode 100644
index 0000000..02c4290
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Menu/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Menu;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['menu'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Message/Client.php b/vendor/overtrue/wechat/src/Work/Message/Client.php
new file mode 100644
index 0000000..1497586
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Message/Client.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Message;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Messages\Message;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * @param string|\EasyWeChat\Kernel\Messages\Message $message
+     *
+     * @return \EasyWeChat\Work\Message\Messenger
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function message($message)
+    {
+        return (new Messenger($this))->message($message);
+    }
+
+    /**
+     * @param array $message
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function send(array $message)
+    {
+        return $this->httpPostJson('cgi-bin/message/send', $message);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Message/Messenger.php b/vendor/overtrue/wechat/src/Work/Message/Messenger.php
new file mode 100644
index 0000000..62bc62d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Message/Messenger.php
@@ -0,0 +1,205 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Message;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Messages\Message;
+use EasyWeChat\Kernel\Messages\Text;
+
+/**
+ * Class MessageBuilder.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Messenger
+{
+    /**
+     * @var \EasyWeChat\Kernel\Messages\Message;
+     */
+    protected $message;
+
+    /**
+     * @var array
+     */
+    protected $to = ['touser' => '@all'];
+
+    /**
+     * @var int
+     */
+    protected $agentId;
+
+    /**
+     * @var bool
+     */
+    protected $secretive = false;
+
+    /**
+     * @var \EasyWeChat\Work\Message\Client
+     */
+    protected $client;
+
+    /**
+     * MessageBuilder constructor.
+     *
+     * @param \EasyWeChat\Work\Message\Client $client
+     */
+    public function __construct(Client $client)
+    {
+        $this->client = $client;
+    }
+
+    /**
+     * Set message to send.
+     *
+     * @param string|Message $message
+     *
+     * @return \EasyWeChat\Work\Message\Messenger
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function message($message)
+    {
+        if (is_string($message) || is_numeric($message)) {
+            $message = new Text($message);
+        }
+
+        if (!($message instanceof Message)) {
+            throw new InvalidArgumentException('Invalid message.');
+        }
+
+        $this->message = $message;
+
+        return $this;
+    }
+
+    /**
+     * @param int $agentId
+     *
+     * @return \EasyWeChat\Work\Message\Messenger
+     */
+    public function ofAgent(int $agentId)
+    {
+        $this->agentId = $agentId;
+
+        return $this;
+    }
+
+    /**
+     * @param array|string $userIds
+     *
+     * @return \EasyWeChat\Work\Message\Messenger
+     */
+    public function toUser($userIds)
+    {
+        return $this->setRecipients($userIds, 'touser');
+    }
+
+    /**
+     * @param array|string $partyIds
+     *
+     * @return \EasyWeChat\Work\Message\Messenger
+     */
+    public function toParty($partyIds)
+    {
+        return $this->setRecipients($partyIds, 'toparty');
+    }
+
+    /**
+     * @param array|string $tagIds
+     *
+     * @return \EasyWeChat\Work\Message\Messenger
+     */
+    public function toTag($tagIds)
+    {
+        return $this->setRecipients($tagIds, 'totag');
+    }
+
+    /**
+     * Keep secret.
+     *
+     * @return \EasyWeChat\Work\Message\Messenger
+     */
+    public function secretive()
+    {
+        $this->secretive = true;
+
+        return $this;
+    }
+
+    /**
+     * @param array|string $ids
+     * @param string       $key
+     *
+     * @return \EasyWeChat\Work\Message\Messenger
+     */
+    protected function setRecipients($ids, string $key): self
+    {
+        if (is_array($ids)) {
+            $ids = implode('|', $ids);
+        }
+
+        $this->to = [$key => $ids];
+
+        return $this;
+    }
+
+    /**
+     * @param \EasyWeChat\Kernel\Messages\Message|string|null $message
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+     */
+    public function send($message = null)
+    {
+        if ($message) {
+            $this->message($message);
+        }
+
+        if (empty($this->message)) {
+            throw new RuntimeException('No message to send.');
+        }
+
+        if (is_null($this->agentId)) {
+            throw new RuntimeException('No agentid specified.');
+        }
+
+        $message = $this->message->transformForJsonRequest(array_merge([
+            'agentid' => $this->agentId,
+            'safe' => intval($this->secretive),
+        ], $this->to));
+
+        $this->secretive = false;
+
+        return $this->client->send($message);
+    }
+
+    /**
+     * Return property.
+     *
+     * @param string $property
+     *
+     * @return mixed
+     *
+     * @throws InvalidArgumentException
+     */
+    public function __get($property)
+    {
+        if (property_exists($this, $property)) {
+            return $this->$property;
+        }
+
+        throw new InvalidArgumentException(sprintf('No property named "%s"', $property));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Message/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Message/ServiceProvider.php
new file mode 100644
index 0000000..18193bd
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Message/ServiceProvider.php
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Message;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['message'] = function ($app) {
+            return new Client($app);
+        };
+
+        $app['messenger'] = function ($app) {
+            $messenger = new Messenger($app['message']);
+
+            if (is_int($app['config']['agent_id'])) {
+                $messenger->ofAgent($app['config']['agent_id']);
+            }
+
+            return $messenger;
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/MiniProgram/Application.php b/vendor/overtrue/wechat/src/Work/MiniProgram/Application.php
new file mode 100644
index 0000000..aa290cd
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/MiniProgram/Application.php
@@ -0,0 +1,44 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\MiniProgram;
+
+use EasyWeChat\MiniProgram\Application as MiniProgram;
+use EasyWeChat\Work\Auth\AccessToken;
+use EasyWeChat\Work\MiniProgram\Auth\Client;
+
+/**
+ * Class Application.
+ *
+ * @author Caikeal <caikeal@qq.com>
+ *
+ * @property \EasyWeChat\Work\MiniProgram\Auth\Client $auth
+ */
+class Application extends MiniProgram
+{
+    /**
+     * Application constructor.
+     *
+     * @param array $config
+     * @param array $prepends
+     */
+    public function __construct(array $config = [], array $prepends = [])
+    {
+        parent::__construct($config, $prepends + [
+            'access_token' => function ($app) {
+                return new AccessToken($app);
+            },
+            'auth' => function ($app) {
+                return new Client($app);
+            },
+        ]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/MiniProgram/Auth/Client.php b/vendor/overtrue/wechat/src/Work/MiniProgram/Auth/Client.php
new file mode 100644
index 0000000..ac9baff
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/MiniProgram/Auth/Client.php
@@ -0,0 +1,39 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\MiniProgram\Auth;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get session info by code.
+     *
+     * @param string $code
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function session(string $code)
+    {
+        $params = [
+            'js_code' => $code,
+            'grant_type' => 'authorization_code',
+        ];
+
+        return $this->httpGet('cgi-bin/miniprogram/jscode2session', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/OA/Client.php b/vendor/overtrue/wechat/src/Work/OA/Client.php
new file mode 100644
index 0000000..483d892
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/OA/Client.php
@@ -0,0 +1,171 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\OA;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Get the checkin data.
+     *
+     * @param int   $startTime
+     * @param int   $endTime
+     * @param array $userList
+     * @param int   $type
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function checkinRecords(int $startTime, int $endTime, array $userList, int $type = 3)
+    {
+        $params = [
+            'opencheckindatatype' => $type,
+            'starttime' => $startTime,
+            'endtime' => $endTime,
+            'useridlist' => $userList,
+        ];
+
+        return $this->httpPostJson('cgi-bin/checkin/getcheckindata', $params);
+    }
+
+    /**
+     * Get the checkin rules.
+     *
+     * @param int   $datetime
+     * @param array $userList
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function checkinRules(int $datetime, array $userList)
+    {
+        $params = [
+            'datetime' => $datetime,
+            'useridlist' => $userList,
+        ];
+
+        return $this->httpPostJson('cgi-bin/checkin/getcheckinoption', $params);
+    }
+
+    /**
+     * Get approval template details.
+     *
+     * @param string $templateId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function approvalTemplate(string $templateId)
+    {
+        $params = [
+            'template_id' => $templateId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/oa/gettemplatedetail', $params);
+    }
+
+    /**
+     * Submit an application for approval.
+     *
+     * @param array $data
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function createApproval(array $data)
+    {
+        return $this->httpPostJson('cgi-bin/oa/applyevent', $data);
+    }
+
+    /**
+     * Get Approval number.
+     *
+     * @param int   $startTime
+     * @param int   $endTime
+     * @param int   $nextCursor
+     * @param int   $size
+     * @param array $filters
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function approvalNumbers(int $startTime, int $endTime, int $nextCursor = 0, int $size = 100, array $filters = [])
+    {
+        $params = [
+            'starttime' => $startTime,
+            'endtime' => $endTime,
+            'cursor' => $nextCursor,
+            'size' => $size > 100 ? 100 : $size,
+            'filters' => $filters,
+        ];
+
+        return $this->httpPostJson('cgi-bin/oa/getapprovalinfo', $params);
+    }
+
+    /**
+     * Get approval detail.
+     *
+     * @param int $number
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function approvalDetail(int $number)
+    {
+        $params = [
+            'sp_no' => $number,
+        ];
+
+        return $this->httpPostJson('cgi-bin/oa/getapprovaldetail', $params);
+    }
+
+    /**
+     * Get Approval Data.
+     *
+     * @param int $startTime
+     * @param int $endTime
+     * @param int $nextNumber
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function approvalRecords(int $startTime, int $endTime, int $nextNumber = null)
+    {
+        $params = [
+            'starttime' => $startTime,
+            'endtime' => $endTime,
+            'next_spnum' => $nextNumber,
+        ];
+
+        return $this->httpPostJson('cgi-bin/corp/getapprovaldata', $params);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/OA/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/OA/ServiceProvider.php
new file mode 100644
index 0000000..5d389c2
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/OA/ServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\OA;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['oa'] = function ($app) {
+            return new Client($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/OAuth/AccessTokenDelegate.php b/vendor/overtrue/wechat/src/Work/OAuth/AccessTokenDelegate.php
new file mode 100644
index 0000000..9acfbbb
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/OAuth/AccessTokenDelegate.php
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\OAuth;
+
+use EasyWeChat\Work\Application;
+use Overtrue\Socialite\AccessTokenInterface;
+
+/**
+ * Class AccessTokenDelegate.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class AccessTokenDelegate implements AccessTokenInterface
+{
+    /**
+     * @var \EasyWeChat\Work\Application
+     */
+    protected $app;
+
+    /**
+     * @param \EasyWeChat\Work\Application $app
+     */
+    public function __construct(Application $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * Return the access token string.
+     *
+     * @return string
+     */
+    public function getToken()
+    {
+        return $this->app['access_token']->getToken()['access_token'];
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/OAuth/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/OAuth/ServiceProvider.php
new file mode 100644
index 0000000..b1fdfce
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/OAuth/ServiceProvider.php
@@ -0,0 +1,62 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\OAuth;
+
+use Overtrue\Socialite\SocialiteManager;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+    public function register(Container $app)
+    {
+        $app['oauth'] = function ($app) {
+            $socialite = (new SocialiteManager([
+                'wework' => [
+                    'client_id' => $app['config']['corp_id'],
+                    'client_secret' => null,
+                    'redirect' => $this->prepareCallbackUrl($app),
+                ],
+            ], $app['request']))->driver('wework');
+
+            $scopes = (array) $app['config']->get('oauth.scopes', ['snsapi_base']);
+
+            if (!empty($scopes)) {
+                $socialite->scopes($scopes);
+            } else {
+                $socialite->setAgentId($app['config']['agent_id']);
+            }
+
+            return $socialite->setAccessToken(new AccessTokenDelegate($app));
+        };
+    }
+
+    /**
+     * Prepare the OAuth callback url for wechat.
+     *
+     * @param Container $app
+     *
+     * @return string
+     */
+    private function prepareCallbackUrl($app)
+    {
+        $callback = $app['config']->get('oauth.callback');
+
+        if (0 === stripos($callback, 'http')) {
+            return $callback;
+        }
+
+        $baseUrl = $app['request']->getSchemeAndHttpHost();
+
+        return $baseUrl.'/'.ltrim($callback, '/');
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Server/Guard.php b/vendor/overtrue/wechat/src/Work/Server/Guard.php
new file mode 100644
index 0000000..b77c6c1
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Server/Guard.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Server;
+
+use EasyWeChat\Kernel\ServerGuard;
+
+/**
+ * Class Guard.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class Guard extends ServerGuard
+{
+    /**
+     * @return $this
+     */
+    public function validate()
+    {
+        return $this;
+    }
+
+    /**
+     * Check the request message safe mode.
+     *
+     * @return bool
+     */
+    protected function isSafeMode(): bool
+    {
+        return true;
+    }
+
+    /**
+     * @return bool
+     */
+    protected function shouldReturnRawResponse(): bool
+    {
+        return !is_null($this->app['request']->get('echostr'));
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Server/Handlers/EchoStrHandler.php b/vendor/overtrue/wechat/src/Work/Server/Handlers/EchoStrHandler.php
new file mode 100644
index 0000000..64555ef
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Server/Handlers/EchoStrHandler.php
@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Server\Handlers;
+
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+use EasyWeChat\Kernel\Decorators\FinallyResult;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Class EchoStrHandler.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class EchoStrHandler implements EventHandlerInterface
+{
+    /**
+     * @var ServiceContainer
+     */
+    protected $app;
+
+    /**
+     * EchoStrHandler constructor.
+     *
+     * @param ServiceContainer $app
+     */
+    public function __construct(ServiceContainer $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * @param mixed $payload
+     *
+     * @return FinallyResult|null
+     */
+    public function handle($payload = null)
+    {
+        if ($decrypted = $this->app['request']->get('echostr')) {
+            $str = $this->app['encryptor']->decrypt(
+                $decrypted,
+                $this->app['request']->get('msg_signature'),
+                $this->app['request']->get('nonce'),
+                $this->app['request']->get('timestamp')
+            );
+
+            return new FinallyResult($str);
+        }
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Server/ServiceProvider.php
new file mode 100644
index 0000000..8a2fc3e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/Server/ServiceProvider.php
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\Server;
+
+use EasyWeChat\Kernel\Encryptor;
+use EasyWeChat\Work\Server\Handlers\EchoStrHandler;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue <i@overtrue.me>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        !isset($app['encryptor']) && $app['encryptor'] = function ($app) {
+            return new Encryptor(
+                $app['config']['corp_id'],
+                $app['config']['token'],
+                $app['config']['aes_key']
+            );
+        };
+
+        !isset($app['server']) && $app['server'] = function ($app) {
+            $guard = new Guard($app);
+            $guard->push(new EchoStrHandler($app));
+
+            return $guard;
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/User/Client.php b/vendor/overtrue/wechat/src/Work/User/Client.php
new file mode 100644
index 0000000..8c07ec6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/User/Client.php
@@ -0,0 +1,251 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\User;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class Client extends BaseClient
+{
+    /**
+     * Create a user.
+     *
+     * @param array $data
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create(array $data)
+    {
+        return $this->httpPostJson('cgi-bin/user/create', $data);
+    }
+
+    /**
+     * Update an exist user.
+     *
+     * @param string $id
+     * @param array  $data
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(string $id, array $data)
+    {
+        return $this->httpPostJson('cgi-bin/user/update', array_merge(['userid' => $id], $data));
+    }
+
+    /**
+     * Delete a user.
+     *
+     * @param string|array $userId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function delete($userId)
+    {
+        if (is_array($userId)) {
+            return $this->batchDelete($userId);
+        }
+
+        return $this->httpGet('cgi-bin/user/delete', ['userid' => $userId]);
+    }
+
+    /**
+     * Batch delete users.
+     *
+     * @param array $userIds
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function batchDelete(array $userIds)
+    {
+        return $this->httpPostJson('cgi-bin/user/batchdelete', ['useridlist' => $userIds]);
+    }
+
+    /**
+     * Get user.
+     *
+     * @param string $userId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function get(string $userId)
+    {
+        return $this->httpGet('cgi-bin/user/get', ['userid' => $userId]);
+    }
+
+    /**
+     * Get simple user list.
+     *
+     * @param int  $departmentId
+     * @param bool $fetchChild
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function getDepartmentUsers(int $departmentId, bool $fetchChild = false)
+    {
+        $params = [
+            'department_id' => $departmentId,
+            'fetch_child' => (int) $fetchChild,
+        ];
+
+        return $this->httpGet('cgi-bin/user/simplelist', $params);
+    }
+
+    /**
+     * Get user list.
+     *
+     * @param int  $departmentId
+     * @param bool $fetchChild
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function getDetailedDepartmentUsers(int $departmentId, bool $fetchChild = false)
+    {
+        $params = [
+            'department_id' => $departmentId,
+            'fetch_child' => (int) $fetchChild,
+        ];
+
+        return $this->httpGet('cgi-bin/user/list', $params);
+    }
+
+    /**
+     * Convert userId to openid.
+     *
+     * @param string   $userId
+     * @param int|null $agentId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function userIdToOpenid(string $userId, int $agentId = null)
+    {
+        $params = [
+            'userid' => $userId,
+            'agentid' => $agentId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/user/convert_to_openid', $params);
+    }
+
+    /**
+     * Convert openid to userId.
+     *
+     * @param string $openid
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function openidToUserId(string $openid)
+    {
+        $params = [
+            'openid' => $openid,
+        ];
+
+        return $this->httpPostJson('cgi-bin/user/convert_to_userid', $params);
+    }
+
+    /**
+     * Convert mobile to userId.
+     *
+     * @param string $mobile
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function mobileToUserId(string $mobile)
+    {
+        $params = [
+            'mobile' => $mobile,
+        ];
+
+        return $this->httpPostJson('cgi-bin/user/getuserid', $params);
+    }
+
+    /**
+     * @param string $userId
+     *
+     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function accept(string $userId)
+    {
+        $params = [
+            'userid' => $userId,
+        ];
+
+        return $this->httpGet('cgi-bin/user/authsucc', $params);
+    }
+
+    /**
+     * Batch invite users.
+     *
+     * @param array $params
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function invite(array $params)
+    {
+        return $this->httpPostJson('cgi-bin/batch/invite', $params);
+    }
+
+    /**
+     * Get invitation QR code.
+     *
+     * @param int $sizeType
+     *
+     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+     *
+     * @throws InvalidArgumentException
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function getInvitationQrCode(int $sizeType = 1)
+    {
+        if (!\in_array($sizeType, [1, 2, 3, 4], true)) {
+            throw new InvalidArgumentException('The sizeType must be 1, 2, 3, 4.');
+        }
+
+        return $this->httpGet('cgi-bin/corp/get_join_qrcode', ['size_type' => $sizeType]);
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/User/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/User/ServiceProvider.php
new file mode 100644
index 0000000..fcb5f10
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/User/ServiceProvider.php
@@ -0,0 +1,37 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\User;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+    /**
+     * {@inheritdoc}.
+     */
+    public function register(Container $app)
+    {
+        $app['user'] = function ($app) {
+            return new Client($app);
+        };
+
+        $app['tag'] = function ($app) {
+            return new TagClient($app);
+        };
+    }
+}
diff --git a/vendor/overtrue/wechat/src/Work/User/TagClient.php b/vendor/overtrue/wechat/src/Work/User/TagClient.php
new file mode 100644
index 0000000..aae7cf1
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Work/User/TagClient.php
@@ -0,0 +1,178 @@
+<?php
+
+/*
+ * This file is part of the overtrue/wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Work\User;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class TagClient.
+ *
+ * @author mingyoung <mingyoungcheung@gmail.com>
+ */
+class TagClient extends BaseClient
+{
+    /**
+     * Create tag.
+     *
+     * @param string   $tagName
+     * @param int|null $tagId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function create(string $tagName, int $tagId = null)
+    {
+        $params = [
+            'tagname' => $tagName,
+            'tagid' => $tagId,
+        ];
+
+        return $this->httpPostJson('cgi-bin/tag/create', $params);
+    }
+
+    /**
+     * Update tag.
+     *
+     * @param int    $tagId
+     * @param string $tagName
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function update(int $tagId, string $tagName)
+    {
+        $params = [
+            'tagid' => $tagId,
+            'tagname' => $tagName,
+        ];
+
+        return $this->httpPostJson('cgi-bin/tag/update', $params);
+    }
+
+    /**
+     * Delete tag.
+     *
+     * @param int $tagId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function delete(int $tagId)
+    {
+        return $this->httpGet('cgi-bin/tag/delete', ['tagid' => $tagId]);
+    }
+
+    /**
+     * @param int $tagId
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function get(int $tagId)
+    {
+        return $this->httpGet('cgi-bin/tag/get', ['tagid' => $tagId]);
+    }
+
+    /**
+     * @param int   $tagId
+     * @param array $userList
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function tagUsers(int $tagId, array $userList = [])
+    {
+        return $this->tagOrUntagUsers('cgi-bin/tag/addtagusers', $tagId, $userList);
+    }
+
+    /**
+     * @param int   $tagId
+     * @param array $partyList
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function tagDepartments(int $tagId, array $partyList = [])
+    {
+        return $this->tagOrUntagUsers('cgi-bin/tag/addtagusers', $tagId, [], $partyList);
+    }
+
+    /**
+     * @param int   $tagId
+     * @param array $userList
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function untagUsers(int $tagId, array $userList = [])
+    {
+        return $this->tagOrUntagUsers('cgi-bin/tag/deltagusers', $tagId, $userList);
+    }
+
+    /**
+     * @param int   $tagId
+     * @param array $partyList
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    public function untagDepartments(int $tagId, array $partyList = [])
+    {
+        return $this->tagOrUntagUsers('cgi-bin/tag/deltagusers', $tagId, [], $partyList);
+    }
+
+    /**
+     * @param string $endpoint
+     * @param int    $tagId
+     * @param array  $userList
+     * @param array  $partyList
+     *
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     * @throws \GuzzleHttp\Exception\GuzzleException
+     */
+    protected function tagOrUntagUsers(string $endpoint, int $tagId, array $userList = [], array $partyList = [])
+    {
+        $data = [
+            'tagid' => $tagId,
+            'userlist' => $userList,
+            'partylist' => $partyList,
+        ];
+
+        return $this->httpPostJson($endpoint, $data);
+    }
+
+    /**
+     * @return mixed
+     *
+     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+     */
+    public function list()
+    {
+        return $this->httpGet('cgi-bin/tag/list');
+    }
+}
diff --git a/vendor/pimple/pimple/.gitignore b/vendor/pimple/pimple/.gitignore
new file mode 100644
index 0000000..c089b09
--- /dev/null
+++ b/vendor/pimple/pimple/.gitignore
@@ -0,0 +1,3 @@
+phpunit.xml
+composer.lock
+/vendor/
diff --git a/vendor/pimple/pimple/ext/pimple/.gitignore b/vendor/pimple/pimple/ext/pimple/.gitignore
new file mode 100644
index 0000000..1861088
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/.gitignore
@@ -0,0 +1,30 @@
+*.sw*
+.deps
+Makefile
+Makefile.fragments
+Makefile.global
+Makefile.objects
+acinclude.m4
+aclocal.m4
+build/
+config.cache
+config.guess
+config.h
+config.h.in
+config.log
+config.nice
+config.status
+config.sub
+configure
+configure.in
+install-sh
+libtool
+ltmain.sh
+missing
+mkinstalldirs
+run-tests.php
+*.loT
+.libs/
+modules/
+*.la
+*.lo
diff --git a/vendor/psr/cache/CHANGELOG.md b/vendor/psr/cache/CHANGELOG.md
new file mode 100644
index 0000000..58ddab0
--- /dev/null
+++ b/vendor/psr/cache/CHANGELOG.md
@@ -0,0 +1,16 @@
+# Changelog
+
+All notable changes to this project will be documented in this file, in reverse chronological order by release.
+
+## 1.0.1 - 2016-08-06
+
+### Fixed
+
+- Make spacing consistent in phpdoc annotations php-fig/cache#9 - chalasr
+- Fix grammar in phpdoc annotations php-fig/cache#10 - chalasr
+- Be more specific in docblocks that `getItems()` and `deleteItems()` take an array of strings (`string[]`) compared to just `array` php-fig/cache#8 - GrahamCampbell
+- For `expiresAt()` and `expiresAfter()` in CacheItemInterface fix docblock to specify null as a valid parameters as well as an implementation of DateTimeInterface php-fig/cache#7 - GrahamCampbell
+
+## 1.0.0 - 2015-12-11
+
+Initial stable release; reflects accepted PSR-6 specification
diff --git a/vendor/psr/cache/LICENSE.txt b/vendor/psr/cache/LICENSE.txt
new file mode 100644
index 0000000..b1c2c97
--- /dev/null
+++ b/vendor/psr/cache/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2015 PHP Framework Interoperability Group
+
+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.
diff --git a/vendor/psr/cache/README.md b/vendor/psr/cache/README.md
new file mode 100644
index 0000000..c8706ce
--- /dev/null
+++ b/vendor/psr/cache/README.md
@@ -0,0 +1,9 @@
+PSR Cache
+=========
+
+This repository holds all interfaces defined by
+[PSR-6](http://www.php-fig.org/psr/psr-6/).
+
+Note that this is not a Cache implementation of its own. It is merely an
+interface that describes a Cache implementation. See the specification for more 
+details.
diff --git a/vendor/psr/cache/composer.json b/vendor/psr/cache/composer.json
new file mode 100644
index 0000000..e828fec
--- /dev/null
+++ b/vendor/psr/cache/composer.json
@@ -0,0 +1,25 @@
+{
+    "name": "psr/cache",
+    "description": "Common interface for caching libraries",
+    "keywords": ["psr", "psr-6", "cache"],
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "PHP-FIG",
+            "homepage": "http://www.php-fig.org/"
+        }
+    ],
+    "require": {
+        "php": ">=5.3.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Psr\\Cache\\": "src/"
+        }
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.0.x-dev"
+        }
+    }
+}
diff --git a/vendor/psr/cache/src/CacheException.php b/vendor/psr/cache/src/CacheException.php
new file mode 100644
index 0000000..e27f22f
--- /dev/null
+++ b/vendor/psr/cache/src/CacheException.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Psr\Cache;
+
+/**
+ * Exception interface for all exceptions thrown by an Implementing Library.
+ */
+interface CacheException
+{
+}
diff --git a/vendor/psr/cache/src/CacheItemInterface.php b/vendor/psr/cache/src/CacheItemInterface.php
new file mode 100644
index 0000000..63d05dd
--- /dev/null
+++ b/vendor/psr/cache/src/CacheItemInterface.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Psr\Cache;
+
+/**
+ * CacheItemInterface defines an interface for interacting with objects inside a cache.
+ *
+ * Each Item object MUST be associated with a specific key, which can be set
+ * according to the implementing system and is typically passed by the
+ * Cache\CacheItemPoolInterface object.
+ *
+ * The Cache\CacheItemInterface object encapsulates the storage and retrieval of
+ * cache items. Each Cache\CacheItemInterface is generated by a
+ * Cache\CacheItemPoolInterface object, which is responsible for any required
+ * setup as well as associating the object with a unique Key.
+ * Cache\CacheItemInterface objects MUST be able to store and retrieve any type
+ * of PHP value defined in the Data section of the specification.
+ *
+ * Calling Libraries MUST NOT instantiate Item objects themselves. They may only
+ * be requested from a Pool object via the getItem() method.  Calling Libraries
+ * SHOULD NOT assume that an Item created by one Implementing Library is
+ * compatible with a Pool from another Implementing Library.
+ */
+interface CacheItemInterface
+{
+    /**
+     * Returns the key for the current cache item.
+     *
+     * The key is loaded by the Implementing Library, but should be available to
+     * the higher level callers when needed.
+     *
+     * @return string
+     *   The key string for this cache item.
+     */
+    public function getKey();
+
+    /**
+     * Retrieves the value of the item from the cache associated with this object's key.
+     *
+     * The value returned must be identical to the value originally stored by set().
+     *
+     * If isHit() returns false, this method MUST return null. Note that null
+     * is a legitimate cached value, so the isHit() method SHOULD be used to
+     * differentiate between "null value was found" and "no value was found."
+     *
+     * @return mixed
+     *   The value corresponding to this cache item's key, or null if not found.
+     */
+    public function get();
+
+    /**
+     * Confirms if the cache item lookup resulted in a cache hit.
+     *
+     * Note: This method MUST NOT have a race condition between calling isHit()
+     * and calling get().
+     *
+     * @return bool
+     *   True if the request resulted in a cache hit. False otherwise.
+     */
+    public function isHit();
+
+    /**
+     * Sets the value represented by this cache item.
+     *
+     * The $value argument may be any item that can be serialized by PHP,
+     * although the method of serialization is left up to the Implementing
+     * Library.
+     *
+     * @param mixed $value
+     *   The serializable value to be stored.
+     *
+     * @return static
+     *   The invoked object.
+     */
+    public function set($value);
+
+    /**
+     * Sets the expiration time for this cache item.
+     *
+     * @param \DateTimeInterface|null $expiration
+     *   The point in time after which the item MUST be considered expired.
+     *   If null is passed explicitly, a default value MAY be used. If none is set,
+     *   the value should be stored permanently or for as long as the
+     *   implementation allows.
+     *
+     * @return static
+     *   The called object.
+     */
+    public function expiresAt($expiration);
+
+    /**
+     * Sets the expiration time for this cache item.
+     *
+     * @param int|\DateInterval|null $time
+     *   The period of time from the present after which the item MUST be considered
+     *   expired. An integer parameter is understood to be the time in seconds until
+     *   expiration. If null is passed explicitly, a default value MAY be used.
+     *   If none is set, the value should be stored permanently or for as long as the
+     *   implementation allows.
+     *
+     * @return static
+     *   The called object.
+     */
+    public function expiresAfter($time);
+}
diff --git a/vendor/psr/cache/src/CacheItemPoolInterface.php b/vendor/psr/cache/src/CacheItemPoolInterface.php
new file mode 100644
index 0000000..0351419
--- /dev/null
+++ b/vendor/psr/cache/src/CacheItemPoolInterface.php
@@ -0,0 +1,138 @@
+<?php
+
+namespace Psr\Cache;
+
+/**
+ * CacheItemPoolInterface generates CacheItemInterface objects.
+ *
+ * The primary purpose of Cache\CacheItemPoolInterface is to accept a key from
+ * the Calling Library and return the associated Cache\CacheItemInterface object.
+ * It is also the primary point of interaction with the entire cache collection.
+ * All configuration and initialization of the Pool is left up to an
+ * Implementing Library.
+ */
+interface CacheItemPoolInterface
+{
+    /**
+     * Returns a Cache Item representing the specified key.
+     *
+     * This method must always return a CacheItemInterface object, even in case of
+     * a cache miss. It MUST NOT return null.
+     *
+     * @param string $key
+     *   The key for which to return the corresponding Cache Item.
+     *
+     * @throws InvalidArgumentException
+     *   If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
+     *   MUST be thrown.
+     *
+     * @return CacheItemInterface
+     *   The corresponding Cache Item.
+     */
+    public function getItem($key);
+
+    /**
+     * Returns a traversable set of cache items.
+     *
+     * @param string[] $keys
+     *   An indexed array of keys of items to retrieve.
+     *
+     * @throws InvalidArgumentException
+     *   If any of the keys in $keys are not a legal value a \Psr\Cache\InvalidArgumentException
+     *   MUST be thrown.
+     *
+     * @return array|\Traversable
+     *   A traversable collection of Cache Items keyed by the cache keys of
+     *   each item. A Cache item will be returned for each key, even if that
+     *   key is not found. However, if no keys are specified then an empty
+     *   traversable MUST be returned instead.
+     */
+    public function getItems(array $keys = array());
+
+    /**
+     * Confirms if the cache contains specified cache item.
+     *
+     * Note: This method MAY avoid retrieving the cached value for performance reasons.
+     * This could result in a race condition with CacheItemInterface::get(). To avoid
+     * such situation use CacheItemInterface::isHit() instead.
+     *
+     * @param string $key
+     *   The key for which to check existence.
+     *
+     * @throws InvalidArgumentException
+     *   If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
+     *   MUST be thrown.
+     *
+     * @return bool
+     *   True if item exists in the cache, false otherwise.
+     */
+    public function hasItem($key);
+
+    /**
+     * Deletes all items in the pool.
+     *
+     * @return bool
+     *   True if the pool was successfully cleared. False if there was an error.
+     */
+    public function clear();
+
+    /**
+     * Removes the item from the pool.
+     *
+     * @param string $key
+     *   The key to delete.
+     *
+     * @throws InvalidArgumentException
+     *   If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
+     *   MUST be thrown.
+     *
+     * @return bool
+     *   True if the item was successfully removed. False if there was an error.
+     */
+    public function deleteItem($key);
+
+    /**
+     * Removes multiple items from the pool.
+     *
+     * @param string[] $keys
+     *   An array of keys that should be removed from the pool.
+
+     * @throws InvalidArgumentException
+     *   If any of the keys in $keys are not a legal value a \Psr\Cache\InvalidArgumentException
+     *   MUST be thrown.
+     *
+     * @return bool
+     *   True if the items were successfully removed. False if there was an error.
+     */
+    public function deleteItems(array $keys);
+
+    /**
+     * Persists a cache item immediately.
+     *
+     * @param CacheItemInterface $item
+     *   The cache item to save.
+     *
+     * @return bool
+     *   True if the item was successfully persisted. False if there was an error.
+     */
+    public function save(CacheItemInterface $item);
+
+    /**
+     * Sets a cache item to be persisted later.
+     *
+     * @param CacheItemInterface $item
+     *   The cache item to save.
+     *
+     * @return bool
+     *   False if the item could not be queued or if a commit was attempted and failed. True otherwise.
+     */
+    public function saveDeferred(CacheItemInterface $item);
+
+    /**
+     * Persists any deferred cache items.
+     *
+     * @return bool
+     *   True if all not-yet-saved items were successfully saved or there were none. False otherwise.
+     */
+    public function commit();
+}
diff --git a/vendor/psr/cache/src/InvalidArgumentException.php b/vendor/psr/cache/src/InvalidArgumentException.php
new file mode 100644
index 0000000..be7c6fa
--- /dev/null
+++ b/vendor/psr/cache/src/InvalidArgumentException.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Psr\Cache;
+
+/**
+ * Exception interface for invalid cache arguments.
+ *
+ * Any time an invalid argument is passed into a method it must throw an
+ * exception class which implements Psr\Cache\InvalidArgumentException.
+ */
+interface InvalidArgumentException extends CacheException
+{
+}
diff --git a/vendor/psr/container/.gitignore b/vendor/psr/container/.gitignore
new file mode 100644
index 0000000..b2395aa
--- /dev/null
+++ b/vendor/psr/container/.gitignore
@@ -0,0 +1,3 @@
+composer.lock
+composer.phar
+/vendor/
diff --git a/vendor/psr/log/.gitignore b/vendor/psr/log/.gitignore
new file mode 100644
index 0000000..22d0d82
--- /dev/null
+++ b/vendor/psr/log/.gitignore
@@ -0,0 +1 @@
+vendor
diff --git a/vendor/symfony/cache-contracts/.gitignore b/vendor/symfony/cache-contracts/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/vendor/symfony/cache-contracts/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/vendor/symfony/cache-contracts/CacheInterface.php b/vendor/symfony/cache-contracts/CacheInterface.php
new file mode 100644
index 0000000..4b1686b
--- /dev/null
+++ b/vendor/symfony/cache-contracts/CacheInterface.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\Contracts\Cache;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\InvalidArgumentException;
+
+/**
+ * Covers most simple to advanced caching needs.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+interface CacheInterface
+{
+    /**
+     * Fetches a value from the pool or computes it if not found.
+     *
+     * On cache misses, a callback is called that should return the missing value.
+     * This callback is given a PSR-6 CacheItemInterface instance corresponding to the
+     * requested key, that could be used e.g. for expiration control. It could also
+     * be an ItemInterface instance when its additional features are needed.
+     *
+     * @param string                     $key       The key of the item to retrieve from the cache
+     * @param callable|CallbackInterface $callback  Should return the computed value for the given key/item
+     * @param float|null                 $beta      A float that, as it grows, controls the likeliness of triggering
+     *                                              early expiration. 0 disables it, INF forces immediate expiration.
+     *                                              The default (or providing null) is implementation dependent but should
+     *                                              typically be 1.0, which should provide optimal stampede protection.
+     *                                              See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration
+     * @param array                      &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()}
+     *
+     * @return mixed The value corresponding to the provided key
+     *
+     * @throws InvalidArgumentException When $key is not valid or when $beta is negative
+     */
+    public function get(string $key, callable $callback, float $beta = null, array &$metadata = null);
+
+    /**
+     * Removes an item from the pool.
+     *
+     * @param string $key The key to delete
+     *
+     * @throws InvalidArgumentException When $key is not valid
+     *
+     * @return bool True if the item was successfully removed, false if there was any error
+     */
+    public function delete(string $key): bool;
+}
diff --git a/vendor/symfony/cache-contracts/CacheTrait.php b/vendor/symfony/cache-contracts/CacheTrait.php
new file mode 100644
index 0000000..355ea29
--- /dev/null
+++ b/vendor/symfony/cache-contracts/CacheTrait.php
@@ -0,0 +1,76 @@
+<?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\Contracts\Cache;
+
+use Psr\Cache\CacheItemPoolInterface;
+use Psr\Cache\InvalidArgumentException;
+use Psr\Log\LoggerInterface;
+
+/**
+ * An implementation of CacheInterface for PSR-6 CacheItemPoolInterface classes.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+trait CacheTrait
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+    {
+        return $this->doGet($this, $key, $callback, $beta, $metadata);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function delete(string $key): bool
+    {
+        return $this->deleteItem($key);
+    }
+
+    private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null, LoggerInterface $logger = null)
+    {
+        if (0 > $beta = $beta ?? 1.0) {
+            throw new class(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', \get_class($this), $beta)) extends \InvalidArgumentException implements InvalidArgumentException {
+            };
+        }
+
+        $item = $pool->getItem($key);
+        $recompute = !$item->isHit() || INF === $beta;
+        $metadata = $item instanceof ItemInterface ? $item->getMetadata() : [];
+
+        if (!$recompute && $metadata) {
+            $expiry = $metadata[ItemInterface::METADATA_EXPIRY] ?? false;
+            $ctime = $metadata[ItemInterface::METADATA_CTIME] ?? false;
+
+            if ($recompute = $ctime && $expiry && $expiry <= ($now = microtime(true)) - $ctime / 1000 * $beta * log(random_int(1, PHP_INT_MAX) / PHP_INT_MAX)) {
+                // force applying defaultLifetime to expiry
+                $item->expiresAt(null);
+                $logger && $logger->info('Item "{key}" elected for early recomputation {delta}s before its expiration', [
+                    'key' => $key,
+                    'delta' => sprintf('%.1f', $expiry - $now),
+                ]);
+            }
+        }
+
+        if ($recompute) {
+            $save = true;
+            $item->set($callback($item, $save));
+            if ($save) {
+                $pool->save($item);
+            }
+        }
+
+        return $item->get();
+    }
+}
diff --git a/vendor/symfony/cache-contracts/CallbackInterface.php b/vendor/symfony/cache-contracts/CallbackInterface.php
new file mode 100644
index 0000000..7dae2aa
--- /dev/null
+++ b/vendor/symfony/cache-contracts/CallbackInterface.php
@@ -0,0 +1,30 @@
+<?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\Contracts\Cache;
+
+use Psr\Cache\CacheItemInterface;
+
+/**
+ * Computes and returns the cached value of an item.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+interface CallbackInterface
+{
+    /**
+     * @param CacheItemInterface|ItemInterface $item  The item to compute the value for
+     * @param bool                             &$save Should be set to false when the value should not be saved in the pool
+     *
+     * @return mixed The computed value for the passed item
+     */
+    public function __invoke(CacheItemInterface $item, bool &$save);
+}
diff --git a/vendor/symfony/cache-contracts/ItemInterface.php b/vendor/symfony/cache-contracts/ItemInterface.php
new file mode 100644
index 0000000..cbd7226
--- /dev/null
+++ b/vendor/symfony/cache-contracts/ItemInterface.php
@@ -0,0 +1,65 @@
+<?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\Contracts\Cache;
+
+use Psr\Cache\CacheException;
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\InvalidArgumentException;
+
+/**
+ * Augments PSR-6's CacheItemInterface with support for tags and metadata.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+interface ItemInterface extends CacheItemInterface
+{
+    /**
+     * References the Unix timestamp stating when the item will expire.
+     */
+    const METADATA_EXPIRY = 'expiry';
+
+    /**
+     * References the time the item took to be created, in milliseconds.
+     */
+    const METADATA_CTIME = 'ctime';
+
+    /**
+     * References the list of tags that were assigned to the item, as string[].
+     */
+    const METADATA_TAGS = 'tags';
+
+    /**
+     * Reserved characters that cannot be used in a key or tag.
+     */
+    const RESERVED_CHARACTERS = '{}()/\@:';
+
+    /**
+     * Adds a tag to a cache item.
+     *
+     * Tags are strings that follow the same validation rules as keys.
+     *
+     * @param string|string[] $tags A tag or array of tags
+     *
+     * @return $this
+     *
+     * @throws InvalidArgumentException When $tag is not valid
+     * @throws CacheException           When the item comes from a pool that is not tag-aware
+     */
+    public function tag($tags): self;
+
+    /**
+     * Returns a list of metadata info that were saved alongside with the cached value.
+     *
+     * See ItemInterface::METADATA_* consts for keys potentially found in the returned array.
+     */
+    public function getMetadata(): array;
+}
diff --git a/vendor/symfony/cache-contracts/LICENSE b/vendor/symfony/cache-contracts/LICENSE
new file mode 100644
index 0000000..3f853aa
--- /dev/null
+++ b/vendor/symfony/cache-contracts/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018-2019 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.
diff --git a/vendor/symfony/cache-contracts/README.md b/vendor/symfony/cache-contracts/README.md
new file mode 100644
index 0000000..58c589e
--- /dev/null
+++ b/vendor/symfony/cache-contracts/README.md
@@ -0,0 +1,9 @@
+Symfony Cache Contracts
+=======================
+
+A set of abstractions extracted out of the Symfony components.
+
+Can be used to build on semantics that the Symfony components proved useful - and
+that already have battle tested implementations.
+
+See https://github.com/symfony/contracts/blob/master/README.md for more information.
diff --git a/vendor/symfony/cache-contracts/TagAwareCacheInterface.php b/vendor/symfony/cache-contracts/TagAwareCacheInterface.php
new file mode 100644
index 0000000..7c4cf11
--- /dev/null
+++ b/vendor/symfony/cache-contracts/TagAwareCacheInterface.php
@@ -0,0 +1,38 @@
+<?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\Contracts\Cache;
+
+use Psr\Cache\InvalidArgumentException;
+
+/**
+ * Allows invalidating cached items using tags.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+interface TagAwareCacheInterface extends CacheInterface
+{
+    /**
+     * Invalidates cached items using tags.
+     *
+     * When implemented on a PSR-6 pool, invalidation should not apply
+     * to deferred items. Instead, they should be committed as usual.
+     * This allows replacing old tagged values by new ones without
+     * race conditions.
+     *
+     * @param string[] $tags An array of tags to invalidate
+     *
+     * @return bool True on success
+     *
+     * @throws InvalidArgumentException When $tags is not valid
+     */
+    public function invalidateTags(array $tags);
+}
diff --git a/vendor/symfony/cache-contracts/composer.json b/vendor/symfony/cache-contracts/composer.json
new file mode 100644
index 0000000..4e0bd1a
--- /dev/null
+++ b/vendor/symfony/cache-contracts/composer.json
@@ -0,0 +1,34 @@
+{
+    "name": "symfony/cache-contracts",
+    "type": "library",
+    "description": "Generic abstractions related to caching",
+    "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"],
+    "homepage": "https://symfony.com",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Nicolas Grekas",
+            "email": "p@tchwork.com"
+        },
+        {
+            "name": "Symfony Community",
+            "homepage": "https://symfony.com/contributors"
+        }
+    ],
+    "require": {
+        "php": "^7.1.3",
+        "psr/cache": "^1.0"
+    },
+    "suggest": {
+        "symfony/cache-implementation": ""
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Contracts\\Cache\\": "" }
+    },
+    "minimum-stability": "dev",
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.1-dev"
+        }
+    }
+}
diff --git a/vendor/symfony/cache/.gitignore b/vendor/symfony/cache/.gitignore
new file mode 100644
index 0000000..5414c2c
--- /dev/null
+++ b/vendor/symfony/cache/.gitignore
@@ -0,0 +1,3 @@
+composer.lock
+phpunit.xml
+vendor/
diff --git a/vendor/symfony/cache/Adapter/AbstractAdapter.php b/vendor/symfony/cache/Adapter/AbstractAdapter.php
new file mode 100644
index 0000000..46c3576
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/AbstractAdapter.php
@@ -0,0 +1,201 @@
+<?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\Cache\Adapter;
+
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
+{
+    /**
+     * @internal
+     */
+    protected const NS_SEPARATOR = ':';
+
+    use AbstractAdapterTrait;
+    use ContractsTrait;
+
+    private static $apcuSupported;
+    private static $phpFilesSupported;
+
+    protected function __construct(string $namespace = '', int $defaultLifetime = 0)
+    {
+        $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::NS_SEPARATOR;
+        if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
+            throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
+        }
+        $this->createCacheItem = \Closure::bind(
+            static function ($key, $value, $isHit) use ($defaultLifetime) {
+                $item = new CacheItem();
+                $item->key = $key;
+                $item->value = $v = $value;
+                $item->isHit = $isHit;
+                $item->defaultLifetime = $defaultLifetime;
+                // Detect wrapped values that encode for their expiry and creation duration
+                // For compactness, these values are packed in the key of an array using
+                // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
+                if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
+                    $item->value = $v[$k];
+                    $v = unpack('Ve/Nc', substr($k, 1, -1));
+                    $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
+                    $item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
+                }
+
+                return $item;
+            },
+            null,
+            CacheItem::class
+        );
+        $getId = \Closure::fromCallable([$this, 'getId']);
+        $this->mergeByLifetime = \Closure::bind(
+            static function ($deferred, $namespace, &$expiredIds) use ($getId) {
+                $byLifetime = [];
+                $now = microtime(true);
+                $expiredIds = [];
+
+                foreach ($deferred as $key => $item) {
+                    $key = (string) $key;
+                    if (null === $item->expiry) {
+                        $ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0;
+                    } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
+                        $expiredIds[] = $getId($key);
+                        continue;
+                    }
+                    if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
+                        unset($metadata[CacheItem::METADATA_TAGS]);
+                    }
+                    // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
+                    $byLifetime[$ttl][$getId($key)] = $metadata ? ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item->value] : $item->value;
+                }
+
+                return $byLifetime;
+            },
+            null,
+            CacheItem::class
+        );
+    }
+
+    /**
+     * Returns the best possible adapter that your runtime supports.
+     *
+     * Using ApcuAdapter makes system caches compatible with read-only filesystems.
+     *
+     * @param string $namespace
+     * @param int    $defaultLifetime
+     * @param string $version
+     * @param string $directory
+     *
+     * @return AdapterInterface
+     */
+    public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null)
+    {
+        $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true);
+        if (null !== $logger) {
+            $opcache->setLogger($logger);
+        }
+
+        if (!self::$apcuSupported = self::$apcuSupported ?? ApcuAdapter::isSupported()) {
+            return $opcache;
+        }
+
+        $apcu = new ApcuAdapter($namespace, (int) $defaultLifetime / 5, $version);
+        if ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) {
+            $apcu->setLogger(new NullLogger());
+        } elseif (null !== $logger) {
+            $apcu->setLogger($logger);
+        }
+
+        return new ChainAdapter([$apcu, $opcache]);
+    }
+
+    public static function createConnection($dsn, array $options = [])
+    {
+        if (!\is_string($dsn)) {
+            throw new InvalidArgumentException(sprintf('The %s() method expect argument #1 to be string, %s given.', __METHOD__, \gettype($dsn)));
+        }
+        if (0 === strpos($dsn, 'redis:') || 0 === strpos($dsn, 'rediss:')) {
+            return RedisAdapter::createConnection($dsn, $options);
+        }
+        if (0 === strpos($dsn, 'memcached:')) {
+            return MemcachedAdapter::createConnection($dsn, $options);
+        }
+
+        throw new InvalidArgumentException(sprintf('Unsupported DSN: %s.', $dsn));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function commit()
+    {
+        $ok = true;
+        $byLifetime = $this->mergeByLifetime;
+        $byLifetime = $byLifetime($this->deferred, $this->namespace, $expiredIds);
+        $retry = $this->deferred = [];
+
+        if ($expiredIds) {
+            $this->doDelete($expiredIds);
+        }
+        foreach ($byLifetime as $lifetime => $values) {
+            try {
+                $e = $this->doSave($values, $lifetime);
+            } catch (\Exception $e) {
+            }
+            if (true === $e || [] === $e) {
+                continue;
+            }
+            if (\is_array($e) || 1 === \count($values)) {
+                foreach (\is_array($e) ? $e : array_keys($values) as $id) {
+                    $ok = false;
+                    $v = $values[$id];
+                    $type = \is_object($v) ? \get_class($v) : \gettype($v);
+                    $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
+                    CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
+                }
+            } else {
+                foreach ($values as $id => $v) {
+                    $retry[$lifetime][] = $id;
+                }
+            }
+        }
+
+        // When bulk-save failed, retry each item individually
+        foreach ($retry as $lifetime => $ids) {
+            foreach ($ids as $id) {
+                try {
+                    $v = $byLifetime[$lifetime][$id];
+                    $e = $this->doSave([$id => $v], $lifetime);
+                } catch (\Exception $e) {
+                }
+                if (true === $e || [] === $e) {
+                    continue;
+                }
+                $ok = false;
+                $type = \is_object($v) ? \get_class($v) : \gettype($v);
+                $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
+                CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
+            }
+        }
+
+        return $ok;
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php b/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php
new file mode 100644
index 0000000..13a4968
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php
@@ -0,0 +1,309 @@
+<?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\Cache\Adapter;
+
+use Psr\Log\LoggerAwareInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Contracts\Cache\TagAwareCacheInterface;
+
+/**
+ * Abstract for native TagAware adapters.
+ *
+ * To keep info on tags, the tags are both serialized as part of cache value and provided as tag ids
+ * to Adapters on operations when needed for storage to doSave(), doDelete() & doInvalidate().
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ * @author André Rømcke <andre.romcke+symfony@gmail.com>
+ *
+ * @internal
+ * @experimental in 4.3
+ */
+abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, LoggerAwareInterface, ResettableInterface
+{
+    use AbstractAdapterTrait;
+    use ContractsTrait;
+
+    private const TAGS_PREFIX = "\0tags\0";
+
+    protected function __construct(string $namespace = '', int $defaultLifetime = 0)
+    {
+        $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
+        if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
+            throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
+        }
+        $this->createCacheItem = \Closure::bind(
+            static function ($key, $value, $isHit) use ($defaultLifetime) {
+                $item = new CacheItem();
+                $item->key = $key;
+                $item->defaultLifetime = $defaultLifetime;
+                $item->isTaggable = true;
+                // If structure does not match what we expect return item as is (no value and not a hit)
+                if (!\is_array($value) || !\array_key_exists('value', $value)) {
+                    return $item;
+                }
+                $item->isHit = $isHit;
+                // Extract value, tags and meta data from the cache value
+                $item->value = $value['value'];
+                $item->metadata[CacheItem::METADATA_TAGS] = $value['tags'] ?? [];
+                if (isset($value['meta'])) {
+                    // For compactness these values are packed, & expiry is offset to reduce size
+                    $v = unpack('Ve/Nc', $value['meta']);
+                    $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
+                    $item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
+                }
+
+                return $item;
+            },
+            null,
+            CacheItem::class
+        );
+        $getId = \Closure::fromCallable([$this, 'getId']);
+        $tagPrefix = self::TAGS_PREFIX;
+        $this->mergeByLifetime = \Closure::bind(
+            static function ($deferred, &$expiredIds) use ($getId, $tagPrefix) {
+                $byLifetime = [];
+                $now = microtime(true);
+                $expiredIds = [];
+
+                foreach ($deferred as $key => $item) {
+                    $key = (string) $key;
+                    if (null === $item->expiry) {
+                        $ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0;
+                    } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
+                        $expiredIds[] = $getId($key);
+                        continue;
+                    }
+                    // Store Value and Tags on the cache value
+                    if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
+                        $value = ['value' => $item->value, 'tags' => $metadata[CacheItem::METADATA_TAGS]];
+                        unset($metadata[CacheItem::METADATA_TAGS]);
+                    } else {
+                        $value = ['value' => $item->value, 'tags' => []];
+                    }
+
+                    if ($metadata) {
+                        // For compactness, expiry and creation duration are packed, using magic numbers as separators
+                        $value['meta'] = pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME]);
+                    }
+
+                    // Extract tag changes, these should be removed from values in doSave()
+                    $value['tag-operations'] = ['add' => [], 'remove' => []];
+                    $oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? [];
+                    foreach (array_diff($value['tags'], $oldTags) as $addedTag) {
+                        $value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag);
+                    }
+                    foreach (array_diff($oldTags, $value['tags']) as $removedTag) {
+                        $value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag);
+                    }
+
+                    $byLifetime[$ttl][$getId($key)] = $value;
+                }
+
+                return $byLifetime;
+            },
+            null,
+            CacheItem::class
+        );
+    }
+
+    /**
+     * Persists several cache items immediately.
+     *
+     * @param array   $values        The values to cache, indexed by their cache identifier
+     * @param int     $lifetime      The lifetime of the cached values, 0 for persisting until manual cleaning
+     * @param array[] $addTagData    Hash where key is tag id, and array value is list of cache id's to add to tag
+     * @param array[] $removeTagData Hash where key is tag id, and array value is list of cache id's to remove to tag
+     *
+     * @return array The identifiers that failed to be cached or a boolean stating if caching succeeded or not
+     */
+    abstract protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array;
+
+    /**
+     * Removes multiple items from the pool and their corresponding tags.
+     *
+     * @param array $ids     An array of identifiers that should be removed from the pool
+     * @param array $tagData Optional array of tag identifiers => key identifiers that should be removed from the pool
+     *
+     * @return bool True if the items were successfully removed, false otherwise
+     */
+    abstract protected function doDelete(array $ids, array $tagData = []): bool;
+
+    /**
+     * Invalidates cached items using tags.
+     *
+     * @param string[] $tagIds An array of tags to invalidate, key is tag and value is tag id
+     *
+     * @return bool True on success
+     */
+    abstract protected function doInvalidate(array $tagIds): bool;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function commit()
+    {
+        $ok = true;
+        $byLifetime = $this->mergeByLifetime;
+        $byLifetime = $byLifetime($this->deferred, $expiredIds);
+        $retry = $this->deferred = [];
+
+        if ($expiredIds) {
+            // Tags are not cleaned up in this case, however that is done on invalidateTags().
+            $this->doDelete($expiredIds);
+        }
+        foreach ($byLifetime as $lifetime => $values) {
+            try {
+                $values = $this->extractTagData($values, $addTagData, $removeTagData);
+                $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
+            } catch (\Exception $e) {
+            }
+            if (true === $e || [] === $e) {
+                continue;
+            }
+            if (\is_array($e) || 1 === \count($values)) {
+                foreach (\is_array($e) ? $e : array_keys($values) as $id) {
+                    $ok = false;
+                    $v = $values[$id];
+                    $type = \is_object($v) ? \get_class($v) : \gettype($v);
+                    $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
+                    CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
+                }
+            } else {
+                foreach ($values as $id => $v) {
+                    $retry[$lifetime][] = $id;
+                }
+            }
+        }
+
+        // When bulk-save failed, retry each item individually
+        foreach ($retry as $lifetime => $ids) {
+            foreach ($ids as $id) {
+                try {
+                    $v = $byLifetime[$lifetime][$id];
+                    $values = $this->extractTagData([$id => $v], $addTagData, $removeTagData);
+                    $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
+                } catch (\Exception $e) {
+                }
+                if (true === $e || [] === $e) {
+                    continue;
+                }
+                $ok = false;
+                $type = \is_object($v) ? \get_class($v) : \gettype($v);
+                $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
+                CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
+            }
+        }
+
+        return $ok;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * Overloaded in order to deal with tags for adjusted doDelete() signature.
+     */
+    public function deleteItems(array $keys)
+    {
+        if (!$keys) {
+            return true;
+        }
+
+        $ids = [];
+        $tagData = [];
+
+        foreach ($keys as $key) {
+            $ids[$key] = $this->getId($key);
+            unset($this->deferred[$key]);
+        }
+
+        try {
+            foreach ($this->doFetch($ids) as $id => $value) {
+                foreach ($value['tags'] ?? [] as $tag) {
+                    $tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id;
+                }
+            }
+        } catch (\Exception $e) {
+            // ignore unserialization failures
+        }
+
+        try {
+            if ($this->doDelete(array_values($ids), $tagData)) {
+                return true;
+            }
+        } catch (\Exception $e) {
+        }
+
+        $ok = true;
+
+        // When bulk-delete failed, retry each item individually
+        foreach ($ids as $key => $id) {
+            try {
+                $e = null;
+                if ($this->doDelete([$id])) {
+                    continue;
+                }
+            } catch (\Exception $e) {
+            }
+            $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
+            CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]);
+            $ok = false;
+        }
+
+        return $ok;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function invalidateTags(array $tags)
+    {
+        if (empty($tags)) {
+            return false;
+        }
+
+        $tagIds = [];
+        foreach (array_unique($tags) as $tag) {
+            $tagIds[] = $this->getId(self::TAGS_PREFIX.$tag);
+        }
+
+        if ($this->doInvalidate($tagIds)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Extracts tags operation data from $values set in mergeByLifetime, and returns values without it.
+     */
+    private function extractTagData(array $values, ?array &$addTagData, ?array &$removeTagData): array
+    {
+        $addTagData = $removeTagData = [];
+        foreach ($values as $id => $value) {
+            foreach ($value['tag-operations']['add'] as $tag => $tagId) {
+                $addTagData[$tagId][] = $id;
+            }
+
+            foreach ($value['tag-operations']['remove'] as $tag => $tagId) {
+                $removeTagData[$tagId][] = $id;
+            }
+
+            unset($values[$id]['tag-operations']);
+        }
+
+        return $values;
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/AdapterInterface.php b/vendor/symfony/cache/Adapter/AdapterInterface.php
new file mode 100644
index 0000000..85fe076
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/AdapterInterface.php
@@ -0,0 +1,37 @@
+<?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\Cache\Adapter;
+
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\CacheItem;
+
+/**
+ * Interface for adapters managing instances of Symfony's CacheItem.
+ *
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ */
+interface AdapterInterface extends CacheItemPoolInterface
+{
+    /**
+     * {@inheritdoc}
+     *
+     * @return CacheItem
+     */
+    public function getItem($key);
+
+    /**
+     * {@inheritdoc}
+     *
+     * @return \Traversable|CacheItem[]
+     */
+    public function getItems(array $keys = []);
+}
diff --git a/vendor/symfony/cache/Adapter/ApcuAdapter.php b/vendor/symfony/cache/Adapter/ApcuAdapter.php
new file mode 100644
index 0000000..7db3956
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/ApcuAdapter.php
@@ -0,0 +1,27 @@
+<?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\Cache\Adapter;
+
+use Symfony\Component\Cache\Traits\ApcuTrait;
+
+class ApcuAdapter extends AbstractAdapter
+{
+    use ApcuTrait;
+
+    /**
+     * @throws CacheException if APCu is not enabled
+     */
+    public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null)
+    {
+        $this->init($namespace, $defaultLifetime, $version);
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/ArrayAdapter.php b/vendor/symfony/cache/Adapter/ArrayAdapter.php
new file mode 100644
index 0000000..43b56dc
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/ArrayAdapter.php
@@ -0,0 +1,163 @@
+<?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\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Log\LoggerAwareInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ArrayTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
+{
+    use ArrayTrait;
+
+    private $createCacheItem;
+
+    /**
+     * @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
+     */
+    public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true)
+    {
+        $this->storeSerialized = $storeSerialized;
+        $this->createCacheItem = \Closure::bind(
+            static function ($key, $value, $isHit) use ($defaultLifetime) {
+                $item = new CacheItem();
+                $item->key = $key;
+                $item->value = $value;
+                $item->isHit = $isHit;
+                $item->defaultLifetime = $defaultLifetime;
+
+                return $item;
+            },
+            null,
+            CacheItem::class
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+    {
+        $item = $this->getItem($key);
+        $metadata = $item->getMetadata();
+
+        // ArrayAdapter works in memory, we don't care about stampede protection
+        if (INF === $beta || !$item->isHit()) {
+            $save = true;
+            $this->save($item->set($callback($item, $save)));
+        }
+
+        return $item->get();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItem($key)
+    {
+        if (!$isHit = $this->hasItem($key)) {
+            $this->values[$key] = $value = null;
+        } else {
+            $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
+        }
+        $f = $this->createCacheItem;
+
+        return $f($key, $value, $isHit);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItems(array $keys = [])
+    {
+        foreach ($keys as $key) {
+            if (!\is_string($key) || !isset($this->expiries[$key])) {
+                CacheItem::validateKey($key);
+            }
+        }
+
+        return $this->generateItems($keys, microtime(true), $this->createCacheItem);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItems(array $keys)
+    {
+        foreach ($keys as $key) {
+            $this->deleteItem($key);
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save(CacheItemInterface $item)
+    {
+        if (!$item instanceof CacheItem) {
+            return false;
+        }
+        $item = (array) $item;
+        $key = $item["\0*\0key"];
+        $value = $item["\0*\0value"];
+        $expiry = $item["\0*\0expiry"];
+
+        if (null !== $expiry && $expiry <= microtime(true)) {
+            $this->deleteItem($key);
+
+            return true;
+        }
+        if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
+            return false;
+        }
+        if (null === $expiry && 0 < $item["\0*\0defaultLifetime"]) {
+            $expiry = microtime(true) + $item["\0*\0defaultLifetime"];
+        }
+
+        $this->values[$key] = $value;
+        $this->expiries[$key] = null !== $expiry ? $expiry : PHP_INT_MAX;
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function saveDeferred(CacheItemInterface $item)
+    {
+        return $this->save($item);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function commit()
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function delete(string $key): bool
+    {
+        return $this->deleteItem($key);
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/ChainAdapter.php b/vendor/symfony/cache/Adapter/ChainAdapter.php
new file mode 100644
index 0000000..0217c80
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/ChainAdapter.php
@@ -0,0 +1,310 @@
+<?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\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * Chains several adapters together.
+ *
+ * Cached items are fetched from the first adapter having them in its data store.
+ * They are saved and deleted in all adapters at once.
+ *
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ */
+class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
+{
+    use ContractsTrait;
+
+    private $adapters = [];
+    private $adapterCount;
+    private $syncItem;
+
+    /**
+     * @param CacheItemPoolInterface[] $adapters        The ordered list of adapters used to fetch cached items
+     * @param int                      $defaultLifetime The default lifetime of items propagated from lower adapters to upper ones
+     */
+    public function __construct(array $adapters, int $defaultLifetime = 0)
+    {
+        if (!$adapters) {
+            throw new InvalidArgumentException('At least one adapter must be specified.');
+        }
+
+        foreach ($adapters as $adapter) {
+            if (!$adapter instanceof CacheItemPoolInterface) {
+                throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($adapter), CacheItemPoolInterface::class));
+            }
+
+            if ($adapter instanceof AdapterInterface) {
+                $this->adapters[] = $adapter;
+            } else {
+                $this->adapters[] = new ProxyAdapter($adapter);
+            }
+        }
+        $this->adapterCount = \count($this->adapters);
+
+        $this->syncItem = \Closure::bind(
+            static function ($sourceItem, $item) use ($defaultLifetime) {
+                $item->value = $sourceItem->value;
+                $item->expiry = $sourceItem->expiry;
+                $item->isHit = $sourceItem->isHit;
+                $item->metadata = $sourceItem->metadata;
+
+                $sourceItem->isTaggable = false;
+                unset($sourceItem->metadata[CacheItem::METADATA_TAGS]);
+
+                if (0 < $sourceItem->defaultLifetime && $sourceItem->defaultLifetime < $defaultLifetime) {
+                    $defaultLifetime = $sourceItem->defaultLifetime;
+                }
+                if (0 < $defaultLifetime && ($item->defaultLifetime <= 0 || $defaultLifetime < $item->defaultLifetime)) {
+                    $item->defaultLifetime = $defaultLifetime;
+                }
+
+                return $item;
+            },
+            null,
+            CacheItem::class
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+    {
+        $lastItem = null;
+        $i = 0;
+        $wrap = function (CacheItem $item = null) use ($key, $callback, $beta, &$wrap, &$i, &$lastItem, &$metadata) {
+            $adapter = $this->adapters[$i];
+            if (isset($this->adapters[++$i])) {
+                $callback = $wrap;
+                $beta = INF === $beta ? INF : 0;
+            }
+            if ($adapter instanceof CacheInterface) {
+                $value = $adapter->get($key, $callback, $beta, $metadata);
+            } else {
+                $value = $this->doGet($adapter, $key, $callback, $beta, $metadata);
+            }
+            if (null !== $item) {
+                ($this->syncItem)($lastItem = $lastItem ?? $item, $item);
+            }
+
+            return $value;
+        };
+
+        return $wrap();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItem($key)
+    {
+        $syncItem = $this->syncItem;
+        $misses = [];
+
+        foreach ($this->adapters as $i => $adapter) {
+            $item = $adapter->getItem($key);
+
+            if ($item->isHit()) {
+                while (0 <= --$i) {
+                    $this->adapters[$i]->save($syncItem($item, $misses[$i]));
+                }
+
+                return $item;
+            }
+
+            $misses[$i] = $item;
+        }
+
+        return $item;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItems(array $keys = [])
+    {
+        return $this->generateItems($this->adapters[0]->getItems($keys), 0);
+    }
+
+    private function generateItems($items, $adapterIndex)
+    {
+        $missing = [];
+        $misses = [];
+        $nextAdapterIndex = $adapterIndex + 1;
+        $nextAdapter = isset($this->adapters[$nextAdapterIndex]) ? $this->adapters[$nextAdapterIndex] : null;
+
+        foreach ($items as $k => $item) {
+            if (!$nextAdapter || $item->isHit()) {
+                yield $k => $item;
+            } else {
+                $missing[] = $k;
+                $misses[$k] = $item;
+            }
+        }
+
+        if ($missing) {
+            $syncItem = $this->syncItem;
+            $adapter = $this->adapters[$adapterIndex];
+            $items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex);
+
+            foreach ($items as $k => $item) {
+                if ($item->isHit()) {
+                    $adapter->save($syncItem($item, $misses[$k]));
+                }
+
+                yield $k => $item;
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasItem($key)
+    {
+        foreach ($this->adapters as $adapter) {
+            if ($adapter->hasItem($key)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        $cleared = true;
+        $i = $this->adapterCount;
+
+        while ($i--) {
+            $cleared = $this->adapters[$i]->clear() && $cleared;
+        }
+
+        return $cleared;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItem($key)
+    {
+        $deleted = true;
+        $i = $this->adapterCount;
+
+        while ($i--) {
+            $deleted = $this->adapters[$i]->deleteItem($key) && $deleted;
+        }
+
+        return $deleted;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItems(array $keys)
+    {
+        $deleted = true;
+        $i = $this->adapterCount;
+
+        while ($i--) {
+            $deleted = $this->adapters[$i]->deleteItems($keys) && $deleted;
+        }
+
+        return $deleted;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save(CacheItemInterface $item)
+    {
+        $saved = true;
+        $i = $this->adapterCount;
+
+        while ($i--) {
+            $saved = $this->adapters[$i]->save($item) && $saved;
+        }
+
+        return $saved;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function saveDeferred(CacheItemInterface $item)
+    {
+        $saved = true;
+        $i = $this->adapterCount;
+
+        while ($i--) {
+            $saved = $this->adapters[$i]->saveDeferred($item) && $saved;
+        }
+
+        return $saved;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function commit()
+    {
+        $committed = true;
+        $i = $this->adapterCount;
+
+        while ($i--) {
+            $committed = $this->adapters[$i]->commit() && $committed;
+        }
+
+        return $committed;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function prune()
+    {
+        $pruned = true;
+
+        foreach ($this->adapters as $adapter) {
+            if ($adapter instanceof PruneableInterface) {
+                $pruned = $adapter->prune() && $pruned;
+            }
+        }
+
+        return $pruned;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function reset()
+    {
+        foreach ($this->adapters as $adapter) {
+            if ($adapter instanceof ResetInterface) {
+                $adapter->reset();
+            }
+        }
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/DoctrineAdapter.php b/vendor/symfony/cache/Adapter/DoctrineAdapter.php
new file mode 100644
index 0000000..75ae4cb
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/DoctrineAdapter.php
@@ -0,0 +1,27 @@
+<?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\Cache\Adapter;
+
+use Doctrine\Common\Cache\CacheProvider;
+use Symfony\Component\Cache\Traits\DoctrineTrait;
+
+class DoctrineAdapter extends AbstractAdapter
+{
+    use DoctrineTrait;
+
+    public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0)
+    {
+        parent::__construct('', $defaultLifetime);
+        $this->provider = $provider;
+        $provider->setNamespace($namespace);
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/FilesystemAdapter.php b/vendor/symfony/cache/Adapter/FilesystemAdapter.php
new file mode 100644
index 0000000..7185dd4
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/FilesystemAdapter.php
@@ -0,0 +1,29 @@
+<?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\Cache\Adapter;
+
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\FilesystemTrait;
+
+class FilesystemAdapter extends AbstractAdapter implements PruneableInterface
+{
+    use FilesystemTrait;
+
+    public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
+    {
+        $this->marshaller = $marshaller ?? new DefaultMarshaller();
+        parent::__construct('', $defaultLifetime);
+        $this->init($namespace, $directory);
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php b/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php
new file mode 100644
index 0000000..67801e8
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php
@@ -0,0 +1,152 @@
+<?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\Cache\Adapter;
+
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\FilesystemTrait;
+
+/**
+ * Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ * @author André Rømcke <andre.romcke+symfony@gmail.com>
+ *
+ * @experimental in 4.3
+ */
+class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface
+{
+    use FilesystemTrait {
+        doSave as private doSaveCache;
+        doDelete as private doDeleteCache;
+    }
+
+    /**
+     * Folder used for tag symlinks.
+     */
+    private const TAG_FOLDER = 'tags';
+
+    public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
+    {
+        $this->marshaller = $marshaller ?? new DefaultMarshaller();
+        parent::__construct('', $defaultLifetime);
+        $this->init($namespace, $directory);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array
+    {
+        $failed = $this->doSaveCache($values, $lifetime);
+
+        // Add Tags as symlinks
+        foreach ($addTagData as $tagId => $ids) {
+            $tagFolder = $this->getTagFolder($tagId);
+            foreach ($ids as $id) {
+                if ($failed && \in_array($id, $failed, true)) {
+                    continue;
+                }
+
+                $file = $this->getFile($id);
+
+                if (!@symlink($file, $this->getFile($id, true, $tagFolder))) {
+                    @unlink($file);
+                    $failed[] = $id;
+                }
+            }
+        }
+
+        // Unlink removed Tags
+        foreach ($removeTagData as $tagId => $ids) {
+            $tagFolder = $this->getTagFolder($tagId);
+            foreach ($ids as $id) {
+                if ($failed && \in_array($id, $failed, true)) {
+                    continue;
+                }
+
+                @unlink($this->getFile($id, false, $tagFolder));
+            }
+        }
+
+        return $failed;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDelete(array $ids, array $tagData = []): bool
+    {
+        $ok = $this->doDeleteCache($ids);
+
+        // Remove tags
+        foreach ($tagData as $tagId => $idMap) {
+            $tagFolder = $this->getTagFolder($tagId);
+            foreach ($idMap as $id) {
+                @unlink($this->getFile($id, false, $tagFolder));
+            }
+        }
+
+        return $ok;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doInvalidate(array $tagIds): bool
+    {
+        foreach ($tagIds as $tagId) {
+            if (!file_exists($tagFolder = $this->getTagFolder($tagId))) {
+                continue;
+            }
+
+            set_error_handler(static function () {});
+
+            try {
+                if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -1))) {
+                    $tagFolder = $renamed.\DIRECTORY_SEPARATOR;
+                } else {
+                    $renamed = null;
+                }
+
+                foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($tagFolder, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $itemLink) {
+                    unlink(realpath($itemLink) ?: $itemLink);
+                    unlink($itemLink);
+                }
+
+                if (null === $renamed) {
+                    continue;
+                }
+
+                $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+                for ($i = 0; $i < 38; ++$i) {
+                    for ($j = 0; $j < 38; ++$j) {
+                        rmdir($tagFolder.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j]);
+                    }
+                    rmdir($tagFolder.$chars[$i]);
+                }
+                rmdir($renamed);
+            } finally {
+                restore_error_handler();
+            }
+        }
+
+        return true;
+    }
+
+    private function getTagFolder(string $tagId): string
+    {
+        return $this->getFile($tagId, false, $this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR;
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/MemcachedAdapter.php b/vendor/symfony/cache/Adapter/MemcachedAdapter.php
new file mode 100644
index 0000000..b678bb5
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/MemcachedAdapter.php
@@ -0,0 +1,37 @@
+<?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\Cache\Adapter;
+
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Traits\MemcachedTrait;
+
+class MemcachedAdapter extends AbstractAdapter
+{
+    use MemcachedTrait;
+
+    protected $maxIdLength = 250;
+
+    /**
+     * Using a MemcachedAdapter with a TagAwareAdapter for storing tags is discouraged.
+     * Using a RedisAdapter is recommended instead. If you cannot do otherwise, be aware that:
+     * - the Memcached::OPT_BINARY_PROTOCOL must be enabled
+     *   (that's the default when using MemcachedAdapter::createConnection());
+     * - tags eviction by Memcached's LRU algorithm will break by-tags invalidation;
+     *   your Memcached memory should be large enough to never trigger LRU.
+     *
+     * Using a MemcachedAdapter as a pure items store is fine.
+     */
+    public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+    {
+        $this->init($client, $namespace, $defaultLifetime, $marshaller);
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/NullAdapter.php b/vendor/symfony/cache/Adapter/NullAdapter.php
new file mode 100644
index 0000000..54cd453
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/NullAdapter.php
@@ -0,0 +1,140 @@
+<?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\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Contracts\Cache\CacheInterface;
+
+/**
+ * @author Titouan Galopin <galopintitouan@gmail.com>
+ */
+class NullAdapter implements AdapterInterface, CacheInterface
+{
+    private $createCacheItem;
+
+    public function __construct()
+    {
+        $this->createCacheItem = \Closure::bind(
+            function ($key) {
+                $item = new CacheItem();
+                $item->key = $key;
+                $item->isHit = false;
+
+                return $item;
+            },
+            $this,
+            CacheItem::class
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+    {
+        $save = true;
+
+        return $callback(($this->createCacheItem)($key), $save);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItem($key)
+    {
+        $f = $this->createCacheItem;
+
+        return $f($key);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItems(array $keys = [])
+    {
+        return $this->generateItems($keys);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasItem($key)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItem($key)
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItems(array $keys)
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save(CacheItemInterface $item)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function saveDeferred(CacheItemInterface $item)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function commit()
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function delete(string $key): bool
+    {
+        return $this->deleteItem($key);
+    }
+
+    private function generateItems(array $keys)
+    {
+        $f = $this->createCacheItem;
+
+        foreach ($keys as $key) {
+            yield $key => $f($key);
+        }
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/PdoAdapter.php b/vendor/symfony/cache/Adapter/PdoAdapter.php
new file mode 100644
index 0000000..d118736
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/PdoAdapter.php
@@ -0,0 +1,54 @@
+<?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\Cache\Adapter;
+
+use Doctrine\DBAL\Connection;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\PdoTrait;
+
+class PdoAdapter extends AbstractAdapter implements PruneableInterface
+{
+    use PdoTrait;
+
+    protected $maxIdLength = 255;
+
+    /**
+     * You can either pass an existing database connection as PDO instance or
+     * a Doctrine DBAL Connection or a DSN string that will be used to
+     * lazy-connect to the database when the cache is actually used.
+     *
+     * When a Doctrine DBAL Connection is passed, the cache table is created
+     * automatically when possible. Otherwise, use the createTable() method.
+     *
+     * List of available options:
+     *  * db_table: The name of the table [default: cache_items]
+     *  * db_id_col: The column where to store the cache id [default: item_id]
+     *  * db_data_col: The column where to store the cache data [default: item_data]
+     *  * db_lifetime_col: The column where to store the lifetime [default: item_lifetime]
+     *  * db_time_col: The column where to store the timestamp [default: item_time]
+     *  * db_username: The username when lazy-connect [default: '']
+     *  * db_password: The password when lazy-connect [default: '']
+     *  * db_connection_options: An array of driver-specific connection options [default: []]
+     *
+     * @param \PDO|Connection|string $connOrDsn a \PDO or Connection instance or DSN string or null
+     *
+     * @throws InvalidArgumentException When first argument is not PDO nor Connection nor string
+     * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
+     * @throws InvalidArgumentException When namespace contains invalid characters
+     */
+    public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
+    {
+        $this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/PhpArrayAdapter.php b/vendor/symfony/cache/Adapter/PhpArrayAdapter.php
new file mode 100644
index 0000000..2259240
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/PhpArrayAdapter.php
@@ -0,0 +1,325 @@
+<?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\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Component\Cache\Traits\PhpArrayTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+/**
+ * Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0.
+ * Warmed up items are read-only and run-time discovered items are cached using a fallback adapter.
+ *
+ * @author Titouan Galopin <galopintitouan@gmail.com>
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
+{
+    use PhpArrayTrait;
+    use ContractsTrait;
+
+    private $createCacheItem;
+
+    /**
+     * @param string           $file         The PHP file were values are cached
+     * @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit
+     */
+    public function __construct(string $file, AdapterInterface $fallbackPool)
+    {
+        $this->file = $file;
+        $this->pool = $fallbackPool;
+        $this->createCacheItem = \Closure::bind(
+            static function ($key, $value, $isHit) {
+                $item = new CacheItem();
+                $item->key = $key;
+                $item->value = $value;
+                $item->isHit = $isHit;
+
+                return $item;
+            },
+            null,
+            CacheItem::class
+        );
+    }
+
+    /**
+     * This adapter takes advantage of how PHP stores arrays in its latest versions.
+     *
+     * @param string                 $file         The PHP file were values are cached
+     * @param CacheItemPoolInterface $fallbackPool Fallback when opcache is disabled
+     *
+     * @return CacheItemPoolInterface
+     */
+    public static function create($file, CacheItemPoolInterface $fallbackPool)
+    {
+        // Shared memory is available in PHP 7.0+ with OPCache enabled
+        if (filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) {
+            if (!$fallbackPool instanceof AdapterInterface) {
+                $fallbackPool = new ProxyAdapter($fallbackPool);
+            }
+
+            return new static($file, $fallbackPool);
+        }
+
+        return $fallbackPool;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+    {
+        if (null === $this->values) {
+            $this->initialize();
+        }
+        if (!isset($this->keys[$key])) {
+            get_from_pool:
+            if ($this->pool instanceof CacheInterface) {
+                return $this->pool->get($key, $callback, $beta, $metadata);
+            }
+
+            return $this->doGet($this->pool, $key, $callback, $beta, $metadata);
+        }
+        $value = $this->values[$this->keys[$key]];
+
+        if ('N;' === $value) {
+            return null;
+        }
+        try {
+            if ($value instanceof \Closure) {
+                return $value();
+            }
+        } catch (\Throwable $e) {
+            unset($this->keys[$key]);
+            goto get_from_pool;
+        }
+
+        return $value;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItem($key)
+    {
+        if (!\is_string($key)) {
+            throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+        }
+        if (null === $this->values) {
+            $this->initialize();
+        }
+        if (!isset($this->keys[$key])) {
+            return $this->pool->getItem($key);
+        }
+
+        $value = $this->values[$this->keys[$key]];
+        $isHit = true;
+
+        if ('N;' === $value) {
+            $value = null;
+        } elseif ($value instanceof \Closure) {
+            try {
+                $value = $value();
+            } catch (\Throwable $e) {
+                $value = null;
+                $isHit = false;
+            }
+        }
+
+        $f = $this->createCacheItem;
+
+        return $f($key, $value, $isHit);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItems(array $keys = [])
+    {
+        foreach ($keys as $key) {
+            if (!\is_string($key)) {
+                throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+            }
+        }
+        if (null === $this->values) {
+            $this->initialize();
+        }
+
+        return $this->generateItems($keys);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasItem($key)
+    {
+        if (!\is_string($key)) {
+            throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+        }
+        if (null === $this->values) {
+            $this->initialize();
+        }
+
+        return isset($this->keys[$key]) || $this->pool->hasItem($key);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItem($key)
+    {
+        if (!\is_string($key)) {
+            throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+        }
+        if (null === $this->values) {
+            $this->initialize();
+        }
+
+        return !isset($this->keys[$key]) && $this->pool->deleteItem($key);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItems(array $keys)
+    {
+        $deleted = true;
+        $fallbackKeys = [];
+
+        foreach ($keys as $key) {
+            if (!\is_string($key)) {
+                throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+            }
+
+            if (isset($this->keys[$key])) {
+                $deleted = false;
+            } else {
+                $fallbackKeys[] = $key;
+            }
+        }
+        if (null === $this->values) {
+            $this->initialize();
+        }
+
+        if ($fallbackKeys) {
+            $deleted = $this->pool->deleteItems($fallbackKeys) && $deleted;
+        }
+
+        return $deleted;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save(CacheItemInterface $item)
+    {
+        if (null === $this->values) {
+            $this->initialize();
+        }
+
+        return !isset($this->keys[$item->getKey()]) && $this->pool->save($item);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function saveDeferred(CacheItemInterface $item)
+    {
+        if (null === $this->values) {
+            $this->initialize();
+        }
+
+        return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function commit()
+    {
+        return $this->pool->commit();
+    }
+
+    private function generateItems(array $keys): \Generator
+    {
+        $f = $this->createCacheItem;
+        $fallbackKeys = [];
+
+        foreach ($keys as $key) {
+            if (isset($this->keys[$key])) {
+                $value = $this->values[$this->keys[$key]];
+
+                if ('N;' === $value) {
+                    yield $key => $f($key, null, true);
+                } elseif ($value instanceof \Closure) {
+                    try {
+                        yield $key => $f($key, $value(), true);
+                    } catch (\Throwable $e) {
+                        yield $key => $f($key, null, false);
+                    }
+                } else {
+                    yield $key => $f($key, $value, true);
+                }
+            } else {
+                $fallbackKeys[] = $key;
+            }
+        }
+
+        if ($fallbackKeys) {
+            yield from $this->pool->getItems($fallbackKeys);
+        }
+    }
+
+    /**
+     * @throws \ReflectionException When $class is not found and is required
+     *
+     * @internal to be removed in Symfony 5.0
+     */
+    public static function throwOnRequiredClass($class)
+    {
+        $e = new \ReflectionException("Class $class does not exist");
+        $trace = $e->getTrace();
+        $autoloadFrame = [
+            'function' => 'spl_autoload_call',
+            'args' => [$class],
+        ];
+        $i = 1 + array_search($autoloadFrame, $trace, true);
+
+        if (isset($trace[$i]['function']) && !isset($trace[$i]['class'])) {
+            switch ($trace[$i]['function']) {
+                case 'get_class_methods':
+                case 'get_class_vars':
+                case 'get_parent_class':
+                case 'is_a':
+                case 'is_subclass_of':
+                case 'class_exists':
+                case 'class_implements':
+                case 'class_parents':
+                case 'trait_exists':
+                case 'defined':
+                case 'interface_exists':
+                case 'method_exists':
+                case 'property_exists':
+                case 'is_callable':
+                    return;
+            }
+        }
+
+        throw $e;
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/PhpFilesAdapter.php b/vendor/symfony/cache/Adapter/PhpFilesAdapter.php
new file mode 100644
index 0000000..10938a0
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/PhpFilesAdapter.php
@@ -0,0 +1,38 @@
+<?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\Cache\Adapter;
+
+use Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\PhpFilesTrait;
+
+class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
+{
+    use PhpFilesTrait;
+
+    /**
+     * @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire.
+     *                    Doing so is encouraged because it fits perfectly OPcache's memory model.
+     *
+     * @throws CacheException if OPcache is not enabled
+     */
+    public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
+    {
+        $this->appendOnly = $appendOnly;
+        self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
+        parent::__construct('', $defaultLifetime);
+        $this->init($namespace, $directory);
+        $this->includeHandler = static function ($type, $msg, $file, $line) {
+            throw new \ErrorException($msg, 0, $type, $file, $line);
+        };
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/ProxyAdapter.php b/vendor/symfony/cache/Adapter/ProxyAdapter.php
new file mode 100644
index 0000000..ffae53e
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/ProxyAdapter.php
@@ -0,0 +1,247 @@
+<?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\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Component\Cache\Traits\ProxyTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
+{
+    use ProxyTrait;
+    use ContractsTrait;
+
+    private $namespace;
+    private $namespaceLen;
+    private $createCacheItem;
+    private $setInnerItem;
+    private $poolHash;
+
+    public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0)
+    {
+        $this->pool = $pool;
+        $this->poolHash = $poolHash = spl_object_hash($pool);
+        $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace);
+        $this->namespaceLen = \strlen($namespace);
+        $this->createCacheItem = \Closure::bind(
+            static function ($key, $innerItem) use ($defaultLifetime, $poolHash) {
+                $item = new CacheItem();
+                $item->key = $key;
+
+                if (null === $innerItem) {
+                    return $item;
+                }
+
+                $item->value = $v = $innerItem->get();
+                $item->isHit = $innerItem->isHit();
+                $item->innerItem = $innerItem;
+                $item->defaultLifetime = $defaultLifetime;
+                $item->poolHash = $poolHash;
+
+                // Detect wrapped values that encode for their expiry and creation duration
+                // For compactness, these values are packed in the key of an array using
+                // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
+                if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
+                    $item->value = $v[$k];
+                    $v = unpack('Ve/Nc', substr($k, 1, -1));
+                    $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
+                    $item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
+                } elseif ($innerItem instanceof CacheItem) {
+                    $item->metadata = $innerItem->metadata;
+                }
+                $innerItem->set(null);
+
+                return $item;
+            },
+            null,
+            CacheItem::class
+        );
+        $this->setInnerItem = \Closure::bind(
+            /**
+             * @param array $item A CacheItem cast to (array); accessing protected properties requires adding the "\0*\0" PHP prefix
+             */
+            static function (CacheItemInterface $innerItem, array $item) {
+                // Tags are stored separately, no need to account for them when considering this item's newly set metadata
+                if (isset(($metadata = $item["\0*\0newMetadata"])[CacheItem::METADATA_TAGS])) {
+                    unset($metadata[CacheItem::METADATA_TAGS]);
+                }
+                if ($metadata) {
+                    // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
+                    $item["\0*\0value"] = ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item["\0*\0value"]];
+                }
+                $innerItem->set($item["\0*\0value"]);
+                $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6f', $item["\0*\0expiry"])) : null);
+            },
+            null,
+            CacheItem::class
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+    {
+        if (!$this->pool instanceof CacheInterface) {
+            return $this->doGet($this, $key, $callback, $beta, $metadata);
+        }
+
+        return $this->pool->get($this->getId($key), function ($innerItem, bool &$save) use ($key, $callback) {
+            $item = ($this->createCacheItem)($key, $innerItem);
+            $item->set($value = $callback($item, $save));
+            ($this->setInnerItem)($innerItem, (array) $item);
+
+            return $value;
+        }, $beta, $metadata);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItem($key)
+    {
+        $f = $this->createCacheItem;
+        $item = $this->pool->getItem($this->getId($key));
+
+        return $f($key, $item);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItems(array $keys = [])
+    {
+        if ($this->namespaceLen) {
+            foreach ($keys as $i => $key) {
+                $keys[$i] = $this->getId($key);
+            }
+        }
+
+        return $this->generateItems($this->pool->getItems($keys));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasItem($key)
+    {
+        return $this->pool->hasItem($this->getId($key));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        return $this->pool->clear();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItem($key)
+    {
+        return $this->pool->deleteItem($this->getId($key));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItems(array $keys)
+    {
+        if ($this->namespaceLen) {
+            foreach ($keys as $i => $key) {
+                $keys[$i] = $this->getId($key);
+            }
+        }
+
+        return $this->pool->deleteItems($keys);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save(CacheItemInterface $item)
+    {
+        return $this->doSave($item, __FUNCTION__);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function saveDeferred(CacheItemInterface $item)
+    {
+        return $this->doSave($item, __FUNCTION__);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function commit()
+    {
+        return $this->pool->commit();
+    }
+
+    private function doSave(CacheItemInterface $item, $method)
+    {
+        if (!$item instanceof CacheItem) {
+            return false;
+        }
+        $item = (array) $item;
+        if (null === $item["\0*\0expiry"] && 0 < $item["\0*\0defaultLifetime"]) {
+            $item["\0*\0expiry"] = microtime(true) + $item["\0*\0defaultLifetime"];
+        }
+
+        if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) {
+            $innerItem = $item["\0*\0innerItem"];
+        } elseif ($this->pool instanceof AdapterInterface) {
+            // this is an optimization specific for AdapterInterface implementations
+            // so we can save a round-trip to the backend by just creating a new item
+            $f = $this->createCacheItem;
+            $innerItem = $f($this->namespace.$item["\0*\0key"], null);
+        } else {
+            $innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]);
+        }
+
+        ($this->setInnerItem)($innerItem, $item);
+
+        return $this->pool->$method($innerItem);
+    }
+
+    private function generateItems($items)
+    {
+        $f = $this->createCacheItem;
+
+        foreach ($items as $key => $item) {
+            if ($this->namespaceLen) {
+                $key = substr($key, $this->namespaceLen);
+            }
+
+            yield $key => $f($key, $item);
+        }
+    }
+
+    private function getId($key)
+    {
+        CacheItem::validateKey($key);
+
+        return $this->namespace.$key;
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/Psr16Adapter.php b/vendor/symfony/cache/Adapter/Psr16Adapter.php
new file mode 100644
index 0000000..bb38871
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/Psr16Adapter.php
@@ -0,0 +1,86 @@
+<?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\Cache\Adapter;
+
+use Psr\SimpleCache\CacheInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ProxyTrait;
+
+/**
+ * Turns a PSR-16 cache into a PSR-6 one.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface
+{
+    /**
+     * @internal
+     */
+    protected const NS_SEPARATOR = '_';
+
+    use ProxyTrait;
+
+    private $miss;
+
+    public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0)
+    {
+        parent::__construct($namespace, $defaultLifetime);
+
+        $this->pool = $pool;
+        $this->miss = new \stdClass();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doFetch(array $ids)
+    {
+        foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) {
+            if ($this->miss !== $value) {
+                yield $key => $value;
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doHave($id)
+    {
+        return $this->pool->has($id);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doClear($namespace)
+    {
+        return $this->pool->clear();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDelete(array $ids)
+    {
+        return $this->pool->deleteMultiple($ids);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doSave(array $values, $lifetime)
+    {
+        return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/RedisAdapter.php b/vendor/symfony/cache/Adapter/RedisAdapter.php
new file mode 100644
index 0000000..5c49f7a
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/RedisAdapter.php
@@ -0,0 +1,30 @@
+<?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\Cache\Adapter;
+
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Traits\RedisTrait;
+
+class RedisAdapter extends AbstractAdapter
+{
+    use RedisTrait;
+
+    /**
+     * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient     The redis client
+     * @param string                                                   $namespace       The default namespace
+     * @param int                                                      $defaultLifetime The default lifetime
+     */
+    public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+    {
+        $this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php b/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php
new file mode 100644
index 0000000..b83b09b
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php
@@ -0,0 +1,212 @@
+<?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\Cache\Adapter;
+
+use Predis;
+use Predis\Connection\Aggregate\ClusterInterface;
+use Predis\Response\Status;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\LogicException;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Traits\RedisTrait;
+
+/**
+ * Stores tag id <> cache id relationship as a Redis Set, lookup on invalidation using sPOP.
+ *
+ * Set (tag relation info) is stored without expiry (non-volatile), while cache always gets an expiry (volatile) even
+ * if not set by caller. Thus if you configure redis with the right eviction policy you can be safe this tag <> cache
+ * relationship survives eviction (cache cleanup when Redis runs out of memory).
+ *
+ * Requirements:
+ *  - Server: Redis 3.2+
+ *  - Client: PHP Redis 3.1.3+ OR Predis
+ *  - Redis Server(s) configured with any `volatile-*` eviction policy, OR `noeviction` if it will NEVER fill up memory
+ *
+ * Design limitations:
+ *  - Max 2 billion cache keys per cache tag
+ *    E.g. If you use a "all" items tag for expiry instead of clear(), that limits you to 2 billion cache items as well
+ *
+ * @see https://redis.io/topics/lru-cache#eviction-policies Documentation for Redis eviction policies.
+ * @see https://redis.io/topics/data-types#sets Documentation for Redis Set datatype.
+ * @see https://redis.io/commands/spop Documentation for sPOP operation, capable of retriving AND emptying a Set at once.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ * @author André Rømcke <andre.romcke+symfony@gmail.com>
+ *
+ * @experimental in 4.3
+ */
+class RedisTagAwareAdapter extends AbstractTagAwareAdapter
+{
+    use RedisTrait;
+
+    /**
+     * Redis "Set" can hold more than 4 billion members, here we limit ourselves to PHP's > 2 billion max int (32Bit).
+     */
+    private const POP_MAX_LIMIT = 2147483647 - 1;
+
+    /**
+     * Limits for how many keys are deleted in batch.
+     */
+    private const BULK_DELETE_LIMIT = 10000;
+
+    /**
+     * On cache items without a lifetime set, we set it to 100 days. This is to make sure cache items are
+     * preferred to be evicted over tag Sets, if eviction policy is configured according to requirements.
+     */
+    private const DEFAULT_CACHE_TTL = 8640000;
+
+    /**
+     * @var bool|null
+     */
+    private $redisServerSupportSPOP = null;
+
+    /**
+     * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient     The redis client
+     * @param string                                                   $namespace       The default namespace
+     * @param int                                                      $defaultLifetime The default lifetime
+     *
+     * @throws \Symfony\Component\Cache\Exception\LogicException If phpredis with version lower than 3.1.3.
+     */
+    public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+    {
+        $this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
+
+        // Make sure php-redis is 3.1.3 or higher configured for Redis classes
+        if (!$this->redis instanceof \Predis\ClientInterface && version_compare(phpversion('redis'), '3.1.3', '<')) {
+            throw new LogicException('RedisTagAwareAdapter requires php-redis 3.1.3 or higher, alternatively use predis/predis');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $delTagData = []): array
+    {
+        // serialize values
+        if (!$serialized = $this->marshaller->marshall($values, $failed)) {
+            return $failed;
+        }
+
+        // While pipeline isn't supported on RedisCluster, other setups will at least benefit from doing this in one op
+        $results = $this->pipeline(static function () use ($serialized, $lifetime, $addTagData, $delTagData, $failed) {
+            // Store cache items, force a ttl if none is set, as there is no MSETEX we need to set each one
+            foreach ($serialized as $id => $value) {
+                yield 'setEx' => [
+                    $id,
+                    0 >= $lifetime ? self::DEFAULT_CACHE_TTL : $lifetime,
+                    $value,
+                ];
+            }
+
+            // Add and Remove Tags
+            foreach ($addTagData as $tagId => $ids) {
+                if (!$failed || $ids = array_diff($ids, $failed)) {
+                    yield 'sAdd' => array_merge([$tagId], $ids);
+                }
+            }
+
+            foreach ($delTagData as $tagId => $ids) {
+                if (!$failed || $ids = array_diff($ids, $failed)) {
+                    yield 'sRem' => array_merge([$tagId], $ids);
+                }
+            }
+        });
+
+        foreach ($results as $id => $result) {
+            // Skip results of SADD/SREM operations, they'll be 1 or 0 depending on if set value already existed or not
+            if (is_numeric($result)) {
+                continue;
+            }
+            // setEx results
+            if (true !== $result && (!$result instanceof Status || $result !== Status::get('OK'))) {
+                $failed[] = $id;
+            }
+        }
+
+        return $failed;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDelete(array $ids, array $tagData = []): bool
+    {
+        if (!$ids) {
+            return true;
+        }
+
+        $predisCluster = $this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface;
+        $this->pipeline(static function () use ($ids, $tagData, $predisCluster) {
+            if ($predisCluster) {
+                foreach ($ids as $id) {
+                    yield 'del' => [$id];
+                }
+            } else {
+                yield 'del' => $ids;
+            }
+
+            foreach ($tagData as $tagId => $idList) {
+                yield 'sRem' => array_merge([$tagId], $idList);
+            }
+        })->rewind();
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doInvalidate(array $tagIds): bool
+    {
+        if (!$this->redisServerSupportSPOP()) {
+            return false;
+        }
+
+        // Pop all tag info at once to avoid race conditions
+        $tagIdSets = $this->pipeline(static function () use ($tagIds) {
+            foreach ($tagIds as $tagId) {
+                // Client: Predis or PHP Redis 3.1.3+ (https://github.com/phpredis/phpredis/commit/d2e203a6)
+                // Server: Redis 3.2 or higher (https://redis.io/commands/spop)
+                yield 'sPop' => [$tagId, self::POP_MAX_LIMIT];
+            }
+        });
+
+        // Flatten generator result from pipeline, ignore keys (tag ids)
+        $ids = array_unique(array_merge(...iterator_to_array($tagIdSets, false)));
+
+        // Delete cache in chunks to avoid overloading the connection
+        foreach (array_chunk($ids, self::BULK_DELETE_LIMIT) as $chunkIds) {
+            $this->doDelete($chunkIds);
+        }
+
+        return true;
+    }
+
+    private function redisServerSupportSPOP(): bool
+    {
+        if (null !== $this->redisServerSupportSPOP) {
+            return $this->redisServerSupportSPOP;
+        }
+
+        foreach ($this->getHosts() as $host) {
+            $info = $host->info('Server');
+            $info = isset($info['Server']) ? $info['Server'] : $info;
+            if (version_compare($info['redis_version'], '3.2', '<')) {
+                CacheItem::log($this->logger, 'Redis server needs to be version 3.2 or higher, your Redis server was detected as '.$info['redis_version']);
+
+                return $this->redisServerSupportSPOP = false;
+            }
+        }
+
+        return $this->redisServerSupportSPOP = true;
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/SimpleCacheAdapter.php b/vendor/symfony/cache/Adapter/SimpleCacheAdapter.php
new file mode 100644
index 0000000..d0d42e5
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/SimpleCacheAdapter.php
@@ -0,0 +1,21 @@
+<?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\Cache\Adapter;
+
+@trigger_error(sprintf('The "%s" class is @deprecated since Symfony 4.3, use "Psr16Adapter" instead.', SimpleCacheAdapter::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use Psr16Adapter instead.
+ */
+class SimpleCacheAdapter extends Psr16Adapter
+{
+}
diff --git a/vendor/symfony/cache/Adapter/TagAwareAdapter.php b/vendor/symfony/cache/Adapter/TagAwareAdapter.php
new file mode 100644
index 0000000..72fc526
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/TagAwareAdapter.php
@@ -0,0 +1,399 @@
+<?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\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\InvalidArgumentException;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Component\Cache\Traits\ProxyTrait;
+use Symfony\Contracts\Cache\TagAwareCacheInterface;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface
+{
+    const TAGS_PREFIX = "\0tags\0";
+
+    use ProxyTrait;
+    use ContractsTrait;
+
+    private $deferred = [];
+    private $createCacheItem;
+    private $setCacheItemTags;
+    private $getTagsByKey;
+    private $invalidateTags;
+    private $tags;
+    private $knownTagVersions = [];
+    private $knownTagVersionsTtl;
+
+    public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, float $knownTagVersionsTtl = 0.15)
+    {
+        $this->pool = $itemsPool;
+        $this->tags = $tagsPool ?: $itemsPool;
+        $this->knownTagVersionsTtl = $knownTagVersionsTtl;
+        $this->createCacheItem = \Closure::bind(
+            static function ($key, $value, CacheItem $protoItem) {
+                $item = new CacheItem();
+                $item->key = $key;
+                $item->value = $value;
+                $item->defaultLifetime = $protoItem->defaultLifetime;
+                $item->expiry = $protoItem->expiry;
+                $item->poolHash = $protoItem->poolHash;
+
+                return $item;
+            },
+            null,
+            CacheItem::class
+        );
+        $this->setCacheItemTags = \Closure::bind(
+            static function (CacheItem $item, $key, array &$itemTags) {
+                $item->isTaggable = true;
+                if (!$item->isHit) {
+                    return $item;
+                }
+                if (isset($itemTags[$key])) {
+                    foreach ($itemTags[$key] as $tag => $version) {
+                        $item->metadata[CacheItem::METADATA_TAGS][$tag] = $tag;
+                    }
+                    unset($itemTags[$key]);
+                } else {
+                    $item->value = null;
+                    $item->isHit = false;
+                }
+
+                return $item;
+            },
+            null,
+            CacheItem::class
+        );
+        $this->getTagsByKey = \Closure::bind(
+            static function ($deferred) {
+                $tagsByKey = [];
+                foreach ($deferred as $key => $item) {
+                    $tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? [];
+                }
+
+                return $tagsByKey;
+            },
+            null,
+            CacheItem::class
+        );
+        $this->invalidateTags = \Closure::bind(
+            static function (AdapterInterface $tagsAdapter, array $tags) {
+                foreach ($tags as $v) {
+                    $v->defaultLifetime = 0;
+                    $v->expiry = null;
+                    $tagsAdapter->saveDeferred($v);
+                }
+
+                return $tagsAdapter->commit();
+            },
+            null,
+            CacheItem::class
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function invalidateTags(array $tags)
+    {
+        $ok = true;
+        $tagsByKey = [];
+        $invalidatedTags = [];
+        foreach ($tags as $tag) {
+            CacheItem::validateKey($tag);
+            $invalidatedTags[$tag] = 0;
+        }
+
+        if ($this->deferred) {
+            $items = $this->deferred;
+            foreach ($items as $key => $item) {
+                if (!$this->pool->saveDeferred($item)) {
+                    unset($this->deferred[$key]);
+                    $ok = false;
+                }
+            }
+
+            $f = $this->getTagsByKey;
+            $tagsByKey = $f($items);
+            $this->deferred = [];
+        }
+
+        $tagVersions = $this->getTagVersions($tagsByKey, $invalidatedTags);
+        $f = $this->createCacheItem;
+
+        foreach ($tagsByKey as $key => $tags) {
+            $this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key]));
+        }
+        $ok = $this->pool->commit() && $ok;
+
+        if ($invalidatedTags) {
+            $f = $this->invalidateTags;
+            $ok = $f($this->tags, $invalidatedTags) && $ok;
+        }
+
+        return $ok;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasItem($key)
+    {
+        if ($this->deferred) {
+            $this->commit();
+        }
+        if (!$this->pool->hasItem($key)) {
+            return false;
+        }
+
+        $itemTags = $this->pool->getItem(static::TAGS_PREFIX.$key);
+
+        if (!$itemTags->isHit()) {
+            return false;
+        }
+
+        if (!$itemTags = $itemTags->get()) {
+            return true;
+        }
+
+        foreach ($this->getTagVersions([$itemTags]) as $tag => $version) {
+            if ($itemTags[$tag] !== $version && 1 !== $itemTags[$tag] - $version) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItem($key)
+    {
+        foreach ($this->getItems([$key]) as $item) {
+            return $item;
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItems(array $keys = [])
+    {
+        if ($this->deferred) {
+            $this->commit();
+        }
+        $tagKeys = [];
+
+        foreach ($keys as $key) {
+            if ('' !== $key && \is_string($key)) {
+                $key = static::TAGS_PREFIX.$key;
+                $tagKeys[$key] = $key;
+            }
+        }
+
+        try {
+            $items = $this->pool->getItems($tagKeys + $keys);
+        } catch (InvalidArgumentException $e) {
+            $this->pool->getItems($keys); // Should throw an exception
+
+            throw $e;
+        }
+
+        return $this->generateItems($items, $tagKeys);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        $this->deferred = [];
+
+        return $this->pool->clear();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItem($key)
+    {
+        return $this->deleteItems([$key]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItems(array $keys)
+    {
+        foreach ($keys as $key) {
+            if ('' !== $key && \is_string($key)) {
+                $keys[] = static::TAGS_PREFIX.$key;
+            }
+        }
+
+        return $this->pool->deleteItems($keys);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save(CacheItemInterface $item)
+    {
+        if (!$item instanceof CacheItem) {
+            return false;
+        }
+        $this->deferred[$item->getKey()] = $item;
+
+        return $this->commit();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function saveDeferred(CacheItemInterface $item)
+    {
+        if (!$item instanceof CacheItem) {
+            return false;
+        }
+        $this->deferred[$item->getKey()] = $item;
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function commit()
+    {
+        return $this->invalidateTags([]);
+    }
+
+    public function __sleep()
+    {
+        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+    }
+
+    public function __wakeup()
+    {
+        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+    }
+
+    public function __destruct()
+    {
+        $this->commit();
+    }
+
+    private function generateItems($items, array $tagKeys)
+    {
+        $bufferedItems = $itemTags = [];
+        $f = $this->setCacheItemTags;
+
+        foreach ($items as $key => $item) {
+            if (!$tagKeys) {
+                yield $key => $f($item, static::TAGS_PREFIX.$key, $itemTags);
+                continue;
+            }
+            if (!isset($tagKeys[$key])) {
+                $bufferedItems[$key] = $item;
+                continue;
+            }
+
+            unset($tagKeys[$key]);
+
+            if ($item->isHit()) {
+                $itemTags[$key] = $item->get() ?: [];
+            }
+
+            if (!$tagKeys) {
+                $tagVersions = $this->getTagVersions($itemTags);
+
+                foreach ($itemTags as $key => $tags) {
+                    foreach ($tags as $tag => $version) {
+                        if ($tagVersions[$tag] !== $version && 1 !== $version - $tagVersions[$tag]) {
+                            unset($itemTags[$key]);
+                            continue 2;
+                        }
+                    }
+                }
+                $tagVersions = $tagKeys = null;
+
+                foreach ($bufferedItems as $key => $item) {
+                    yield $key => $f($item, static::TAGS_PREFIX.$key, $itemTags);
+                }
+                $bufferedItems = null;
+            }
+        }
+    }
+
+    private function getTagVersions(array $tagsByKey, array &$invalidatedTags = [])
+    {
+        $tagVersions = $invalidatedTags;
+
+        foreach ($tagsByKey as $tags) {
+            $tagVersions += $tags;
+        }
+
+        if (!$tagVersions) {
+            return [];
+        }
+
+        if (!$fetchTagVersions = 1 !== \func_num_args()) {
+            foreach ($tagsByKey as $tags) {
+                foreach ($tags as $tag => $version) {
+                    if ($tagVersions[$tag] > $version) {
+                        $tagVersions[$tag] = $version;
+                    }
+                }
+            }
+        }
+
+        $now = microtime(true);
+        $tags = [];
+        foreach ($tagVersions as $tag => $version) {
+            $tags[$tag.static::TAGS_PREFIX] = $tag;
+            if ($fetchTagVersions || !isset($this->knownTagVersions[$tag])) {
+                $fetchTagVersions = true;
+                continue;
+            }
+            $version -= $this->knownTagVersions[$tag][1];
+            if ((0 !== $version && 1 !== $version) || $now - $this->knownTagVersions[$tag][0] >= $this->knownTagVersionsTtl) {
+                // reuse previously fetched tag versions up to the ttl, unless we are storing items or a potential miss arises
+                $fetchTagVersions = true;
+            } else {
+                $this->knownTagVersions[$tag][1] += $version;
+            }
+        }
+
+        if (!$fetchTagVersions) {
+            return $tagVersions;
+        }
+
+        foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) {
+            $tagVersions[$tag = $tags[$tag]] = $version->get() ?: 0;
+            if (isset($invalidatedTags[$tag])) {
+                $invalidatedTags[$tag] = $version->set(++$tagVersions[$tag]);
+            }
+            $this->knownTagVersions[$tag] = [$now, $tagVersions[$tag]];
+        }
+
+        return $tagVersions;
+    }
+}
diff --git a/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php b/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php
new file mode 100644
index 0000000..340048c
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php
@@ -0,0 +1,33 @@
+<?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\Cache\Adapter;
+
+use Psr\Cache\InvalidArgumentException;
+
+/**
+ * Interface for invalidating cached items using tags.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+interface TagAwareAdapterInterface extends AdapterInterface
+{
+    /**
+     * Invalidates cached items using tags.
+     *
+     * @param string[] $tags An array of tags to invalidate
+     *
+     * @return bool True on success
+     *
+     * @throws InvalidArgumentException When $tags is not valid
+     */
+    public function invalidateTags(array $tags);
+}
diff --git a/vendor/symfony/cache/Adapter/TraceableAdapter.php b/vendor/symfony/cache/Adapter/TraceableAdapter.php
new file mode 100644
index 0000000..660acf5
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/TraceableAdapter.php
@@ -0,0 +1,281 @@
+<?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\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * An adapter that collects data about all cache calls.
+ *
+ * @author Aaron Scherer <aequasi@gmail.com>
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
+{
+    protected $pool;
+    private $calls = [];
+
+    public function __construct(AdapterInterface $pool)
+    {
+        $this->pool = $pool;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+    {
+        if (!$this->pool instanceof CacheInterface) {
+            throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', \get_class($this->pool), CacheInterface::class));
+        }
+
+        $isHit = true;
+        $callback = function (CacheItem $item, bool &$save) use ($callback, &$isHit) {
+            $isHit = $item->isHit();
+
+            return $callback($item, $save);
+        };
+
+        $event = $this->start(__FUNCTION__);
+        try {
+            $value = $this->pool->get($key, $callback, $beta, $metadata);
+            $event->result[$key] = \is_object($value) ? \get_class($value) : \gettype($value);
+        } finally {
+            $event->end = microtime(true);
+        }
+        if ($isHit) {
+            ++$event->hits;
+        } else {
+            ++$event->misses;
+        }
+
+        return $value;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItem($key)
+    {
+        $event = $this->start(__FUNCTION__);
+        try {
+            $item = $this->pool->getItem($key);
+        } finally {
+            $event->end = microtime(true);
+        }
+        if ($event->result[$key] = $item->isHit()) {
+            ++$event->hits;
+        } else {
+            ++$event->misses;
+        }
+
+        return $item;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasItem($key)
+    {
+        $event = $this->start(__FUNCTION__);
+        try {
+            return $event->result[$key] = $this->pool->hasItem($key);
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItem($key)
+    {
+        $event = $this->start(__FUNCTION__);
+        try {
+            return $event->result[$key] = $this->pool->deleteItem($key);
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save(CacheItemInterface $item)
+    {
+        $event = $this->start(__FUNCTION__);
+        try {
+            return $event->result[$item->getKey()] = $this->pool->save($item);
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function saveDeferred(CacheItemInterface $item)
+    {
+        $event = $this->start(__FUNCTION__);
+        try {
+            return $event->result[$item->getKey()] = $this->pool->saveDeferred($item);
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItems(array $keys = [])
+    {
+        $event = $this->start(__FUNCTION__);
+        try {
+            $result = $this->pool->getItems($keys);
+        } finally {
+            $event->end = microtime(true);
+        }
+        $f = function () use ($result, $event) {
+            $event->result = [];
+            foreach ($result as $key => $item) {
+                if ($event->result[$key] = $item->isHit()) {
+                    ++$event->hits;
+                } else {
+                    ++$event->misses;
+                }
+                yield $key => $item;
+            }
+        };
+
+        return $f();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        $event = $this->start(__FUNCTION__);
+        try {
+            return $event->result = $this->pool->clear();
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItems(array $keys)
+    {
+        $event = $this->start(__FUNCTION__);
+        $event->result['keys'] = $keys;
+        try {
+            return $event->result['result'] = $this->pool->deleteItems($keys);
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function commit()
+    {
+        $event = $this->start(__FUNCTION__);
+        try {
+            return $event->result = $this->pool->commit();
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function prune()
+    {
+        if (!$this->pool instanceof PruneableInterface) {
+            return false;
+        }
+        $event = $this->start(__FUNCTION__);
+        try {
+            return $event->result = $this->pool->prune();
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function reset()
+    {
+        if (!$this->pool instanceof ResetInterface) {
+            return;
+        }
+        $event = $this->start(__FUNCTION__);
+        try {
+            $this->pool->reset();
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function delete(string $key): bool
+    {
+        $event = $this->start(__FUNCTION__);
+        try {
+            return $event->result[$key] = $this->pool->deleteItem($key);
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    public function getCalls()
+    {
+        return $this->calls;
+    }
+
+    public function clearCalls()
+    {
+        $this->calls = [];
+    }
+
+    protected function start($name)
+    {
+        $this->calls[] = $event = new TraceableAdapterEvent();
+        $event->name = $name;
+        $event->start = microtime(true);
+
+        return $event;
+    }
+}
+
+class TraceableAdapterEvent
+{
+    public $name;
+    public $start;
+    public $end;
+    public $result;
+    public $hits = 0;
+    public $misses = 0;
+}
diff --git a/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php b/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php
new file mode 100644
index 0000000..69461b8
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php
@@ -0,0 +1,38 @@
+<?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\Cache\Adapter;
+
+use Symfony\Contracts\Cache\TagAwareCacheInterface;
+
+/**
+ * @author Robin Chalas <robin.chalas@gmail.com>
+ */
+class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface
+{
+    public function __construct(TagAwareAdapterInterface $pool)
+    {
+        parent::__construct($pool);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function invalidateTags(array $tags)
+    {
+        $event = $this->start(__FUNCTION__);
+        try {
+            return $event->result = $this->pool->invalidateTags($tags);
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+}
diff --git a/vendor/symfony/cache/CHANGELOG.md b/vendor/symfony/cache/CHANGELOG.md
new file mode 100644
index 0000000..37dd2e1
--- /dev/null
+++ b/vendor/symfony/cache/CHANGELOG.md
@@ -0,0 +1,61 @@
+CHANGELOG
+=========
+
+4.3.0
+-----
+
+ * removed `psr/simple-cache` dependency, run `composer require psr/simple-cache` if you need it
+ * deprecated all PSR-16 adapters, use `Psr16Cache` or `Symfony\Contracts\Cache\CacheInterface` implementations instead
+ * deprecated `SimpleCacheAdapter`, use `Psr16Adapter` instead
+
+4.2.0
+-----
+
+ * added support for connecting to Redis clusters via DSN
+ * added support for configuring multiple Memcached servers via DSN
+ * added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available
+ * implemented `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache
+ * added sub-second expiry accuracy for backends that support it
+ * added support for phpredis 4 `compression` and `tcp_keepalive` options
+ * added automatic table creation when using Doctrine DBAL with PDO-based backends
+ * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool
+ * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead
+ * deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods
+ * added `CacheCollectorPass` (originally in `FrameworkBundle`)
+ * added `CachePoolClearerPass` (originally in `FrameworkBundle`)
+ * added `CachePoolPass` (originally in `FrameworkBundle`)
+ * added `CachePoolPrunerPass` (originally in `FrameworkBundle`)
+
+3.4.0
+-----
+
+ * added using options from Memcached DSN
+ * added PruneableInterface so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning
+ * added prune logic to FilesystemTrait, PhpFilesTrait, PdoTrait, TagAwareAdapter and ChainTrait
+ * now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, PhpFilesCache, PdoAdapter, PdoCache, ChainAdapter, and
+   ChainCache implement PruneableInterface and support manual stale cache pruning
+
+3.3.0
+-----
+
+ * added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any
+ * added PSR-16 "Simple Cache" implementations for all existing PSR-6 adapters
+ * added Psr6Cache and SimpleCacheAdapter for bidirectional interoperability between PSR-6 and PSR-16
+ * added MemcachedAdapter (PSR-6) and MemcachedCache (PSR-16)
+ * added TraceableAdapter (PSR-6) and TraceableCache (PSR-16)
+
+3.2.0
+-----
+
+ * added TagAwareAdapter for tags-based invalidation
+ * added PdoAdapter with PDO and Doctrine DBAL support
+ * added PhpArrayAdapter and PhpFilesAdapter for OPcache-backed shared memory storage (PHP 7+ only)
+ * added NullAdapter
+
+3.1.0
+-----
+
+ * added the component with strict PSR-6 implementations
+ * added ApcuAdapter, ArrayAdapter, FilesystemAdapter and RedisAdapter
+ * added AbstractAdapter, ChainAdapter and ProxyAdapter
+ * added DoctrineAdapter and DoctrineProvider for bidirectional interoperability with Doctrine Cache
diff --git a/vendor/symfony/cache/CacheItem.php b/vendor/symfony/cache/CacheItem.php
new file mode 100644
index 0000000..85b5dd5
--- /dev/null
+++ b/vendor/symfony/cache/CacheItem.php
@@ -0,0 +1,206 @@
+<?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\Cache;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Exception\LogicException;
+use Symfony\Contracts\Cache\ItemInterface;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+final class CacheItem implements ItemInterface
+{
+    private const METADATA_EXPIRY_OFFSET = 1527506807;
+
+    protected $key;
+    protected $value;
+    protected $isHit = false;
+    protected $expiry;
+    protected $defaultLifetime;
+    protected $metadata = [];
+    protected $newMetadata = [];
+    protected $innerItem;
+    protected $poolHash;
+    protected $isTaggable = false;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getKey()
+    {
+        return $this->key;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get()
+    {
+        return $this->value;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isHit()
+    {
+        return $this->isHit;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @return $this
+     */
+    public function set($value)
+    {
+        $this->value = $value;
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @return $this
+     */
+    public function expiresAt($expiration)
+    {
+        if (null === $expiration) {
+            $this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null;
+        } elseif ($expiration instanceof \DateTimeInterface) {
+            $this->expiry = (float) $expiration->format('U.u');
+        } else {
+            throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given', \is_object($expiration) ? \get_class($expiration) : \gettype($expiration)));
+        }
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @return $this
+     */
+    public function expiresAfter($time)
+    {
+        if (null === $time) {
+            $this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null;
+        } elseif ($time instanceof \DateInterval) {
+            $this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u');
+        } elseif (\is_int($time)) {
+            $this->expiry = $time + microtime(true);
+        } else {
+            throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', \is_object($time) ? \get_class($time) : \gettype($time)));
+        }
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function tag($tags): ItemInterface
+    {
+        if (!$this->isTaggable) {
+            throw new LogicException(sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key));
+        }
+        if (!is_iterable($tags)) {
+            $tags = [$tags];
+        }
+        foreach ($tags as $tag) {
+            if (!\is_string($tag)) {
+                throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given', \is_object($tag) ? \get_class($tag) : \gettype($tag)));
+            }
+            if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) {
+                continue;
+            }
+            if ('' === $tag) {
+                throw new InvalidArgumentException('Cache tag length must be greater than zero');
+            }
+            if (false !== strpbrk($tag, '{}()/\@:')) {
+                throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters {}()/\@:', $tag));
+            }
+            $this->newMetadata[self::METADATA_TAGS][$tag] = $tag;
+        }
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMetadata(): array
+    {
+        return $this->metadata;
+    }
+
+    /**
+     * Returns the list of tags bound to the value coming from the pool storage if any.
+     *
+     * @return array
+     *
+     * @deprecated since Symfony 4.2, use the "getMetadata()" method instead.
+     */
+    public function getPreviousTags()
+    {
+        @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "getMetadata()" method instead.', __METHOD__), E_USER_DEPRECATED);
+
+        return $this->metadata[self::METADATA_TAGS] ?? [];
+    }
+
+    /**
+     * Validates a cache key according to PSR-6.
+     *
+     * @param string $key The key to validate
+     *
+     * @return string
+     *
+     * @throws InvalidArgumentException When $key is not valid
+     */
+    public static function validateKey($key)
+    {
+        if (!\is_string($key)) {
+            throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given', \is_object($key) ? \get_class($key) : \gettype($key)));
+        }
+        if ('' === $key) {
+            throw new InvalidArgumentException('Cache key length must be greater than zero');
+        }
+        if (false !== strpbrk($key, '{}()/\@:')) {
+            throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters {}()/\@:', $key));
+        }
+
+        return $key;
+    }
+
+    /**
+     * Internal logging helper.
+     *
+     * @internal
+     */
+    public static function log(LoggerInterface $logger = null, $message, $context = [])
+    {
+        if ($logger) {
+            $logger->warning($message, $context);
+        } else {
+            $replace = [];
+            foreach ($context as $k => $v) {
+                if (is_scalar($v)) {
+                    $replace['{'.$k.'}'] = $v;
+                }
+            }
+            @trigger_error(strtr($message, $replace), E_USER_WARNING);
+        }
+    }
+}
diff --git a/vendor/symfony/cache/DataCollector/CacheDataCollector.php b/vendor/symfony/cache/DataCollector/CacheDataCollector.php
new file mode 100644
index 0000000..1ac7029
--- /dev/null
+++ b/vendor/symfony/cache/DataCollector/CacheDataCollector.php
@@ -0,0 +1,190 @@
+<?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\Cache\DataCollector;
+
+use Symfony\Component\Cache\Adapter\TraceableAdapter;
+use Symfony\Component\Cache\Adapter\TraceableAdapterEvent;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\DataCollector\DataCollector;
+use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
+
+/**
+ * @author Aaron Scherer <aequasi@gmail.com>
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+class CacheDataCollector extends DataCollector implements LateDataCollectorInterface
+{
+    /**
+     * @var TraceableAdapter[]
+     */
+    private $instances = [];
+
+    /**
+     * @param string $name
+     */
+    public function addInstance($name, TraceableAdapter $instance)
+    {
+        $this->instances[$name] = $instance;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function collect(Request $request, Response $response, \Exception $exception = null)
+    {
+        $empty = ['calls' => [], 'config' => [], 'options' => [], 'statistics' => []];
+        $this->data = ['instances' => $empty, 'total' => $empty];
+        foreach ($this->instances as $name => $instance) {
+            $this->data['instances']['calls'][$name] = $instance->getCalls();
+        }
+
+        $this->data['instances']['statistics'] = $this->calculateStatistics();
+        $this->data['total']['statistics'] = $this->calculateTotalStatistics();
+    }
+
+    public function reset()
+    {
+        $this->data = [];
+        foreach ($this->instances as $instance) {
+            $instance->clearCalls();
+        }
+    }
+
+    public function lateCollect()
+    {
+        $this->data = $this->cloneVar($this->data);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getName()
+    {
+        return 'cache';
+    }
+
+    /**
+     * Method returns amount of logged Cache reads: "get" calls.
+     *
+     * @return array
+     */
+    public function getStatistics()
+    {
+        return $this->data['instances']['statistics'];
+    }
+
+    /**
+     * Method returns the statistic totals.
+     *
+     * @return array
+     */
+    public function getTotals()
+    {
+        return $this->data['total']['statistics'];
+    }
+
+    /**
+     * Method returns all logged Cache call objects.
+     *
+     * @return mixed
+     */
+    public function getCalls()
+    {
+        return $this->data['instances']['calls'];
+    }
+
+    private function calculateStatistics(): array
+    {
+        $statistics = [];
+        foreach ($this->data['instances']['calls'] as $name => $calls) {
+            $statistics[$name] = [
+                'calls' => 0,
+                'time' => 0,
+                'reads' => 0,
+                'writes' => 0,
+                'deletes' => 0,
+                'hits' => 0,
+                'misses' => 0,
+            ];
+            /** @var TraceableAdapterEvent $call */
+            foreach ($calls as $call) {
+                ++$statistics[$name]['calls'];
+                $statistics[$name]['time'] += $call->end - $call->start;
+                if ('get' === $call->name) {
+                    ++$statistics[$name]['reads'];
+                    if ($call->hits) {
+                        ++$statistics[$name]['hits'];
+                    } else {
+                        ++$statistics[$name]['misses'];
+                        ++$statistics[$name]['writes'];
+                    }
+                } elseif ('getItem' === $call->name) {
+                    ++$statistics[$name]['reads'];
+                    if ($call->hits) {
+                        ++$statistics[$name]['hits'];
+                    } else {
+                        ++$statistics[$name]['misses'];
+                    }
+                } elseif ('getItems' === $call->name) {
+                    $statistics[$name]['reads'] += $call->hits + $call->misses;
+                    $statistics[$name]['hits'] += $call->hits;
+                    $statistics[$name]['misses'] += $call->misses;
+                } elseif ('hasItem' === $call->name) {
+                    ++$statistics[$name]['reads'];
+                    if (false === $call->result) {
+                        ++$statistics[$name]['misses'];
+                    } else {
+                        ++$statistics[$name]['hits'];
+                    }
+                } elseif ('save' === $call->name) {
+                    ++$statistics[$name]['writes'];
+                } elseif ('deleteItem' === $call->name) {
+                    ++$statistics[$name]['deletes'];
+                }
+            }
+            if ($statistics[$name]['reads']) {
+                $statistics[$name]['hit_read_ratio'] = round(100 * $statistics[$name]['hits'] / $statistics[$name]['reads'], 2);
+            } else {
+                $statistics[$name]['hit_read_ratio'] = null;
+            }
+        }
+
+        return $statistics;
+    }
+
+    private function calculateTotalStatistics(): array
+    {
+        $statistics = $this->getStatistics();
+        $totals = [
+            'calls' => 0,
+            'time' => 0,
+            'reads' => 0,
+            'writes' => 0,
+            'deletes' => 0,
+            'hits' => 0,
+            'misses' => 0,
+        ];
+        foreach ($statistics as $name => $values) {
+            foreach ($totals as $key => $value) {
+                $totals[$key] += $statistics[$name][$key];
+            }
+        }
+        if ($totals['reads']) {
+            $totals['hit_read_ratio'] = round(100 * $totals['hits'] / $totals['reads'], 2);
+        } else {
+            $totals['hit_read_ratio'] = null;
+        }
+
+        return $totals;
+    }
+}
diff --git a/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php b/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php
new file mode 100644
index 0000000..6193d34
--- /dev/null
+++ b/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php
@@ -0,0 +1,72 @@
+<?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\Cache\DependencyInjection;
+
+use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
+use Symfony\Component\Cache\Adapter\TraceableAdapter;
+use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Inject a data collector to all the cache services to be able to get detailed statistics.
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+class CacheCollectorPass implements CompilerPassInterface
+{
+    private $dataCollectorCacheId;
+    private $cachePoolTag;
+    private $cachePoolRecorderInnerSuffix;
+
+    public function __construct(string $dataCollectorCacheId = 'data_collector.cache', string $cachePoolTag = 'cache.pool', string $cachePoolRecorderInnerSuffix = '.recorder_inner')
+    {
+        $this->dataCollectorCacheId = $dataCollectorCacheId;
+        $this->cachePoolTag = $cachePoolTag;
+        $this->cachePoolRecorderInnerSuffix = $cachePoolRecorderInnerSuffix;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function process(ContainerBuilder $container)
+    {
+        if (!$container->hasDefinition($this->dataCollectorCacheId)) {
+            return;
+        }
+
+        $collectorDefinition = $container->getDefinition($this->dataCollectorCacheId);
+        foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $attributes) {
+            $definition = $container->getDefinition($id);
+            if ($definition->isAbstract()) {
+                continue;
+            }
+
+            $recorder = new Definition(is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class);
+            $recorder->setTags($definition->getTags());
+            $recorder->setPublic($definition->isPublic());
+            $recorder->setArguments([new Reference($innerId = $id.$this->cachePoolRecorderInnerSuffix)]);
+
+            $definition->setTags([]);
+            $definition->setPublic(false);
+
+            $container->setDefinition($innerId, $definition);
+            $container->setDefinition($id, $recorder);
+
+            // Tell the collector to add the new instance
+            $collectorDefinition->addMethodCall('addInstance', [$id, new Reference($id)]);
+            $collectorDefinition->setPublic(false);
+        }
+    }
+}
diff --git a/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php b/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php
new file mode 100644
index 0000000..3ca89a3
--- /dev/null
+++ b/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php
@@ -0,0 +1,48 @@
+<?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\Cache\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class CachePoolClearerPass implements CompilerPassInterface
+{
+    private $cachePoolClearerTag;
+
+    public function __construct(string $cachePoolClearerTag = 'cache.pool.clearer')
+    {
+        $this->cachePoolClearerTag = $cachePoolClearerTag;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function process(ContainerBuilder $container)
+    {
+        $container->getParameterBag()->remove('cache.prefix.seed');
+
+        foreach ($container->findTaggedServiceIds($this->cachePoolClearerTag) as $id => $attr) {
+            $clearer = $container->getDefinition($id);
+            $pools = [];
+            foreach ($clearer->getArgument(0) as $name => $ref) {
+                if ($container->hasDefinition($ref)) {
+                    $pools[$name] = new Reference($ref);
+                }
+            }
+            $clearer->replaceArgument(0, $pools);
+        }
+    }
+}
diff --git a/vendor/symfony/cache/DependencyInjection/CachePoolPass.php b/vendor/symfony/cache/DependencyInjection/CachePoolPass.php
new file mode 100644
index 0000000..eef9e75
--- /dev/null
+++ b/vendor/symfony/cache/DependencyInjection/CachePoolPass.php
@@ -0,0 +1,178 @@
+<?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\Cache\DependencyInjection;
+
+use Symfony\Component\Cache\Adapter\AbstractAdapter;
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\DependencyInjection\ChildDefinition;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class CachePoolPass implements CompilerPassInterface
+{
+    private $cachePoolTag;
+    private $kernelResetTag;
+    private $cacheClearerId;
+    private $cachePoolClearerTag;
+    private $cacheSystemClearerId;
+    private $cacheSystemClearerTag;
+
+    public function __construct(string $cachePoolTag = 'cache.pool', string $kernelResetTag = 'kernel.reset', string $cacheClearerId = 'cache.global_clearer', string $cachePoolClearerTag = 'cache.pool.clearer', string $cacheSystemClearerId = 'cache.system_clearer', string $cacheSystemClearerTag = 'kernel.cache_clearer')
+    {
+        $this->cachePoolTag = $cachePoolTag;
+        $this->kernelResetTag = $kernelResetTag;
+        $this->cacheClearerId = $cacheClearerId;
+        $this->cachePoolClearerTag = $cachePoolClearerTag;
+        $this->cacheSystemClearerId = $cacheSystemClearerId;
+        $this->cacheSystemClearerTag = $cacheSystemClearerTag;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function process(ContainerBuilder $container)
+    {
+        if ($container->hasParameter('cache.prefix.seed')) {
+            $seed = '.'.$container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed'));
+        } else {
+            $seed = '_'.$container->getParameter('kernel.project_dir');
+        }
+        $seed .= '.'.$container->getParameter('kernel.container_class');
+
+        $pools = [];
+        $clearers = [];
+        $attributes = [
+            'provider',
+            'name',
+            'namespace',
+            'default_lifetime',
+            'reset',
+        ];
+        foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) {
+            $adapter = $pool = $container->getDefinition($id);
+            if ($pool->isAbstract()) {
+                continue;
+            }
+            $class = $adapter->getClass();
+            while ($adapter instanceof ChildDefinition) {
+                $adapter = $container->findDefinition($adapter->getParent());
+                $class = $class ?: $adapter->getClass();
+                if ($t = $adapter->getTag($this->cachePoolTag)) {
+                    $tags[0] += $t[0];
+                }
+            }
+            $name = $tags[0]['name'] ?? $id;
+            if (!isset($tags[0]['namespace'])) {
+                $namespaceSeed = $seed;
+                if (null !== $class) {
+                    $namespaceSeed .= '.'.$class;
+                }
+
+                $tags[0]['namespace'] = $this->getNamespace($namespaceSeed, $name);
+            }
+            if (isset($tags[0]['clearer'])) {
+                $clearer = $tags[0]['clearer'];
+                while ($container->hasAlias($clearer)) {
+                    $clearer = (string) $container->getAlias($clearer);
+                }
+            } else {
+                $clearer = null;
+            }
+            unset($tags[0]['clearer'], $tags[0]['name']);
+
+            if (isset($tags[0]['provider'])) {
+                $tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider']));
+            }
+            $i = 0;
+            foreach ($attributes as $attr) {
+                if (!isset($tags[0][$attr])) {
+                    // no-op
+                } elseif ('reset' === $attr) {
+                    if ($tags[0][$attr]) {
+                        $pool->addTag($this->kernelResetTag, ['method' => $tags[0][$attr]]);
+                    }
+                } elseif ('namespace' !== $attr || ArrayAdapter::class !== $adapter->getClass()) {
+                    $pool->replaceArgument($i++, $tags[0][$attr]);
+                }
+                unset($tags[0][$attr]);
+            }
+            if (!empty($tags[0])) {
+                throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime" and "reset", found "%s".', $this->cachePoolTag, $id, implode('", "', array_keys($tags[0]))));
+            }
+
+            if (null !== $clearer) {
+                $clearers[$clearer][$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
+            }
+
+            $pools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
+        }
+
+        $notAliasedCacheClearerId = $this->cacheClearerId;
+        while ($container->hasAlias($this->cacheClearerId)) {
+            $this->cacheClearerId = (string) $container->getAlias($this->cacheClearerId);
+        }
+        if ($container->hasDefinition($this->cacheClearerId)) {
+            $clearers[$notAliasedCacheClearerId] = $pools;
+        }
+
+        foreach ($clearers as $id => $pools) {
+            $clearer = $container->getDefinition($id);
+            if ($clearer instanceof ChildDefinition) {
+                $clearer->replaceArgument(0, $pools);
+            } else {
+                $clearer->setArgument(0, $pools);
+            }
+            $clearer->addTag($this->cachePoolClearerTag);
+
+            if ($this->cacheSystemClearerId === $id) {
+                $clearer->addTag($this->cacheSystemClearerTag);
+            }
+        }
+
+        if ($container->hasDefinition('console.command.cache_pool_list')) {
+            $container->getDefinition('console.command.cache_pool_list')->replaceArgument(0, array_keys($pools));
+        }
+    }
+
+    private function getNamespace($seed, $id)
+    {
+        return substr(str_replace('/', '-', base64_encode(hash('sha256', $id.$seed, true))), 0, 10);
+    }
+
+    /**
+     * @internal
+     */
+    public static function getServiceProvider(ContainerBuilder $container, $name)
+    {
+        $container->resolveEnvPlaceholders($name, null, $usedEnvs);
+
+        if ($usedEnvs || preg_match('#^[a-z]++:#', $name)) {
+            $dsn = $name;
+
+            if (!$container->hasDefinition($name = '.cache_connection.'.ContainerBuilder::hash($dsn))) {
+                $definition = new Definition(AbstractAdapter::class);
+                $definition->setPublic(false);
+                $definition->setFactory([AbstractAdapter::class, 'createConnection']);
+                $definition->setArguments([$dsn, ['lazy' => true]]);
+                $container->setDefinition($name, $definition);
+            }
+        }
+
+        return $name;
+    }
+}
diff --git a/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php b/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php
new file mode 100644
index 0000000..e569962
--- /dev/null
+++ b/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php
@@ -0,0 +1,60 @@
+<?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\Cache\DependencyInjection;
+
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * @author Rob Frawley 2nd <rmf@src.run>
+ */
+class CachePoolPrunerPass implements CompilerPassInterface
+{
+    private $cacheCommandServiceId;
+    private $cachePoolTag;
+
+    public function __construct(string $cacheCommandServiceId = 'console.command.cache_pool_prune', string $cachePoolTag = 'cache.pool')
+    {
+        $this->cacheCommandServiceId = $cacheCommandServiceId;
+        $this->cachePoolTag = $cachePoolTag;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function process(ContainerBuilder $container)
+    {
+        if (!$container->hasDefinition($this->cacheCommandServiceId)) {
+            return;
+        }
+
+        $services = [];
+
+        foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) {
+            $class = $container->getParameterBag()->resolveValue($container->getDefinition($id)->getClass());
+
+            if (!$reflection = $container->getReflectionClass($class)) {
+                throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
+            }
+
+            if ($reflection->implementsInterface(PruneableInterface::class)) {
+                $services[$id] = new Reference($id);
+            }
+        }
+
+        $container->getDefinition($this->cacheCommandServiceId)->replaceArgument(0, new IteratorArgument($services));
+    }
+}
diff --git a/vendor/symfony/cache/DoctrineProvider.php b/vendor/symfony/cache/DoctrineProvider.php
new file mode 100644
index 0000000..0c0d231
--- /dev/null
+++ b/vendor/symfony/cache/DoctrineProvider.php
@@ -0,0 +1,104 @@
+<?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\Cache;
+
+use Doctrine\Common\Cache\CacheProvider;
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class DoctrineProvider extends CacheProvider implements PruneableInterface, ResettableInterface
+{
+    private $pool;
+
+    public function __construct(CacheItemPoolInterface $pool)
+    {
+        $this->pool = $pool;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function prune()
+    {
+        return $this->pool instanceof PruneableInterface && $this->pool->prune();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function reset()
+    {
+        if ($this->pool instanceof ResetInterface) {
+            $this->pool->reset();
+        }
+        $this->setNamespace($this->getNamespace());
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doFetch($id)
+    {
+        $item = $this->pool->getItem(rawurlencode($id));
+
+        return $item->isHit() ? $item->get() : false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doContains($id)
+    {
+        return $this->pool->hasItem(rawurlencode($id));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doSave($id, $data, $lifeTime = 0)
+    {
+        $item = $this->pool->getItem(rawurlencode($id));
+
+        if (0 < $lifeTime) {
+            $item->expiresAfter($lifeTime);
+        }
+
+        return $this->pool->save($item->set($data));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDelete($id)
+    {
+        return $this->pool->deleteItem(rawurlencode($id));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doFlush()
+    {
+        return $this->pool->clear();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetStats()
+    {
+        return null;
+    }
+}
diff --git a/vendor/symfony/cache/Exception/CacheException.php b/vendor/symfony/cache/Exception/CacheException.php
new file mode 100644
index 0000000..d2e975b
--- /dev/null
+++ b/vendor/symfony/cache/Exception/CacheException.php
@@ -0,0 +1,25 @@
+<?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\Cache\Exception;
+
+use Psr\Cache\CacheException as Psr6CacheInterface;
+use Psr\SimpleCache\CacheException as SimpleCacheInterface;
+
+if (interface_exists(SimpleCacheInterface::class)) {
+    class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface
+    {
+    }
+} else {
+    class CacheException extends \Exception implements Psr6CacheInterface
+    {
+    }
+}
diff --git a/vendor/symfony/cache/Exception/InvalidArgumentException.php b/vendor/symfony/cache/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..7f9584a
--- /dev/null
+++ b/vendor/symfony/cache/Exception/InvalidArgumentException.php
@@ -0,0 +1,25 @@
+<?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\Cache\Exception;
+
+use Psr\Cache\InvalidArgumentException as Psr6CacheInterface;
+use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface;
+
+if (interface_exists(SimpleCacheInterface::class)) {
+    class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface
+    {
+    }
+} else {
+    class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface
+    {
+    }
+}
diff --git a/vendor/symfony/cache/Exception/LogicException.php b/vendor/symfony/cache/Exception/LogicException.php
new file mode 100644
index 0000000..9ffa7ed
--- /dev/null
+++ b/vendor/symfony/cache/Exception/LogicException.php
@@ -0,0 +1,25 @@
+<?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\Cache\Exception;
+
+use Psr\Cache\CacheException as Psr6CacheInterface;
+use Psr\SimpleCache\CacheException as SimpleCacheInterface;
+
+if (interface_exists(SimpleCacheInterface::class)) {
+    class LogicException extends \LogicException implements Psr6CacheInterface, SimpleCacheInterface
+    {
+    }
+} else {
+    class LogicException extends \LogicException implements Psr6CacheInterface
+    {
+    }
+}
diff --git a/vendor/symfony/cache/LICENSE b/vendor/symfony/cache/LICENSE
new file mode 100644
index 0000000..3c464ca
--- /dev/null
+++ b/vendor/symfony/cache/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2016-2019 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.
diff --git a/vendor/symfony/cache/LockRegistry.php b/vendor/symfony/cache/LockRegistry.php
new file mode 100644
index 0000000..9bb2bca
--- /dev/null
+++ b/vendor/symfony/cache/LockRegistry.php
@@ -0,0 +1,152 @@
+<?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\Cache;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Cache\ItemInterface;
+
+/**
+ * LockRegistry is used internally by existing adapters to protect against cache stampede.
+ *
+ * It does so by wrapping the computation of items in a pool of locks.
+ * Foreach each apps, there can be at most 20 concurrent processes that
+ * compute items at the same time and only one per cache-key.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+final class LockRegistry
+{
+    private static $openedFiles = [];
+    private static $lockedFiles = [];
+
+    /**
+     * The number of items in this list controls the max number of concurrent processes.
+     */
+    private static $files = [
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractTagAwareAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AdapterInterface.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ApcuAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ArrayAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ChainAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemTagAwareAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'MemcachedAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'NullAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PdoAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpArrayAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpFilesAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ProxyAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'Psr16Adapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisTagAwareAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'SimpleCacheAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapterInterface.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableAdapter.php',
+        __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableTagAwareAdapter.php',
+    ];
+
+    /**
+     * Defines a set of existing files that will be used as keys to acquire locks.
+     *
+     * @return array The previously defined set of files
+     */
+    public static function setFiles(array $files): array
+    {
+        $previousFiles = self::$files;
+        self::$files = $files;
+
+        foreach (self::$openedFiles as $file) {
+            if ($file) {
+                flock($file, LOCK_UN);
+                fclose($file);
+            }
+        }
+        self::$openedFiles = self::$lockedFiles = [];
+
+        return $previousFiles;
+    }
+
+    public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null)
+    {
+        $key = self::$files ? crc32($item->getKey()) % \count(self::$files) : -1;
+
+        if ($key < 0 || (self::$lockedFiles[$key] ?? false) || !$lock = self::open($key)) {
+            return $callback($item, $save);
+        }
+
+        while (true) {
+            try {
+                // race to get the lock in non-blocking mode
+                $locked = flock($lock, LOCK_EX | LOCK_NB, $wouldBlock);
+
+                if ($locked || !$wouldBlock) {
+                    $logger && $logger->info(sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]);
+                    self::$lockedFiles[$key] = true;
+
+                    $value = $callback($item, $save);
+
+                    if ($save) {
+                        if ($setMetadata) {
+                            $setMetadata($item);
+                        }
+
+                        $pool->save($item->set($value));
+                        $save = false;
+                    }
+
+                    return $value;
+                }
+                // if we failed the race, retry locking in blocking mode to wait for the winner
+                $logger && $logger->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]);
+                flock($lock, LOCK_SH);
+            } finally {
+                flock($lock, LOCK_UN);
+                unset(self::$lockedFiles[$key]);
+            }
+            static $signalingException, $signalingCallback;
+            $signalingException = $signalingException ?? unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}");
+            $signalingCallback = $signalingCallback ?? function () use ($signalingException) { throw $signalingException; };
+
+            try {
+                $value = $pool->get($item->getKey(), $signalingCallback, 0);
+                $logger && $logger->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]);
+                $save = false;
+
+                return $value;
+            } catch (\Exception $e) {
+                if ($signalingException !== $e) {
+                    throw $e;
+                }
+                $logger && $logger->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]);
+            }
+        }
+    }
+
+    private static function open(int $key)
+    {
+        if (null !== $h = self::$openedFiles[$key] ?? null) {
+            return $h;
+        }
+        set_error_handler(function () {});
+        try {
+            $h = fopen(self::$files[$key], 'r+');
+        } finally {
+            restore_error_handler();
+        }
+
+        return self::$openedFiles[$key] = $h ?: @fopen(self::$files[$key], 'r');
+    }
+}
diff --git a/vendor/symfony/cache/Marshaller/DefaultMarshaller.php b/vendor/symfony/cache/Marshaller/DefaultMarshaller.php
new file mode 100644
index 0000000..196fd62
--- /dev/null
+++ b/vendor/symfony/cache/Marshaller/DefaultMarshaller.php
@@ -0,0 +1,99 @@
+<?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\Cache\Marshaller;
+
+use Symfony\Component\Cache\Exception\CacheException;
+
+/**
+ * Serializes/unserializes values using igbinary_serialize() if available, serialize() otherwise.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class DefaultMarshaller implements MarshallerInterface
+{
+    private $useIgbinarySerialize = true;
+
+    public function __construct(bool $useIgbinarySerialize = null)
+    {
+        if (null === $useIgbinarySerialize) {
+            $useIgbinarySerialize = \extension_loaded('igbinary') && \PHP_VERSION_ID !== 70400;
+        } elseif ($useIgbinarySerialize && (!\extension_loaded('igbinary') || \PHP_VERSION_ID === 70400)) {
+            throw new CacheException('The "igbinary" PHP extension is not '.(\PHP_VERSION_ID === 70400 ? 'compatible with PHP 7.4.0.' : 'loaded.'));
+        }
+        $this->useIgbinarySerialize = $useIgbinarySerialize;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function marshall(array $values, ?array &$failed): array
+    {
+        $serialized = $failed = [];
+
+        foreach ($values as $id => $value) {
+            try {
+                if ($this->useIgbinarySerialize) {
+                    $serialized[$id] = igbinary_serialize($value);
+                } else {
+                    $serialized[$id] = serialize($value);
+                }
+            } catch (\Exception $e) {
+                $failed[] = $id;
+            }
+        }
+
+        return $serialized;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function unmarshall(string $value)
+    {
+        if ('b:0;' === $value) {
+            return false;
+        }
+        if ('N;' === $value) {
+            return null;
+        }
+        static $igbinaryNull;
+        if ($value === ($igbinaryNull ?? $igbinaryNull = \extension_loaded('igbinary') && \PHP_VERSION_ID !== 70400 ? igbinary_serialize(null) : false)) {
+            return null;
+        }
+        $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
+        try {
+            if (':' === ($value[1] ?? ':')) {
+                if (false !== $value = unserialize($value)) {
+                    return $value;
+                }
+            } elseif (false === $igbinaryNull) {
+                throw new \RuntimeException('Failed to unserialize values, did you forget to install the "igbinary" extension?');
+            } elseif (null !== $value = igbinary_unserialize($value)) {
+                return $value;
+            }
+
+            throw new \DomainException(error_get_last() ? error_get_last()['message'] : 'Failed to unserialize values.');
+        } catch (\Error $e) {
+            throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
+        } finally {
+            ini_set('unserialize_callback_func', $unserializeCallbackHandler);
+        }
+    }
+
+    /**
+     * @internal
+     */
+    public static function handleUnserializeCallback($class)
+    {
+        throw new \DomainException('Class not found: '.$class);
+    }
+}
diff --git a/vendor/symfony/cache/Marshaller/MarshallerInterface.php b/vendor/symfony/cache/Marshaller/MarshallerInterface.php
new file mode 100644
index 0000000..cdd6c40
--- /dev/null
+++ b/vendor/symfony/cache/Marshaller/MarshallerInterface.php
@@ -0,0 +1,40 @@
+<?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\Cache\Marshaller;
+
+/**
+ * Serializes/unserializes PHP values.
+ *
+ * Implementations of this interface MUST deal with errors carefully. They MUST
+ * also deal with forward and backward compatibility at the storage format level.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+interface MarshallerInterface
+{
+    /**
+     * Serializes a list of values.
+     *
+     * When serialization fails for a specific value, no exception should be
+     * thrown. Instead, its key should be listed in $failed.
+     */
+    public function marshall(array $values, ?array &$failed): array;
+
+    /**
+     * Unserializes a single value and throws an exception if anything goes wrong.
+     *
+     * @return mixed
+     *
+     * @throws \Exception Whenever unserialization fails
+     */
+    public function unmarshall(string $value);
+}
diff --git a/vendor/symfony/cache/PruneableInterface.php b/vendor/symfony/cache/PruneableInterface.php
new file mode 100644
index 0000000..4261525
--- /dev/null
+++ b/vendor/symfony/cache/PruneableInterface.php
@@ -0,0 +1,23 @@
+<?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\Cache;
+
+/**
+ * Interface extends psr-6 and psr-16 caches to allow for pruning (deletion) of all expired cache items.
+ */
+interface PruneableInterface
+{
+    /**
+     * @return bool
+     */
+    public function prune();
+}
diff --git a/vendor/symfony/cache/Psr16Cache.php b/vendor/symfony/cache/Psr16Cache.php
new file mode 100644
index 0000000..99111a4
--- /dev/null
+++ b/vendor/symfony/cache/Psr16Cache.php
@@ -0,0 +1,263 @@
+<?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\Cache;
+
+use Psr\Cache\CacheException as Psr6CacheException;
+use Psr\Cache\CacheItemPoolInterface;
+use Psr\SimpleCache\CacheException as SimpleCacheException;
+use Psr\SimpleCache\CacheInterface;
+use Symfony\Component\Cache\Adapter\AdapterInterface;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Traits\ProxyTrait;
+
+/**
+ * Turns a PSR-6 cache into a PSR-16 one.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterface
+{
+    use ProxyTrait;
+
+    private const METADATA_EXPIRY_OFFSET = 1527506807;
+
+    private $createCacheItem;
+    private $cacheItemPrototype;
+
+    public function __construct(CacheItemPoolInterface $pool)
+    {
+        $this->pool = $pool;
+
+        if (!$pool instanceof AdapterInterface) {
+            return;
+        }
+        $cacheItemPrototype = &$this->cacheItemPrototype;
+        $createCacheItem = \Closure::bind(
+            static function ($key, $value, $allowInt = false) use (&$cacheItemPrototype) {
+                $item = clone $cacheItemPrototype;
+                $item->key = $allowInt && \is_int($key) ? (string) $key : CacheItem::validateKey($key);
+                $item->value = $value;
+                $item->isHit = false;
+
+                return $item;
+            },
+            null,
+            CacheItem::class
+        );
+        $this->createCacheItem = function ($key, $value, $allowInt = false) use ($createCacheItem) {
+            if (null === $this->cacheItemPrototype) {
+                $this->get($allowInt && \is_int($key) ? (string) $key : $key);
+            }
+            $this->createCacheItem = $createCacheItem;
+
+            return $createCacheItem($key, null, $allowInt)->set($value);
+        };
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get($key, $default = null)
+    {
+        try {
+            $item = $this->pool->getItem($key);
+        } catch (SimpleCacheException $e) {
+            throw $e;
+        } catch (Psr6CacheException $e) {
+            throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+        }
+        if (null === $this->cacheItemPrototype) {
+            $this->cacheItemPrototype = clone $item;
+            $this->cacheItemPrototype->set(null);
+        }
+
+        return $item->isHit() ? $item->get() : $default;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function set($key, $value, $ttl = null)
+    {
+        try {
+            if (null !== $f = $this->createCacheItem) {
+                $item = $f($key, $value);
+            } else {
+                $item = $this->pool->getItem($key)->set($value);
+            }
+        } catch (SimpleCacheException $e) {
+            throw $e;
+        } catch (Psr6CacheException $e) {
+            throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+        }
+        if (null !== $ttl) {
+            $item->expiresAfter($ttl);
+        }
+
+        return $this->pool->save($item);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function delete($key)
+    {
+        try {
+            return $this->pool->deleteItem($key);
+        } catch (SimpleCacheException $e) {
+            throw $e;
+        } catch (Psr6CacheException $e) {
+            throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        return $this->pool->clear();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMultiple($keys, $default = null)
+    {
+        if ($keys instanceof \Traversable) {
+            $keys = iterator_to_array($keys, false);
+        } elseif (!\is_array($keys)) {
+            throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+        }
+
+        try {
+            $items = $this->pool->getItems($keys);
+        } catch (SimpleCacheException $e) {
+            throw $e;
+        } catch (Psr6CacheException $e) {
+            throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+        }
+        $values = [];
+
+        if (!$this->pool instanceof AdapterInterface) {
+            foreach ($items as $key => $item) {
+                $values[$key] = $item->isHit() ? $item->get() : $default;
+            }
+
+            return $values;
+        }
+
+        foreach ($items as $key => $item) {
+            if (!$item->isHit()) {
+                $values[$key] = $default;
+                continue;
+            }
+            $values[$key] = $item->get();
+
+            if (!$metadata = $item->getMetadata()) {
+                continue;
+            }
+            unset($metadata[CacheItem::METADATA_TAGS]);
+
+            if ($metadata) {
+                $values[$key] = ["\x9D".pack('VN', (int) (0.1 + $metadata[CacheItem::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[CacheItem::METADATA_CTIME])."\x5F" => $values[$key]];
+            }
+        }
+
+        return $values;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setMultiple($values, $ttl = null)
+    {
+        $valuesIsArray = \is_array($values);
+        if (!$valuesIsArray && !$values instanceof \Traversable) {
+            throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
+        }
+        $items = [];
+
+        try {
+            if (null !== $f = $this->createCacheItem) {
+                $valuesIsArray = false;
+                foreach ($values as $key => $value) {
+                    $items[$key] = $f($key, $value, true);
+                }
+            } elseif ($valuesIsArray) {
+                $items = [];
+                foreach ($values as $key => $value) {
+                    $items[] = (string) $key;
+                }
+                $items = $this->pool->getItems($items);
+            } else {
+                foreach ($values as $key => $value) {
+                    if (\is_int($key)) {
+                        $key = (string) $key;
+                    }
+                    $items[$key] = $this->pool->getItem($key)->set($value);
+                }
+            }
+        } catch (SimpleCacheException $e) {
+            throw $e;
+        } catch (Psr6CacheException $e) {
+            throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+        }
+        $ok = true;
+
+        foreach ($items as $key => $item) {
+            if ($valuesIsArray) {
+                $item->set($values[$key]);
+            }
+            if (null !== $ttl) {
+                $item->expiresAfter($ttl);
+            }
+            $ok = $this->pool->saveDeferred($item) && $ok;
+        }
+
+        return $this->pool->commit() && $ok;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteMultiple($keys)
+    {
+        if ($keys instanceof \Traversable) {
+            $keys = iterator_to_array($keys, false);
+        } elseif (!\is_array($keys)) {
+            throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+        }
+
+        try {
+            return $this->pool->deleteItems($keys);
+        } catch (SimpleCacheException $e) {
+            throw $e;
+        } catch (Psr6CacheException $e) {
+            throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function has($key)
+    {
+        try {
+            return $this->pool->hasItem($key);
+        } catch (SimpleCacheException $e) {
+            throw $e;
+        } catch (Psr6CacheException $e) {
+            throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+        }
+    }
+}
diff --git a/vendor/symfony/cache/README.md b/vendor/symfony/cache/README.md
new file mode 100644
index 0000000..c4ab752
--- /dev/null
+++ b/vendor/symfony/cache/README.md
@@ -0,0 +1,18 @@
+Symfony PSR-6 implementation for caching
+========================================
+
+This component provides an extended [PSR-6](http://www.php-fig.org/psr/psr-6/)
+implementation for adding cache to your applications. It is designed to have a
+low overhead so that caching is fastest. It ships with a few caching adapters
+for the most widespread and suited to caching backends. It also provides a
+`doctrine/cache` proxy adapter to cover more advanced caching needs and a proxy
+adapter for greater interoperability between PSR-6 implementations.
+
+Resources
+---------
+
+  * [Documentation](https://symfony.com/doc/current/components/cache.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)
diff --git a/vendor/symfony/cache/ResettableInterface.php b/vendor/symfony/cache/ResettableInterface.php
new file mode 100644
index 0000000..7b0a853
--- /dev/null
+++ b/vendor/symfony/cache/ResettableInterface.php
@@ -0,0 +1,21 @@
+<?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\Cache;
+
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * Resets a pool's local state.
+ */
+interface ResettableInterface extends ResetInterface
+{
+}
diff --git a/vendor/symfony/cache/Simple/AbstractCache.php b/vendor/symfony/cache/Simple/AbstractCache.php
new file mode 100644
index 0000000..af46bf3
--- /dev/null
+++ b/vendor/symfony/cache/Simple/AbstractCache.php
@@ -0,0 +1,191 @@
+<?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\Cache\Simple;
+
+use Psr\Log\LoggerAwareInterface;
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\AbstractAdapter;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\AbstractTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', AbstractCache::class, AbstractAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use AbstractAdapter and type-hint for CacheInterface instead.
+ */
+abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface
+{
+    /**
+     * @internal
+     */
+    protected const NS_SEPARATOR = ':';
+
+    use AbstractTrait {
+        deleteItems as private;
+        AbstractTrait::deleteItem as delete;
+        AbstractTrait::hasItem as has;
+    }
+
+    private $defaultLifetime;
+
+    protected function __construct(string $namespace = '', int $defaultLifetime = 0)
+    {
+        $this->defaultLifetime = max(0, $defaultLifetime);
+        $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
+        if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
+            throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get($key, $default = null)
+    {
+        $id = $this->getId($key);
+
+        try {
+            foreach ($this->doFetch([$id]) as $value) {
+                return $value;
+            }
+        } catch (\Exception $e) {
+            CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
+        }
+
+        return $default;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function set($key, $value, $ttl = null)
+    {
+        CacheItem::validateKey($key);
+
+        return $this->setMultiple([$key => $value], $ttl);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMultiple($keys, $default = null)
+    {
+        if ($keys instanceof \Traversable) {
+            $keys = iterator_to_array($keys, false);
+        } elseif (!\is_array($keys)) {
+            throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+        }
+        $ids = [];
+
+        foreach ($keys as $key) {
+            $ids[] = $this->getId($key);
+        }
+        try {
+            $values = $this->doFetch($ids);
+        } catch (\Exception $e) {
+            CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e]);
+            $values = [];
+        }
+        $ids = array_combine($ids, $keys);
+
+        return $this->generateValues($values, $ids, $default);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setMultiple($values, $ttl = null)
+    {
+        if (!\is_array($values) && !$values instanceof \Traversable) {
+            throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
+        }
+        $valuesById = [];
+
+        foreach ($values as $key => $value) {
+            if (\is_int($key)) {
+                $key = (string) $key;
+            }
+            $valuesById[$this->getId($key)] = $value;
+        }
+        if (false === $ttl = $this->normalizeTtl($ttl)) {
+            return $this->doDelete(array_keys($valuesById));
+        }
+
+        try {
+            $e = $this->doSave($valuesById, $ttl);
+        } catch (\Exception $e) {
+        }
+        if (true === $e || [] === $e) {
+            return true;
+        }
+        $keys = [];
+        foreach (\is_array($e) ? $e : array_keys($valuesById) as $id) {
+            $keys[] = substr($id, \strlen($this->namespace));
+        }
+        $message = 'Failed to save values'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
+        CacheItem::log($this->logger, $message, ['keys' => $keys, 'exception' => $e instanceof \Exception ? $e : null]);
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteMultiple($keys)
+    {
+        if ($keys instanceof \Traversable) {
+            $keys = iterator_to_array($keys, false);
+        } elseif (!\is_array($keys)) {
+            throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+        }
+
+        return $this->deleteItems($keys);
+    }
+
+    private function normalizeTtl($ttl)
+    {
+        if (null === $ttl) {
+            return $this->defaultLifetime;
+        }
+        if ($ttl instanceof \DateInterval) {
+            $ttl = (int) \DateTime::createFromFormat('U', 0)->add($ttl)->format('U');
+        }
+        if (\is_int($ttl)) {
+            return 0 < $ttl ? $ttl : false;
+        }
+
+        throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)));
+    }
+
+    private function generateValues($values, &$keys, $default)
+    {
+        try {
+            foreach ($values as $id => $value) {
+                if (!isset($keys[$id])) {
+                    $id = key($keys);
+                }
+                $key = $keys[$id];
+                unset($keys[$id]);
+                yield $key => $value;
+            }
+        } catch (\Exception $e) {
+            CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e]);
+        }
+
+        foreach ($keys as $key) {
+            yield $key => $default;
+        }
+    }
+}
diff --git a/vendor/symfony/cache/Simple/ApcuCache.php b/vendor/symfony/cache/Simple/ApcuCache.php
new file mode 100644
index 0000000..c22771e
--- /dev/null
+++ b/vendor/symfony/cache/Simple/ApcuCache.php
@@ -0,0 +1,29 @@
+<?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\Cache\Simple;
+
+use Symfony\Component\Cache\Traits\ApcuTrait;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ApcuCache::class, ApcuAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use ApcuAdapter and type-hint for CacheInterface instead.
+ */
+class ApcuCache extends AbstractCache
+{
+    use ApcuTrait;
+
+    public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null)
+    {
+        $this->init($namespace, $defaultLifetime, $version);
+    }
+}
diff --git a/vendor/symfony/cache/Simple/ArrayCache.php b/vendor/symfony/cache/Simple/ArrayCache.php
new file mode 100644
index 0000000..5cd228f
--- /dev/null
+++ b/vendor/symfony/cache/Simple/ArrayCache.php
@@ -0,0 +1,159 @@
+<?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\Cache\Simple;
+
+use Psr\Log\LoggerAwareInterface;
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ArrayTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ArrayCache::class, ArrayAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use ArrayAdapter and type-hint for CacheInterface instead.
+ */
+class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface
+{
+    use ArrayTrait {
+        ArrayTrait::deleteItem as delete;
+        ArrayTrait::hasItem as has;
+    }
+
+    private $defaultLifetime;
+
+    /**
+     * @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
+     */
+    public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true)
+    {
+        $this->defaultLifetime = $defaultLifetime;
+        $this->storeSerialized = $storeSerialized;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get($key, $default = null)
+    {
+        if (!\is_string($key) || !isset($this->expiries[$key])) {
+            CacheItem::validateKey($key);
+        }
+        if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > microtime(true) || !$this->delete($key))) {
+            $this->values[$key] = null;
+
+            return $default;
+        }
+        if (!$this->storeSerialized) {
+            return $this->values[$key];
+        }
+        $value = $this->unfreeze($key, $isHit);
+
+        return $isHit ? $value : $default;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMultiple($keys, $default = null)
+    {
+        if ($keys instanceof \Traversable) {
+            $keys = iterator_to_array($keys, false);
+        } elseif (!\is_array($keys)) {
+            throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+        }
+        foreach ($keys as $key) {
+            if (!\is_string($key) || !isset($this->expiries[$key])) {
+                CacheItem::validateKey($key);
+            }
+        }
+
+        return $this->generateItems($keys, microtime(true), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; });
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteMultiple($keys)
+    {
+        if (!\is_array($keys) && !$keys instanceof \Traversable) {
+            throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+        }
+        foreach ($keys as $key) {
+            $this->delete($key);
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function set($key, $value, $ttl = null)
+    {
+        if (!\is_string($key)) {
+            CacheItem::validateKey($key);
+        }
+
+        return $this->setMultiple([$key => $value], $ttl);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setMultiple($values, $ttl = null)
+    {
+        if (!\is_array($values) && !$values instanceof \Traversable) {
+            throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
+        }
+        $valuesArray = [];
+
+        foreach ($values as $key => $value) {
+            if (!\is_int($key) && !(\is_string($key) && isset($this->expiries[$key]))) {
+                CacheItem::validateKey($key);
+            }
+            $valuesArray[$key] = $value;
+        }
+        if (false === $ttl = $this->normalizeTtl($ttl)) {
+            return $this->deleteMultiple(array_keys($valuesArray));
+        }
+        $expiry = 0 < $ttl ? microtime(true) + $ttl : PHP_INT_MAX;
+
+        foreach ($valuesArray as $key => $value) {
+            if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
+                return false;
+            }
+            $this->values[$key] = $value;
+            $this->expiries[$key] = $expiry;
+        }
+
+        return true;
+    }
+
+    private function normalizeTtl($ttl)
+    {
+        if (null === $ttl) {
+            return $this->defaultLifetime;
+        }
+        if ($ttl instanceof \DateInterval) {
+            $ttl = (int) \DateTime::createFromFormat('U', 0)->add($ttl)->format('U');
+        }
+        if (\is_int($ttl)) {
+            return 0 < $ttl ? $ttl : false;
+        }
+
+        throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)));
+    }
+}
diff --git a/vendor/symfony/cache/Simple/ChainCache.php b/vendor/symfony/cache/Simple/ChainCache.php
new file mode 100644
index 0000000..a0122fd
--- /dev/null
+++ b/vendor/symfony/cache/Simple/ChainCache.php
@@ -0,0 +1,257 @@
+<?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\Cache\Simple;
+
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\ChainAdapter;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ChainCache::class, ChainAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * Chains several caches together.
+ *
+ * Cached items are fetched from the first cache having them in its data store.
+ * They are saved and deleted in all caches at once.
+ *
+ * @deprecated since Symfony 4.3, use ChainAdapter and type-hint for CacheInterface instead.
+ */
+class ChainCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
+{
+    private $miss;
+    private $caches = [];
+    private $defaultLifetime;
+    private $cacheCount;
+
+    /**
+     * @param Psr16CacheInterface[] $caches          The ordered list of caches used to fetch cached items
+     * @param int                   $defaultLifetime The lifetime of items propagated from lower caches to upper ones
+     */
+    public function __construct(array $caches, int $defaultLifetime = 0)
+    {
+        if (!$caches) {
+            throw new InvalidArgumentException('At least one cache must be specified.');
+        }
+
+        foreach ($caches as $cache) {
+            if (!$cache instanceof Psr16CacheInterface) {
+                throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($cache), Psr16CacheInterface::class));
+            }
+        }
+
+        $this->miss = new \stdClass();
+        $this->caches = array_values($caches);
+        $this->cacheCount = \count($this->caches);
+        $this->defaultLifetime = 0 < $defaultLifetime ? $defaultLifetime : null;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get($key, $default = null)
+    {
+        $miss = null !== $default && \is_object($default) ? $default : $this->miss;
+
+        foreach ($this->caches as $i => $cache) {
+            $value = $cache->get($key, $miss);
+
+            if ($miss !== $value) {
+                while (0 <= --$i) {
+                    $this->caches[$i]->set($key, $value, $this->defaultLifetime);
+                }
+
+                return $value;
+            }
+        }
+
+        return $default;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMultiple($keys, $default = null)
+    {
+        $miss = null !== $default && \is_object($default) ? $default : $this->miss;
+
+        return $this->generateItems($this->caches[0]->getMultiple($keys, $miss), 0, $miss, $default);
+    }
+
+    private function generateItems($values, $cacheIndex, $miss, $default)
+    {
+        $missing = [];
+        $nextCacheIndex = $cacheIndex + 1;
+        $nextCache = isset($this->caches[$nextCacheIndex]) ? $this->caches[$nextCacheIndex] : null;
+
+        foreach ($values as $k => $value) {
+            if ($miss !== $value) {
+                yield $k => $value;
+            } elseif (!$nextCache) {
+                yield $k => $default;
+            } else {
+                $missing[] = $k;
+            }
+        }
+
+        if ($missing) {
+            $cache = $this->caches[$cacheIndex];
+            $values = $this->generateItems($nextCache->getMultiple($missing, $miss), $nextCacheIndex, $miss, $default);
+
+            foreach ($values as $k => $value) {
+                if ($miss !== $value) {
+                    $cache->set($k, $value, $this->defaultLifetime);
+                    yield $k => $value;
+                } else {
+                    yield $k => $default;
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function has($key)
+    {
+        foreach ($this->caches as $cache) {
+            if ($cache->has($key)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        $cleared = true;
+        $i = $this->cacheCount;
+
+        while ($i--) {
+            $cleared = $this->caches[$i]->clear() && $cleared;
+        }
+
+        return $cleared;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function delete($key)
+    {
+        $deleted = true;
+        $i = $this->cacheCount;
+
+        while ($i--) {
+            $deleted = $this->caches[$i]->delete($key) && $deleted;
+        }
+
+        return $deleted;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteMultiple($keys)
+    {
+        if ($keys instanceof \Traversable) {
+            $keys = iterator_to_array($keys, false);
+        }
+        $deleted = true;
+        $i = $this->cacheCount;
+
+        while ($i--) {
+            $deleted = $this->caches[$i]->deleteMultiple($keys) && $deleted;
+        }
+
+        return $deleted;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function set($key, $value, $ttl = null)
+    {
+        $saved = true;
+        $i = $this->cacheCount;
+
+        while ($i--) {
+            $saved = $this->caches[$i]->set($key, $value, $ttl) && $saved;
+        }
+
+        return $saved;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setMultiple($values, $ttl = null)
+    {
+        if ($values instanceof \Traversable) {
+            $valuesIterator = $values;
+            $values = function () use ($valuesIterator, &$values) {
+                $generatedValues = [];
+
+                foreach ($valuesIterator as $key => $value) {
+                    yield $key => $value;
+                    $generatedValues[$key] = $value;
+                }
+
+                $values = $generatedValues;
+            };
+            $values = $values();
+        }
+        $saved = true;
+        $i = $this->cacheCount;
+
+        while ($i--) {
+            $saved = $this->caches[$i]->setMultiple($values, $ttl) && $saved;
+        }
+
+        return $saved;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function prune()
+    {
+        $pruned = true;
+
+        foreach ($this->caches as $cache) {
+            if ($cache instanceof PruneableInterface) {
+                $pruned = $cache->prune() && $pruned;
+            }
+        }
+
+        return $pruned;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function reset()
+    {
+        foreach ($this->caches as $cache) {
+            if ($cache instanceof ResetInterface) {
+                $cache->reset();
+            }
+        }
+    }
+}
diff --git a/vendor/symfony/cache/Simple/DoctrineCache.php b/vendor/symfony/cache/Simple/DoctrineCache.php
new file mode 100644
index 0000000..6a6d003
--- /dev/null
+++ b/vendor/symfony/cache/Simple/DoctrineCache.php
@@ -0,0 +1,34 @@
+<?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\Cache\Simple;
+
+use Doctrine\Common\Cache\CacheProvider;
+use Symfony\Component\Cache\Adapter\DoctrineAdapter;
+use Symfony\Component\Cache\Traits\DoctrineTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', DoctrineCache::class, DoctrineAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use DoctrineAdapter and type-hint for CacheInterface instead.
+ */
+class DoctrineCache extends AbstractCache
+{
+    use DoctrineTrait;
+
+    public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0)
+    {
+        parent::__construct('', $defaultLifetime);
+        $this->provider = $provider;
+        $provider->setNamespace($namespace);
+    }
+}
diff --git a/vendor/symfony/cache/Simple/FilesystemCache.php b/vendor/symfony/cache/Simple/FilesystemCache.php
new file mode 100644
index 0000000..8891abd
--- /dev/null
+++ b/vendor/symfony/cache/Simple/FilesystemCache.php
@@ -0,0 +1,36 @@
+<?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\Cache\Simple;
+
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\FilesystemTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', FilesystemCache::class, FilesystemAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use FilesystemAdapter and type-hint for CacheInterface instead.
+ */
+class FilesystemCache extends AbstractCache implements PruneableInterface
+{
+    use FilesystemTrait;
+
+    public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
+    {
+        $this->marshaller = $marshaller ?? new DefaultMarshaller();
+        parent::__construct('', $defaultLifetime);
+        $this->init($namespace, $directory);
+    }
+}
diff --git a/vendor/symfony/cache/Simple/MemcachedCache.php b/vendor/symfony/cache/Simple/MemcachedCache.php
new file mode 100644
index 0000000..e193411
--- /dev/null
+++ b/vendor/symfony/cache/Simple/MemcachedCache.php
@@ -0,0 +1,34 @@
+<?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\Cache\Simple;
+
+use Symfony\Component\Cache\Adapter\MemcachedAdapter;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Traits\MemcachedTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', MemcachedCache::class, MemcachedAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use MemcachedAdapter and type-hint for CacheInterface instead.
+ */
+class MemcachedCache extends AbstractCache
+{
+    use MemcachedTrait;
+
+    protected $maxIdLength = 250;
+
+    public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+    {
+        $this->init($client, $namespace, $defaultLifetime, $marshaller);
+    }
+}
diff --git a/vendor/symfony/cache/Simple/NullCache.php b/vendor/symfony/cache/Simple/NullCache.php
new file mode 100644
index 0000000..c4760e1
--- /dev/null
+++ b/vendor/symfony/cache/Simple/NullCache.php
@@ -0,0 +1,90 @@
+<?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\Cache\Simple;
+
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\NullAdapter;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', NullCache::class, NullAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use NullAdapter and type-hint for CacheInterface instead.
+ */
+class NullCache implements Psr16CacheInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function get($key, $default = null)
+    {
+        return $default;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMultiple($keys, $default = null)
+    {
+        foreach ($keys as $key) {
+            yield $key => $default;
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function has($key)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function delete($key)
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteMultiple($keys)
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function set($key, $value, $ttl = null)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setMultiple($values, $ttl = null)
+    {
+        return false;
+    }
+}
diff --git a/vendor/symfony/cache/Simple/PdoCache.php b/vendor/symfony/cache/Simple/PdoCache.php
new file mode 100644
index 0000000..07849e9
--- /dev/null
+++ b/vendor/symfony/cache/Simple/PdoCache.php
@@ -0,0 +1,59 @@
+<?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\Cache\Simple;
+
+use Symfony\Component\Cache\Adapter\PdoAdapter;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\PdoTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PdoCache::class, PdoAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use PdoAdapter and type-hint for CacheInterface instead.
+ */
+class PdoCache extends AbstractCache implements PruneableInterface
+{
+    use PdoTrait;
+
+    protected $maxIdLength = 255;
+
+    /**
+     * You can either pass an existing database connection as PDO instance or
+     * a Doctrine DBAL Connection or a DSN string that will be used to
+     * lazy-connect to the database when the cache is actually used.
+     *
+     * When a Doctrine DBAL Connection is passed, the cache table is created
+     * automatically when possible. Otherwise, use the createTable() method.
+     *
+     * List of available options:
+     *  * db_table: The name of the table [default: cache_items]
+     *  * db_id_col: The column where to store the cache id [default: item_id]
+     *  * db_data_col: The column where to store the cache data [default: item_data]
+     *  * db_lifetime_col: The column where to store the lifetime [default: item_lifetime]
+     *  * db_time_col: The column where to store the timestamp [default: item_time]
+     *  * db_username: The username when lazy-connect [default: '']
+     *  * db_password: The password when lazy-connect [default: '']
+     *  * db_connection_options: An array of driver-specific connection options [default: []]
+     *
+     * @param \PDO|Connection|string $connOrDsn a \PDO or Connection instance or DSN string or null
+     *
+     * @throws InvalidArgumentException When first argument is not PDO nor Connection nor string
+     * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
+     * @throws InvalidArgumentException When namespace contains invalid characters
+     */
+    public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
+    {
+        $this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
+    }
+}
diff --git a/vendor/symfony/cache/Simple/PhpArrayCache.php b/vendor/symfony/cache/Simple/PhpArrayCache.php
new file mode 100644
index 0000000..5666093
--- /dev/null
+++ b/vendor/symfony/cache/Simple/PhpArrayCache.php
@@ -0,0 +1,248 @@
+<?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\Cache\Simple;
+
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\PhpArrayTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PhpArrayCache::class, PhpArrayAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use PhpArrayAdapter and type-hint for CacheInterface instead.
+ */
+class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
+{
+    use PhpArrayTrait;
+
+    /**
+     * @param string              $file         The PHP file were values are cached
+     * @param Psr16CacheInterface $fallbackPool A pool to fallback on when an item is not hit
+     */
+    public function __construct(string $file, Psr16CacheInterface $fallbackPool)
+    {
+        $this->file = $file;
+        $this->pool = $fallbackPool;
+    }
+
+    /**
+     * This adapter takes advantage of how PHP stores arrays in its latest versions.
+     *
+     * @param string $file The PHP file were values are cached
+     *
+     * @return Psr16CacheInterface
+     */
+    public static function create($file, Psr16CacheInterface $fallbackPool)
+    {
+        // Shared memory is available in PHP 7.0+ with OPCache enabled
+        if (filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) {
+            return new static($file, $fallbackPool);
+        }
+
+        return $fallbackPool;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get($key, $default = null)
+    {
+        if (!\is_string($key)) {
+            throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+        }
+        if (null === $this->values) {
+            $this->initialize();
+        }
+        if (!isset($this->keys[$key])) {
+            return $this->pool->get($key, $default);
+        }
+        $value = $this->values[$this->keys[$key]];
+
+        if ('N;' === $value) {
+            return null;
+        }
+        if ($value instanceof \Closure) {
+            try {
+                return $value();
+            } catch (\Throwable $e) {
+                return $default;
+            }
+        }
+
+        return $value;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMultiple($keys, $default = null)
+    {
+        if ($keys instanceof \Traversable) {
+            $keys = iterator_to_array($keys, false);
+        } elseif (!\is_array($keys)) {
+            throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+        }
+        foreach ($keys as $key) {
+            if (!\is_string($key)) {
+                throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+            }
+        }
+        if (null === $this->values) {
+            $this->initialize();
+        }
+
+        return $this->generateItems($keys, $default);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function has($key)
+    {
+        if (!\is_string($key)) {
+            throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+        }
+        if (null === $this->values) {
+            $this->initialize();
+        }
+
+        return isset($this->keys[$key]) || $this->pool->has($key);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function delete($key)
+    {
+        if (!\is_string($key)) {
+            throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+        }
+        if (null === $this->values) {
+            $this->initialize();
+        }
+
+        return !isset($this->keys[$key]) && $this->pool->delete($key);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteMultiple($keys)
+    {
+        if (!\is_array($keys) && !$keys instanceof \Traversable) {
+            throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+        }
+
+        $deleted = true;
+        $fallbackKeys = [];
+
+        foreach ($keys as $key) {
+            if (!\is_string($key)) {
+                throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+            }
+
+            if (isset($this->keys[$key])) {
+                $deleted = false;
+            } else {
+                $fallbackKeys[] = $key;
+            }
+        }
+        if (null === $this->values) {
+            $this->initialize();
+        }
+
+        if ($fallbackKeys) {
+            $deleted = $this->pool->deleteMultiple($fallbackKeys) && $deleted;
+        }
+
+        return $deleted;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function set($key, $value, $ttl = null)
+    {
+        if (!\is_string($key)) {
+            throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+        }
+        if (null === $this->values) {
+            $this->initialize();
+        }
+
+        return !isset($this->keys[$key]) && $this->pool->set($key, $value, $ttl);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setMultiple($values, $ttl = null)
+    {
+        if (!\is_array($values) && !$values instanceof \Traversable) {
+            throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
+        }
+
+        $saved = true;
+        $fallbackValues = [];
+
+        foreach ($values as $key => $value) {
+            if (!\is_string($key) && !\is_int($key)) {
+                throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+            }
+
+            if (isset($this->keys[$key])) {
+                $saved = false;
+            } else {
+                $fallbackValues[$key] = $value;
+            }
+        }
+
+        if ($fallbackValues) {
+            $saved = $this->pool->setMultiple($fallbackValues, $ttl) && $saved;
+        }
+
+        return $saved;
+    }
+
+    private function generateItems(array $keys, $default)
+    {
+        $fallbackKeys = [];
+
+        foreach ($keys as $key) {
+            if (isset($this->keys[$key])) {
+                $value = $this->values[$this->keys[$key]];
+
+                if ('N;' === $value) {
+                    yield $key => null;
+                } elseif ($value instanceof \Closure) {
+                    try {
+                        yield $key => $value();
+                    } catch (\Throwable $e) {
+                        yield $key => $default;
+                    }
+                } else {
+                    yield $key => $value;
+                }
+            } else {
+                $fallbackKeys[] = $key;
+            }
+        }
+
+        if ($fallbackKeys) {
+            yield from $this->pool->getMultiple($fallbackKeys, $default);
+        }
+    }
+}
diff --git a/vendor/symfony/cache/Simple/PhpFilesCache.php b/vendor/symfony/cache/Simple/PhpFilesCache.php
new file mode 100644
index 0000000..060244a
--- /dev/null
+++ b/vendor/symfony/cache/Simple/PhpFilesCache.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\Cache\Simple;
+
+use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
+use Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\PhpFilesTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PhpFilesCache::class, PhpFilesAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use PhpFilesAdapter and type-hint for CacheInterface instead.
+ */
+class PhpFilesCache extends AbstractCache implements PruneableInterface
+{
+    use PhpFilesTrait;
+
+    /**
+     * @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire.
+     *                    Doing so is encouraged because it fits perfectly OPcache's memory model.
+     *
+     * @throws CacheException if OPcache is not enabled
+     */
+    public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
+    {
+        $this->appendOnly = $appendOnly;
+        self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
+        parent::__construct('', $defaultLifetime);
+        $this->init($namespace, $directory);
+        $this->includeHandler = static function ($type, $msg, $file, $line) {
+            throw new \ErrorException($msg, 0, $type, $file, $line);
+        };
+    }
+}
diff --git a/vendor/symfony/cache/Simple/Psr6Cache.php b/vendor/symfony/cache/Simple/Psr6Cache.php
new file mode 100644
index 0000000..090f48c
--- /dev/null
+++ b/vendor/symfony/cache/Simple/Psr6Cache.php
@@ -0,0 +1,23 @@
+<?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\Cache\Simple;
+
+use Symfony\Component\Cache\Psr16Cache;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" instead.', Psr6Cache::class, Psr16Cache::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use Psr16Cache instead.
+ */
+class Psr6Cache extends Psr16Cache
+{
+}
diff --git a/vendor/symfony/cache/Simple/RedisCache.php b/vendor/symfony/cache/Simple/RedisCache.php
new file mode 100644
index 0000000..9655a75
--- /dev/null
+++ b/vendor/symfony/cache/Simple/RedisCache.php
@@ -0,0 +1,35 @@
+<?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\Cache\Simple;
+
+use Symfony\Component\Cache\Adapter\RedisAdapter;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Traits\RedisTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', RedisCache::class, RedisAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use RedisAdapter and type-hint for CacheInterface instead.
+ */
+class RedisCache extends AbstractCache
+{
+    use RedisTrait;
+
+    /**
+     * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient
+     */
+    public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+    {
+        $this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
+    }
+}
diff --git a/vendor/symfony/cache/Simple/TraceableCache.php b/vendor/symfony/cache/Simple/TraceableCache.php
new file mode 100644
index 0000000..ad9bfcf
--- /dev/null
+++ b/vendor/symfony/cache/Simple/TraceableCache.php
@@ -0,0 +1,243 @@
+<?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\Cache\Simple;
+
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', TraceableCache::class, TraceableAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use TraceableAdapter and type-hint for CacheInterface instead.
+ */
+class TraceableCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
+{
+    private $pool;
+    private $miss;
+    private $calls = [];
+
+    public function __construct(Psr16CacheInterface $pool)
+    {
+        $this->pool = $pool;
+        $this->miss = new \stdClass();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get($key, $default = null)
+    {
+        $miss = null !== $default && \is_object($default) ? $default : $this->miss;
+        $event = $this->start(__FUNCTION__);
+        try {
+            $value = $this->pool->get($key, $miss);
+        } finally {
+            $event->end = microtime(true);
+        }
+        if ($event->result[$key] = $miss !== $value) {
+            ++$event->hits;
+        } else {
+            ++$event->misses;
+            $value = $default;
+        }
+
+        return $value;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function has($key)
+    {
+        $event = $this->start(__FUNCTION__);
+        try {
+            return $event->result[$key] = $this->pool->has($key);
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function delete($key)
+    {
+        $event = $this->start(__FUNCTION__);
+        try {
+            return $event->result[$key] = $this->pool->delete($key);
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function set($key, $value, $ttl = null)
+    {
+        $event = $this->start(__FUNCTION__);
+        try {
+            return $event->result[$key] = $this->pool->set($key, $value, $ttl);
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setMultiple($values, $ttl = null)
+    {
+        $event = $this->start(__FUNCTION__);
+        $event->result['keys'] = [];
+
+        if ($values instanceof \Traversable) {
+            $values = function () use ($values, $event) {
+                foreach ($values as $k => $v) {
+                    $event->result['keys'][] = $k;
+                    yield $k => $v;
+                }
+            };
+            $values = $values();
+        } elseif (\is_array($values)) {
+            $event->result['keys'] = array_keys($values);
+        }
+
+        try {
+            return $event->result['result'] = $this->pool->setMultiple($values, $ttl);
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMultiple($keys, $default = null)
+    {
+        $miss = null !== $default && \is_object($default) ? $default : $this->miss;
+        $event = $this->start(__FUNCTION__);
+        try {
+            $result = $this->pool->getMultiple($keys, $miss);
+        } finally {
+            $event->end = microtime(true);
+        }
+        $f = function () use ($result, $event, $miss, $default) {
+            $event->result = [];
+            foreach ($result as $key => $value) {
+                if ($event->result[$key] = $miss !== $value) {
+                    ++$event->hits;
+                } else {
+                    ++$event->misses;
+                    $value = $default;
+                }
+                yield $key => $value;
+            }
+        };
+
+        return $f();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        $event = $this->start(__FUNCTION__);
+        try {
+            return $event->result = $this->pool->clear();
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteMultiple($keys)
+    {
+        $event = $this->start(__FUNCTION__);
+        if ($keys instanceof \Traversable) {
+            $keys = $event->result['keys'] = iterator_to_array($keys, false);
+        } else {
+            $event->result['keys'] = $keys;
+        }
+        try {
+            return $event->result['result'] = $this->pool->deleteMultiple($keys);
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function prune()
+    {
+        if (!$this->pool instanceof PruneableInterface) {
+            return false;
+        }
+        $event = $this->start(__FUNCTION__);
+        try {
+            return $event->result = $this->pool->prune();
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function reset()
+    {
+        if (!$this->pool instanceof ResetInterface) {
+            return;
+        }
+        $event = $this->start(__FUNCTION__);
+        try {
+            $this->pool->reset();
+        } finally {
+            $event->end = microtime(true);
+        }
+    }
+
+    public function getCalls()
+    {
+        try {
+            return $this->calls;
+        } finally {
+            $this->calls = [];
+        }
+    }
+
+    private function start($name)
+    {
+        $this->calls[] = $event = new TraceableCacheEvent();
+        $event->name = $name;
+        $event->start = microtime(true);
+
+        return $event;
+    }
+}
+
+class TraceableCacheEvent
+{
+    public $name;
+    public $start;
+    public $end;
+    public $result;
+    public $hits = 0;
+    public $misses = 0;
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/AbstractRedisAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/AbstractRedisAdapterTest.php
new file mode 100644
index 0000000..2d10240
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/AbstractRedisAdapterTest.php
@@ -0,0 +1,46 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\RedisAdapter;
+
+abstract class AbstractRedisAdapterTest extends AdapterTestCase
+{
+    protected $skippedTests = [
+        'testExpiration' => 'Testing expiration slows down the test suite',
+        'testHasItemReturnsFalseWhenDeferredItemIsExpired' => 'Testing expiration slows down the test suite',
+        'testDefaultLifeTime' => 'Testing expiration slows down the test suite',
+    ];
+
+    protected static $redis;
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        return new RedisAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
+    }
+
+    public static function setUpBeforeClass(): void
+    {
+        if (!\extension_loaded('redis')) {
+            self::markTestSkipped('Extension redis required.');
+        }
+        if (!@((new \Redis())->connect(getenv('REDIS_HOST')))) {
+            $e = error_get_last();
+            self::markTestSkipped($e['message']);
+        }
+    }
+
+    public static function tearDownAfterClass(): void
+    {
+        self::$redis = null;
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/AdapterTestCase.php b/vendor/symfony/cache/Tests/Adapter/AdapterTestCase.php
new file mode 100644
index 0000000..a6132eb
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/AdapterTestCase.php
@@ -0,0 +1,263 @@
+<?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\Cache\Tests\Adapter;
+
+use Cache\IntegrationTests\CachePoolTest;
+use PHPUnit\Framework\Assert;
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Contracts\Cache\CallbackInterface;
+
+abstract class AdapterTestCase extends CachePoolTest
+{
+    protected function setUp(): void
+    {
+        parent::setUp();
+
+        if (!\array_key_exists('testPrune', $this->skippedTests) && !$this->createCachePool() instanceof PruneableInterface) {
+            $this->skippedTests['testPrune'] = 'Not a pruneable cache pool.';
+        }
+    }
+
+    public function testGet()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $cache = $this->createCachePool();
+        $cache->clear();
+
+        $value = mt_rand();
+
+        $this->assertSame($value, $cache->get('foo', function (CacheItem $item) use ($value) {
+            $this->assertSame('foo', $item->getKey());
+
+            return $value;
+        }));
+
+        $item = $cache->getItem('foo');
+        $this->assertSame($value, $item->get());
+
+        $isHit = true;
+        $this->assertSame($value, $cache->get('foo', function (CacheItem $item) use (&$isHit) { $isHit = false; }, 0));
+        $this->assertTrue($isHit);
+
+        $this->assertNull($cache->get('foo', function (CacheItem $item) use (&$isHit, $value) {
+            $isHit = false;
+            $this->assertTrue($item->isHit());
+            $this->assertSame($value, $item->get());
+        }, INF));
+        $this->assertFalse($isHit);
+
+        $this->assertSame($value, $cache->get('bar', new class($value) implements CallbackInterface {
+            private $value;
+
+            public function __construct(int $value)
+            {
+                $this->value = $value;
+            }
+
+            public function __invoke(CacheItemInterface $item, bool &$save)
+            {
+                Assert::assertSame('bar', $item->getKey());
+
+                return $this->value;
+            }
+        }));
+    }
+
+    public function testRecursiveGet()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $cache = $this->createCachePool(0, __FUNCTION__);
+
+        $v = $cache->get('k1', function () use (&$counter, $cache) {
+            $cache->get('k2', function () use (&$counter) { return ++$counter; });
+            $v = $cache->get('k2', function () use (&$counter) { return ++$counter; }); // ensure the callback is called once
+
+            return $v;
+        });
+
+        $this->assertSame(1, $counter);
+        $this->assertSame(1, $v);
+        $this->assertSame(1, $cache->get('k2', function () { return 2; }));
+    }
+
+    public function testGetMetadata()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $cache = $this->createCachePool(0, __FUNCTION__);
+
+        $cache->deleteItem('foo');
+        $cache->get('foo', function ($item) {
+            $item->expiresAfter(10);
+            usleep(999000);
+
+            return 'bar';
+        });
+
+        $item = $cache->getItem('foo');
+
+        $expected = [
+            CacheItem::METADATA_EXPIRY => 9.5 + time(),
+            CacheItem::METADATA_CTIME => 1000,
+        ];
+        $this->assertEqualsWithDelta($expected, $item->getMetadata(), .6, 'Item metadata should embed expiry and ctime.');
+    }
+
+    public function testDefaultLifeTime()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $cache = $this->createCachePool(2);
+
+        $item = $cache->getItem('key.dlt');
+        $item->set('value');
+        $cache->save($item);
+        sleep(1);
+
+        $item = $cache->getItem('key.dlt');
+        $this->assertTrue($item->isHit());
+
+        sleep(2);
+        $item = $cache->getItem('key.dlt');
+        $this->assertFalse($item->isHit());
+    }
+
+    public function testExpiration()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $cache = $this->createCachePool();
+        $cache->save($cache->getItem('k1')->set('v1')->expiresAfter(2));
+        $cache->save($cache->getItem('k2')->set('v2')->expiresAfter(366 * 86400));
+
+        sleep(3);
+        $item = $cache->getItem('k1');
+        $this->assertFalse($item->isHit());
+        $this->assertNull($item->get(), "Item's value must be null when isHit() is false.");
+
+        $item = $cache->getItem('k2');
+        $this->assertTrue($item->isHit());
+        $this->assertSame('v2', $item->get());
+    }
+
+    public function testNotUnserializable()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $cache = $this->createCachePool();
+
+        $item = $cache->getItem('foo');
+        $cache->save($item->set(new NotUnserializable()));
+
+        $item = $cache->getItem('foo');
+        $this->assertFalse($item->isHit());
+
+        foreach ($cache->getItems(['foo']) as $item) {
+        }
+        $cache->save($item->set(new NotUnserializable()));
+
+        foreach ($cache->getItems(['foo']) as $item) {
+        }
+        $this->assertFalse($item->isHit());
+    }
+
+    public function testPrune()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        if (!method_exists($this, 'isPruned')) {
+            $this->fail('Test classes for pruneable caches must implement `isPruned($cache, $name)` method.');
+        }
+
+        /** @var PruneableInterface|CacheItemPoolInterface $cache */
+        $cache = $this->createCachePool();
+
+        $doSet = function ($name, $value, \DateInterval $expiresAfter = null) use ($cache) {
+            $item = $cache->getItem($name);
+            $item->set($value);
+
+            if ($expiresAfter) {
+                $item->expiresAfter($expiresAfter);
+            }
+
+            $cache->save($item);
+        };
+
+        $doSet('foo', 'foo-val', new \DateInterval('PT05S'));
+        $doSet('bar', 'bar-val', new \DateInterval('PT10S'));
+        $doSet('baz', 'baz-val', new \DateInterval('PT15S'));
+        $doSet('qux', 'qux-val', new \DateInterval('PT20S'));
+
+        sleep(30);
+        $cache->prune();
+        $this->assertTrue($this->isPruned($cache, 'foo'));
+        $this->assertTrue($this->isPruned($cache, 'bar'));
+        $this->assertTrue($this->isPruned($cache, 'baz'));
+        $this->assertTrue($this->isPruned($cache, 'qux'));
+
+        $doSet('foo', 'foo-val');
+        $doSet('bar', 'bar-val', new \DateInterval('PT20S'));
+        $doSet('baz', 'baz-val', new \DateInterval('PT40S'));
+        $doSet('qux', 'qux-val', new \DateInterval('PT80S'));
+
+        $cache->prune();
+        $this->assertFalse($this->isPruned($cache, 'foo'));
+        $this->assertFalse($this->isPruned($cache, 'bar'));
+        $this->assertFalse($this->isPruned($cache, 'baz'));
+        $this->assertFalse($this->isPruned($cache, 'qux'));
+
+        sleep(30);
+        $cache->prune();
+        $this->assertFalse($this->isPruned($cache, 'foo'));
+        $this->assertTrue($this->isPruned($cache, 'bar'));
+        $this->assertFalse($this->isPruned($cache, 'baz'));
+        $this->assertFalse($this->isPruned($cache, 'qux'));
+
+        sleep(30);
+        $cache->prune();
+        $this->assertFalse($this->isPruned($cache, 'foo'));
+        $this->assertTrue($this->isPruned($cache, 'baz'));
+        $this->assertFalse($this->isPruned($cache, 'qux'));
+
+        sleep(30);
+        $cache->prune();
+        $this->assertFalse($this->isPruned($cache, 'foo'));
+        $this->assertTrue($this->isPruned($cache, 'qux'));
+    }
+}
+
+class NotUnserializable
+{
+    public function __wakeup()
+    {
+        throw new \Exception(__CLASS__);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/ApcuAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/ApcuAdapterTest.php
new file mode 100644
index 0000000..5cca73f
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/ApcuAdapterTest.php
@@ -0,0 +1,124 @@
+<?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\Cache\Tests\Adapter;
+
+use Psr\Log\NullLogger;
+use Symfony\Component\Cache\Adapter\ApcuAdapter;
+
+class ApcuAdapterTest extends AdapterTestCase
+{
+    protected $skippedTests = [
+        'testExpiration' => 'Testing expiration slows down the test suite',
+        'testHasItemReturnsFalseWhenDeferredItemIsExpired' => 'Testing expiration slows down the test suite',
+        'testDefaultLifeTime' => 'Testing expiration slows down the test suite',
+    ];
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        if (!\function_exists('apcu_fetch') || !filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN)) {
+            $this->markTestSkipped('APCu extension is required.');
+        }
+        if ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) {
+            if ('testWithCliSapi' !== $this->getName()) {
+                $this->markTestSkipped('apc.enable_cli=1 is required.');
+            }
+        }
+        if ('\\' === \DIRECTORY_SEPARATOR) {
+            $this->markTestSkipped('Fails transiently on Windows.');
+        }
+
+        return new ApcuAdapter(str_replace('\\', '.', __CLASS__), $defaultLifetime);
+    }
+
+    public function testUnserializable()
+    {
+        $pool = $this->createCachePool();
+
+        $item = $pool->getItem('foo');
+        $item->set(function () {});
+
+        $this->assertFalse($pool->save($item));
+
+        $item = $pool->getItem('foo');
+        $this->assertFalse($item->isHit());
+    }
+
+    public function testVersion()
+    {
+        $namespace = str_replace('\\', '.', \get_class($this));
+
+        $pool1 = new ApcuAdapter($namespace, 0, 'p1');
+
+        $item = $pool1->getItem('foo');
+        $this->assertFalse($item->isHit());
+        $this->assertTrue($pool1->save($item->set('bar')));
+
+        $item = $pool1->getItem('foo');
+        $this->assertTrue($item->isHit());
+        $this->assertSame('bar', $item->get());
+
+        $pool2 = new ApcuAdapter($namespace, 0, 'p2');
+
+        $item = $pool2->getItem('foo');
+        $this->assertFalse($item->isHit());
+        $this->assertNull($item->get());
+
+        $item = $pool1->getItem('foo');
+        $this->assertFalse($item->isHit());
+        $this->assertNull($item->get());
+    }
+
+    public function testNamespace()
+    {
+        $namespace = str_replace('\\', '.', \get_class($this));
+
+        $pool1 = new ApcuAdapter($namespace.'_1', 0, 'p1');
+
+        $item = $pool1->getItem('foo');
+        $this->assertFalse($item->isHit());
+        $this->assertTrue($pool1->save($item->set('bar')));
+
+        $item = $pool1->getItem('foo');
+        $this->assertTrue($item->isHit());
+        $this->assertSame('bar', $item->get());
+
+        $pool2 = new ApcuAdapter($namespace.'_2', 0, 'p1');
+
+        $item = $pool2->getItem('foo');
+        $this->assertFalse($item->isHit());
+        $this->assertNull($item->get());
+
+        $item = $pool1->getItem('foo');
+        $this->assertTrue($item->isHit());
+        $this->assertSame('bar', $item->get());
+    }
+
+    public function testWithCliSapi()
+    {
+        try {
+            // disable PHPUnit error handler to mimic a production environment
+            $isCalled = false;
+            set_error_handler(function () use (&$isCalled) {
+                $isCalled = true;
+            });
+            $pool = new ApcuAdapter(str_replace('\\', '.', __CLASS__));
+            $pool->setLogger(new NullLogger());
+
+            $item = $pool->getItem('foo');
+            $item->isHit();
+            $pool->save($item->set('bar'));
+            $this->assertFalse($isCalled);
+        } finally {
+            restore_error_handler();
+        }
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/ArrayAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/ArrayAdapterTest.php
new file mode 100644
index 0000000..5c72dc6
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/ArrayAdapterTest.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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
+
+/**
+ * @group time-sensitive
+ */
+class ArrayAdapterTest extends AdapterTestCase
+{
+    protected $skippedTests = [
+        'testGetMetadata' => 'ArrayAdapter does not keep metadata.',
+        'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayAdapter is not.',
+        'testSaveWithoutExpire' => 'Assumes a shared cache which ArrayAdapter is not.',
+    ];
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        return new ArrayAdapter($defaultLifetime);
+    }
+
+    public function testGetValuesHitAndMiss()
+    {
+        /** @var ArrayAdapter $cache */
+        $cache = $this->createCachePool();
+
+        // Hit
+        $item = $cache->getItem('foo');
+        $item->set('::4711');
+        $cache->save($item);
+
+        $fooItem = $cache->getItem('foo');
+        $this->assertTrue($fooItem->isHit());
+        $this->assertEquals('::4711', $fooItem->get());
+
+        // Miss (should be present as NULL in $values)
+        $cache->getItem('bar');
+
+        $values = $cache->getValues();
+
+        $this->assertCount(2, $values);
+        $this->assertArrayHasKey('foo', $values);
+        $this->assertSame(serialize('::4711'), $values['foo']);
+        $this->assertArrayHasKey('bar', $values);
+        $this->assertNull($values['bar']);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/ChainAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/ChainAdapterTest.php
new file mode 100644
index 0000000..ac92006
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/ChainAdapterTest.php
@@ -0,0 +1,119 @@
+<?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\Cache\Tests\Adapter;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use Symfony\Component\Cache\Adapter\AdapterInterface;
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\Cache\Adapter\ChainAdapter;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Tests\Fixtures\ExternalAdapter;
+
+/**
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ * @group time-sensitive
+ */
+class ChainAdapterTest extends AdapterTestCase
+{
+    public function createCachePool($defaultLifetime = 0, $testMethod = null)
+    {
+        if ('testGetMetadata' === $testMethod) {
+            return new ChainAdapter([new FilesystemAdapter('', $defaultLifetime)], $defaultLifetime);
+        }
+
+        return new ChainAdapter([new ArrayAdapter($defaultLifetime), new ExternalAdapter(), new FilesystemAdapter('', $defaultLifetime)], $defaultLifetime);
+    }
+
+    public function testEmptyAdaptersException()
+    {
+        $this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
+        $this->expectExceptionMessage('At least one adapter must be specified.');
+        new ChainAdapter([]);
+    }
+
+    public function testInvalidAdapterException()
+    {
+        $this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
+        $this->expectExceptionMessage('The class "stdClass" does not implement');
+        new ChainAdapter([new \stdClass()]);
+    }
+
+    public function testPrune()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $cache = new ChainAdapter([
+            $this->getPruneableMock(),
+            $this->getNonPruneableMock(),
+            $this->getPruneableMock(),
+        ]);
+        $this->assertTrue($cache->prune());
+
+        $cache = new ChainAdapter([
+            $this->getPruneableMock(),
+            $this->getFailingPruneableMock(),
+            $this->getPruneableMock(),
+        ]);
+        $this->assertFalse($cache->prune());
+    }
+
+    /**
+     * @return MockObject|PruneableCacheInterface
+     */
+    private function getPruneableMock()
+    {
+        $pruneable = $this
+            ->getMockBuilder(PruneableCacheInterface::class)
+            ->getMock();
+
+        $pruneable
+            ->expects($this->atLeastOnce())
+            ->method('prune')
+            ->willReturn(true);
+
+        return $pruneable;
+    }
+
+    /**
+     * @return MockObject|PruneableCacheInterface
+     */
+    private function getFailingPruneableMock()
+    {
+        $pruneable = $this
+            ->getMockBuilder(PruneableCacheInterface::class)
+            ->getMock();
+
+        $pruneable
+            ->expects($this->atLeastOnce())
+            ->method('prune')
+            ->willReturn(false);
+
+        return $pruneable;
+    }
+
+    /**
+     * @return MockObject|AdapterInterface
+     */
+    private function getNonPruneableMock()
+    {
+        return $this
+            ->getMockBuilder(AdapterInterface::class)
+            ->getMock();
+    }
+}
+
+interface PruneableCacheInterface extends PruneableInterface, AdapterInterface
+{
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/DoctrineAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/DoctrineAdapterTest.php
new file mode 100644
index 0000000..8f520cb
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/DoctrineAdapterTest.php
@@ -0,0 +1,32 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\DoctrineAdapter;
+use Symfony\Component\Cache\Tests\Fixtures\ArrayCache;
+
+/**
+ * @group time-sensitive
+ */
+class DoctrineAdapterTest extends AdapterTestCase
+{
+    protected $skippedTests = [
+        'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayCache is not.',
+        'testSaveWithoutExpire' => 'Assumes a shared cache which ArrayCache is not.',
+        'testNotUnserializable' => 'ArrayCache does not use serialize/unserialize',
+    ];
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        return new DoctrineAdapter(new ArrayCache($defaultLifetime), '', $defaultLifetime);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/FilesystemAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/FilesystemAdapterTest.php
new file mode 100644
index 0000000..b7a69cb
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/FilesystemAdapterTest.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\Cache\Tests\Adapter;
+
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+
+/**
+ * @group time-sensitive
+ */
+class FilesystemAdapterTest extends AdapterTestCase
+{
+    public function createCachePool($defaultLifetime = 0)
+    {
+        return new FilesystemAdapter('', $defaultLifetime);
+    }
+
+    public static function tearDownAfterClass(): void
+    {
+        self::rmdir(sys_get_temp_dir().'/symfony-cache');
+    }
+
+    public static function rmdir($dir)
+    {
+        if (!file_exists($dir)) {
+            return;
+        }
+        if (!$dir || 0 !== strpos(\dirname($dir), sys_get_temp_dir())) {
+            throw new \Exception(__METHOD__."() operates only on subdirs of system's temp dir");
+        }
+        $children = new \RecursiveIteratorIterator(
+            new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
+            \RecursiveIteratorIterator::CHILD_FIRST
+        );
+        foreach ($children as $child) {
+            if ($child->isDir()) {
+                rmdir($child);
+            } else {
+                unlink($child);
+            }
+        }
+        rmdir($dir);
+    }
+
+    protected function isPruned(CacheItemPoolInterface $cache, $name)
+    {
+        $getFileMethod = (new \ReflectionObject($cache))->getMethod('getFile');
+        $getFileMethod->setAccessible(true);
+
+        return !file_exists($getFileMethod->invoke($cache, $name));
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/FilesystemTagAwareAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/FilesystemTagAwareAdapterTest.php
new file mode 100644
index 0000000..83a7ea5
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/FilesystemTagAwareAdapterTest.php
@@ -0,0 +1,28 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\FilesystemTagAwareAdapter;
+use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
+
+/**
+ * @group time-sensitive
+ */
+class FilesystemTagAwareAdapterTest extends FilesystemAdapterTest
+{
+    use TagAwareTestTrait;
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        return new FilesystemTagAwareAdapter('', $defaultLifetime);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/MaxIdLengthAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/MaxIdLengthAdapterTest.php
new file mode 100644
index 0000000..8d0b70d
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/MaxIdLengthAdapterTest.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\Cache\Tests\Adapter;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Cache\Adapter\AbstractAdapter;
+
+class MaxIdLengthAdapterTest extends TestCase
+{
+    public function testLongKey()
+    {
+        $cache = $this->getMockBuilder(MaxIdLengthAdapter::class)
+            ->setConstructorArgs([str_repeat('-', 10)])
+            ->setMethods(['doHave', 'doFetch', 'doDelete', 'doSave', 'doClear'])
+            ->getMock();
+
+        $cache->expects($this->exactly(2))
+            ->method('doHave')
+            ->withConsecutive(
+                [$this->equalTo('----------:nWfzGiCgLczv3SSUzXL3kg:')],
+                [$this->equalTo('----------:---------------------------------------')]
+            );
+
+        $cache->hasItem(str_repeat('-', 40));
+        $cache->hasItem(str_repeat('-', 39));
+    }
+
+    public function testLongKeyVersioning()
+    {
+        $cache = $this->getMockBuilder(MaxIdLengthAdapter::class)
+            ->setConstructorArgs([str_repeat('-', 26)])
+            ->getMock();
+
+        $cache
+            ->method('doFetch')
+            ->willReturn(['2:']);
+
+        $reflectionClass = new \ReflectionClass(AbstractAdapter::class);
+
+        $reflectionMethod = $reflectionClass->getMethod('getId');
+        $reflectionMethod->setAccessible(true);
+
+        // No versioning enabled
+        $this->assertEquals('--------------------------:------------', $reflectionMethod->invokeArgs($cache, [str_repeat('-', 12)]));
+        $this->assertLessThanOrEqual(50, \strlen($reflectionMethod->invokeArgs($cache, [str_repeat('-', 12)])));
+        $this->assertLessThanOrEqual(50, \strlen($reflectionMethod->invokeArgs($cache, [str_repeat('-', 23)])));
+        $this->assertLessThanOrEqual(50, \strlen($reflectionMethod->invokeArgs($cache, [str_repeat('-', 40)])));
+
+        $reflectionProperty = $reflectionClass->getProperty('versioningIsEnabled');
+        $reflectionProperty->setAccessible(true);
+        $reflectionProperty->setValue($cache, true);
+
+        // Versioning enabled
+        $this->assertEquals('--------------------------:2:------------', $reflectionMethod->invokeArgs($cache, [str_repeat('-', 12)]));
+        $this->assertLessThanOrEqual(50, \strlen($reflectionMethod->invokeArgs($cache, [str_repeat('-', 12)])));
+        $this->assertLessThanOrEqual(50, \strlen($reflectionMethod->invokeArgs($cache, [str_repeat('-', 23)])));
+        $this->assertLessThanOrEqual(50, \strlen($reflectionMethod->invokeArgs($cache, [str_repeat('-', 40)])));
+    }
+
+    public function testTooLongNamespace()
+    {
+        $this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
+        $this->expectExceptionMessage('Namespace must be 26 chars max, 40 given ("----------------------------------------")');
+        $this->getMockBuilder(MaxIdLengthAdapter::class)
+            ->setConstructorArgs([str_repeat('-', 40)])
+            ->getMock();
+    }
+}
+
+abstract class MaxIdLengthAdapter extends AbstractAdapter
+{
+    protected $maxIdLength = 50;
+
+    public function __construct($ns)
+    {
+        parent::__construct($ns);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/MemcachedAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/MemcachedAdapterTest.php
new file mode 100644
index 0000000..9f77072
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/MemcachedAdapterTest.php
@@ -0,0 +1,240 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\AbstractAdapter;
+use Symfony\Component\Cache\Adapter\MemcachedAdapter;
+
+class MemcachedAdapterTest extends AdapterTestCase
+{
+    protected $skippedTests = [
+        'testHasItemReturnsFalseWhenDeferredItemIsExpired' => 'Testing expiration slows down the test suite',
+        'testDefaultLifeTime' => 'Testing expiration slows down the test suite',
+    ];
+
+    protected static $client;
+
+    public static function setUpBeforeClass(): void
+    {
+        if (!MemcachedAdapter::isSupported()) {
+            self::markTestSkipped('Extension memcached >=2.2.0 required.');
+        }
+        self::$client = AbstractAdapter::createConnection('memcached://'.getenv('MEMCACHED_HOST'), ['binary_protocol' => false]);
+        self::$client->get('foo');
+        $code = self::$client->getResultCode();
+
+        if (\Memcached::RES_SUCCESS !== $code && \Memcached::RES_NOTFOUND !== $code) {
+            self::markTestSkipped('Memcached error: '.strtolower(self::$client->getResultMessage()));
+        }
+    }
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        $client = $defaultLifetime ? AbstractAdapter::createConnection('memcached://'.getenv('MEMCACHED_HOST')) : self::$client;
+
+        return new MemcachedAdapter($client, str_replace('\\', '.', __CLASS__), $defaultLifetime);
+    }
+
+    public function testOptions()
+    {
+        $client = MemcachedAdapter::createConnection([], [
+            'libketama_compatible' => false,
+            'distribution' => 'modula',
+            'compression' => true,
+            'serializer' => 'php',
+            'hash' => 'md5',
+        ]);
+
+        $this->assertSame(\Memcached::SERIALIZER_PHP, $client->getOption(\Memcached::OPT_SERIALIZER));
+        $this->assertSame(\Memcached::HASH_MD5, $client->getOption(\Memcached::OPT_HASH));
+        $this->assertTrue($client->getOption(\Memcached::OPT_COMPRESSION));
+        $this->assertSame(0, $client->getOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE));
+        $this->assertSame(\Memcached::DISTRIBUTION_MODULA, $client->getOption(\Memcached::OPT_DISTRIBUTION));
+    }
+
+    /**
+     * @dataProvider provideBadOptions
+     */
+    public function testBadOptions($name, $value)
+    {
+        $this->expectException('ErrorException');
+        $this->expectExceptionMessage('constant(): Couldn\'t find constant Memcached::');
+        MemcachedAdapter::createConnection([], [$name => $value]);
+    }
+
+    public function provideBadOptions()
+    {
+        return [
+            ['foo', 'bar'],
+            ['hash', 'zyx'],
+            ['serializer', 'zyx'],
+            ['distribution', 'zyx'],
+        ];
+    }
+
+    public function testDefaultOptions()
+    {
+        $this->assertTrue(MemcachedAdapter::isSupported());
+
+        $client = MemcachedAdapter::createConnection([]);
+
+        $this->assertTrue($client->getOption(\Memcached::OPT_COMPRESSION));
+        $this->assertSame(1, $client->getOption(\Memcached::OPT_BINARY_PROTOCOL));
+        $this->assertSame(1, $client->getOption(\Memcached::OPT_TCP_NODELAY));
+        $this->assertSame(1, $client->getOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE));
+    }
+
+    public function testOptionSerializer()
+    {
+        $this->expectException('Symfony\Component\Cache\Exception\CacheException');
+        $this->expectExceptionMessage('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
+        if (!\Memcached::HAVE_JSON) {
+            $this->markTestSkipped('Memcached::HAVE_JSON required');
+        }
+
+        new MemcachedAdapter(MemcachedAdapter::createConnection([], ['serializer' => 'json']));
+    }
+
+    /**
+     * @dataProvider provideServersSetting
+     */
+    public function testServersSetting($dsn, $host, $port)
+    {
+        $client1 = MemcachedAdapter::createConnection($dsn);
+        $client2 = MemcachedAdapter::createConnection([$dsn]);
+        $client3 = MemcachedAdapter::createConnection([[$host, $port]]);
+        $expect = [
+            'host' => $host,
+            'port' => $port,
+        ];
+
+        $f = function ($s) { return ['host' => $s['host'], 'port' => $s['port']]; };
+        $this->assertSame([$expect], array_map($f, $client1->getServerList()));
+        $this->assertSame([$expect], array_map($f, $client2->getServerList()));
+        $this->assertSame([$expect], array_map($f, $client3->getServerList()));
+    }
+
+    public function provideServersSetting()
+    {
+        yield [
+            'memcached://127.0.0.1/50',
+            '127.0.0.1',
+            11211,
+        ];
+        yield [
+            'memcached://localhost:11222?weight=25',
+            'localhost',
+            11222,
+        ];
+        if (filter_var(ini_get('memcached.use_sasl'), FILTER_VALIDATE_BOOLEAN)) {
+            yield [
+                'memcached://user:password@127.0.0.1?weight=50',
+                '127.0.0.1',
+                11211,
+            ];
+        }
+        yield [
+            'memcached:///var/run/memcached.sock?weight=25',
+            '/var/run/memcached.sock',
+            0,
+        ];
+        yield [
+            'memcached:///var/local/run/memcached.socket?weight=25',
+            '/var/local/run/memcached.socket',
+            0,
+        ];
+        if (filter_var(ini_get('memcached.use_sasl'), FILTER_VALIDATE_BOOLEAN)) {
+            yield [
+                'memcached://user:password@/var/local/run/memcached.socket?weight=25',
+                '/var/local/run/memcached.socket',
+                0,
+            ];
+        }
+    }
+
+    /**
+     * @dataProvider provideDsnWithOptions
+     */
+    public function testDsnWithOptions($dsn, array $options, array $expectedOptions)
+    {
+        $client = MemcachedAdapter::createConnection($dsn, $options);
+
+        foreach ($expectedOptions as $option => $expect) {
+            $this->assertSame($expect, $client->getOption($option));
+        }
+    }
+
+    public function provideDsnWithOptions()
+    {
+        if (!class_exists('\Memcached')) {
+            self::markTestSkipped('Extension memcached required.');
+        }
+
+        yield [
+            'memcached://localhost:11222?retry_timeout=10',
+            [\Memcached::OPT_RETRY_TIMEOUT => 8],
+            [\Memcached::OPT_RETRY_TIMEOUT => 10],
+        ];
+        yield [
+            'memcached://localhost:11222?socket_recv_size=1&socket_send_size=2',
+            [\Memcached::OPT_RETRY_TIMEOUT => 8],
+            [\Memcached::OPT_SOCKET_RECV_SIZE => 1, \Memcached::OPT_SOCKET_SEND_SIZE => 2, \Memcached::OPT_RETRY_TIMEOUT => 8],
+        ];
+    }
+
+    public function testClear()
+    {
+        $this->assertTrue($this->createCachePool()->clear());
+    }
+
+    public function testMultiServerDsn()
+    {
+        $dsn = 'memcached:?host[localhost]&host[localhost:12345]&host[/some/memcached.sock:]=3';
+        $client = MemcachedAdapter::createConnection($dsn);
+
+        $expected = [
+            0 => [
+                'host' => 'localhost',
+                'port' => 11211,
+                'type' => 'TCP',
+            ],
+            1 => [
+                'host' => 'localhost',
+                'port' => 12345,
+                'type' => 'TCP',
+            ],
+            2 => [
+                'host' => '/some/memcached.sock',
+                'port' => 0,
+                'type' => 'SOCKET',
+            ],
+        ];
+        $this->assertSame($expected, $client->getServerList());
+
+        $dsn = 'memcached://localhost?host[foo.bar]=3';
+        $client = MemcachedAdapter::createConnection($dsn);
+
+        $expected = [
+            0 => [
+                'host' => 'localhost',
+                'port' => 11211,
+                'type' => 'TCP',
+            ],
+            1 => [
+                'host' => 'foo.bar',
+                'port' => 11211,
+                'type' => 'TCP',
+            ],
+        ];
+        $this->assertSame($expected, $client->getServerList());
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/NamespacedProxyAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/NamespacedProxyAdapterTest.php
new file mode 100644
index 0000000..f1ffcbb
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/NamespacedProxyAdapterTest.php
@@ -0,0 +1,31 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Adapter\ProxyAdapter;
+
+/**
+ * @group time-sensitive
+ */
+class NamespacedProxyAdapterTest extends ProxyAdapterTest
+{
+    public function createCachePool($defaultLifetime = 0, $testMethod = null)
+    {
+        if ('testGetMetadata' === $testMethod) {
+            return new ProxyAdapter(new FilesystemAdapter(), 'foo', $defaultLifetime);
+        }
+
+        return new ProxyAdapter(new ArrayAdapter($defaultLifetime), 'foo', $defaultLifetime);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/NullAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/NullAdapterTest.php
new file mode 100644
index 0000000..ae3de76
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/NullAdapterTest.php
@@ -0,0 +1,141 @@
+<?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\Cache\Tests\Adapter;
+
+use PHPUnit\Framework\TestCase;
+use Psr\Cache\CacheItemInterface;
+use Symfony\Component\Cache\Adapter\NullAdapter;
+
+/**
+ * @group time-sensitive
+ */
+class NullAdapterTest extends TestCase
+{
+    public function createCachePool()
+    {
+        return new NullAdapter();
+    }
+
+    public function testGetItem()
+    {
+        $adapter = $this->createCachePool();
+
+        $item = $adapter->getItem('key');
+        $this->assertFalse($item->isHit());
+        $this->assertNull($item->get(), "Item's value must be null when isHit is false.");
+    }
+
+    public function testGet()
+    {
+        $adapter = $this->createCachePool();
+
+        $fetched = [];
+        $adapter->get('myKey', function ($item) use (&$fetched) { $fetched[] = $item; });
+        $this->assertCount(1, $fetched);
+        $item = $fetched[0];
+        $this->assertFalse($item->isHit());
+        $this->assertNull($item->get(), "Item's value must be null when isHit is false.");
+        $this->assertSame('myKey', $item->getKey());
+    }
+
+    public function testHasItem()
+    {
+        $this->assertFalse($this->createCachePool()->hasItem('key'));
+    }
+
+    public function testGetItems()
+    {
+        $adapter = $this->createCachePool();
+
+        $keys = ['foo', 'bar', 'baz', 'biz'];
+
+        /** @var CacheItemInterface[] $items */
+        $items = $adapter->getItems($keys);
+        $count = 0;
+
+        foreach ($items as $key => $item) {
+            $itemKey = $item->getKey();
+
+            $this->assertEquals($itemKey, $key, 'Keys must be preserved when fetching multiple items');
+            $this->assertContains($key, $keys, 'Cache key can not change.');
+            $this->assertFalse($item->isHit());
+
+            // Remove $key for $keys
+            foreach ($keys as $k => $v) {
+                if ($v === $key) {
+                    unset($keys[$k]);
+                }
+            }
+
+            ++$count;
+        }
+
+        $this->assertSame(4, $count);
+    }
+
+    public function testIsHit()
+    {
+        $adapter = $this->createCachePool();
+
+        $item = $adapter->getItem('key');
+        $this->assertFalse($item->isHit());
+    }
+
+    public function testClear()
+    {
+        $this->assertTrue($this->createCachePool()->clear());
+    }
+
+    public function testDeleteItem()
+    {
+        $this->assertTrue($this->createCachePool()->deleteItem('key'));
+    }
+
+    public function testDeleteItems()
+    {
+        $this->assertTrue($this->createCachePool()->deleteItems(['key', 'foo', 'bar']));
+    }
+
+    public function testSave()
+    {
+        $adapter = $this->createCachePool();
+
+        $item = $adapter->getItem('key');
+        $this->assertFalse($item->isHit());
+        $this->assertNull($item->get(), "Item's value must be null when isHit is false.");
+
+        $this->assertFalse($adapter->save($item));
+    }
+
+    public function testDeferredSave()
+    {
+        $adapter = $this->createCachePool();
+
+        $item = $adapter->getItem('key');
+        $this->assertFalse($item->isHit());
+        $this->assertNull($item->get(), "Item's value must be null when isHit is false.");
+
+        $this->assertFalse($adapter->saveDeferred($item));
+    }
+
+    public function testCommit()
+    {
+        $adapter = $this->createCachePool();
+
+        $item = $adapter->getItem('key');
+        $this->assertFalse($item->isHit());
+        $this->assertNull($item->get(), "Item's value must be null when isHit is false.");
+
+        $this->assertFalse($adapter->saveDeferred($item));
+        $this->assertFalse($this->createCachePool()->commit());
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/PdoAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/PdoAdapterTest.php
new file mode 100644
index 0000000..cd4b95c
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/PdoAdapterTest.php
@@ -0,0 +1,73 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\PdoAdapter;
+use Symfony\Component\Cache\Tests\Traits\PdoPruneableTrait;
+
+/**
+ * @group time-sensitive
+ */
+class PdoAdapterTest extends AdapterTestCase
+{
+    use PdoPruneableTrait;
+
+    protected static $dbFile;
+
+    public static function setUpBeforeClass(): void
+    {
+        if (!\extension_loaded('pdo_sqlite')) {
+            self::markTestSkipped('Extension pdo_sqlite required.');
+        }
+
+        self::$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache');
+
+        $pool = new PdoAdapter('sqlite:'.self::$dbFile);
+        $pool->createTable();
+    }
+
+    public static function tearDownAfterClass(): void
+    {
+        @unlink(self::$dbFile);
+    }
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        return new PdoAdapter('sqlite:'.self::$dbFile, 'ns', $defaultLifetime);
+    }
+
+    public function testCleanupExpiredItems()
+    {
+        $pdo = new \PDO('sqlite:'.self::$dbFile);
+
+        $getCacheItemCount = function () use ($pdo) {
+            return (int) $pdo->query('SELECT COUNT(*) FROM cache_items')->fetch(\PDO::FETCH_COLUMN);
+        };
+
+        $this->assertSame(0, $getCacheItemCount());
+
+        $cache = $this->createCachePool();
+
+        $item = $cache->getItem('some_nice_key');
+        $item->expiresAfter(1);
+        $item->set(1);
+
+        $cache->save($item);
+        $this->assertSame(1, $getCacheItemCount());
+
+        sleep(2);
+
+        $newItem = $cache->getItem($item->getKey());
+        $this->assertFalse($newItem->isHit());
+        $this->assertSame(0, $getCacheItemCount(), 'PDOAdapter must clean up expired items');
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/PdoDbalAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/PdoDbalAdapterTest.php
new file mode 100644
index 0000000..d4071ba
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/PdoDbalAdapterTest.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\Cache\Tests\Adapter;
+
+use Doctrine\DBAL\DriverManager;
+use Symfony\Component\Cache\Adapter\PdoAdapter;
+use Symfony\Component\Cache\Tests\Traits\PdoPruneableTrait;
+
+/**
+ * @group time-sensitive
+ */
+class PdoDbalAdapterTest extends AdapterTestCase
+{
+    use PdoPruneableTrait;
+
+    protected static $dbFile;
+
+    public static function setUpBeforeClass(): void
+    {
+        if (!\extension_loaded('pdo_sqlite')) {
+            self::markTestSkipped('Extension pdo_sqlite required.');
+        }
+
+        self::$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache');
+    }
+
+    public static function tearDownAfterClass(): void
+    {
+        @unlink(self::$dbFile);
+    }
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        return new PdoAdapter(DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile]), '', $defaultLifetime);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/PhpArrayAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/PhpArrayAdapterTest.php
new file mode 100644
index 0000000..c4055fb
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/PhpArrayAdapterTest.php
@@ -0,0 +1,160 @@
+<?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\Cache\Tests\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Adapter\NullAdapter;
+use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
+
+/**
+ * @group time-sensitive
+ */
+class PhpArrayAdapterTest extends AdapterTestCase
+{
+    protected $skippedTests = [
+        'testGet' => 'PhpArrayAdapter is read-only.',
+        'testRecursiveGet' => 'PhpArrayAdapter is read-only.',
+        'testBasicUsage' => 'PhpArrayAdapter is read-only.',
+        'testBasicUsageWithLongKey' => 'PhpArrayAdapter is read-only.',
+        'testClear' => 'PhpArrayAdapter is read-only.',
+        'testClearWithDeferredItems' => 'PhpArrayAdapter is read-only.',
+        'testDeleteItem' => 'PhpArrayAdapter is read-only.',
+        'testSaveExpired' => 'PhpArrayAdapter is read-only.',
+        'testSaveWithoutExpire' => 'PhpArrayAdapter is read-only.',
+        'testDeferredSave' => 'PhpArrayAdapter is read-only.',
+        'testDeferredSaveWithoutCommit' => 'PhpArrayAdapter is read-only.',
+        'testDeleteItems' => 'PhpArrayAdapter is read-only.',
+        'testDeleteDeferredItem' => 'PhpArrayAdapter is read-only.',
+        'testCommit' => 'PhpArrayAdapter is read-only.',
+        'testSaveDeferredWhenChangingValues' => 'PhpArrayAdapter is read-only.',
+        'testSaveDeferredOverwrite' => 'PhpArrayAdapter is read-only.',
+        'testIsHitDeferred' => 'PhpArrayAdapter is read-only.',
+
+        'testExpiresAt' => 'PhpArrayAdapter does not support expiration.',
+        'testExpiresAtWithNull' => 'PhpArrayAdapter does not support expiration.',
+        'testExpiresAfterWithNull' => 'PhpArrayAdapter does not support expiration.',
+        'testDeferredExpired' => 'PhpArrayAdapter does not support expiration.',
+        'testExpiration' => 'PhpArrayAdapter does not support expiration.',
+
+        'testGetItemInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.',
+        'testGetItemsInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.',
+        'testHasItemInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.',
+        'testDeleteItemInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.',
+        'testDeleteItemsInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.',
+
+        'testDefaultLifeTime' => 'PhpArrayAdapter does not allow configuring a default lifetime.',
+        'testPrune' => 'PhpArrayAdapter just proxies',
+    ];
+
+    protected static $file;
+
+    public static function setUpBeforeClass(): void
+    {
+        self::$file = sys_get_temp_dir().'/symfony-cache/php-array-adapter-test.php';
+    }
+
+    protected function tearDown(): void
+    {
+        if (file_exists(sys_get_temp_dir().'/symfony-cache')) {
+            FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
+        }
+    }
+
+    public function createCachePool($defaultLifetime = 0, $testMethod = null)
+    {
+        if ('testGetMetadata' === $testMethod) {
+            return new PhpArrayAdapter(self::$file, new FilesystemAdapter());
+        }
+
+        return new PhpArrayAdapterWrapper(self::$file, new NullAdapter());
+    }
+
+    public function testStore()
+    {
+        $arrayWithRefs = [];
+        $arrayWithRefs[0] = 123;
+        $arrayWithRefs[1] = &$arrayWithRefs[0];
+
+        $object = (object) [
+            'foo' => 'bar',
+            'foo2' => 'bar2',
+        ];
+
+        $expected = [
+            'null' => null,
+            'serializedString' => serialize($object),
+            'arrayWithRefs' => $arrayWithRefs,
+            'object' => $object,
+            'arrayWithObject' => ['bar' => $object],
+        ];
+
+        $adapter = $this->createCachePool();
+        $adapter->warmUp($expected);
+
+        foreach ($expected as $key => $value) {
+            $this->assertSame(serialize($value), serialize($adapter->getItem($key)->get()), 'Warm up should create a PHP file that OPCache can load in memory');
+        }
+    }
+
+    public function testStoredFile()
+    {
+        $data = [
+            'integer' => 42,
+            'float' => 42.42,
+            'boolean' => true,
+            'array_simple' => ['foo', 'bar'],
+            'array_associative' => ['foo' => 'bar', 'foo2' => 'bar2'],
+        ];
+        $expected = [
+            [
+                'integer' => 0,
+                'float' => 1,
+                'boolean' => 2,
+                'array_simple' => 3,
+                'array_associative' => 4,
+            ],
+            [
+                0 => 42,
+                1 => 42.42,
+                2 => true,
+                3 => ['foo', 'bar'],
+                4 => ['foo' => 'bar', 'foo2' => 'bar2'],
+            ],
+        ];
+
+        $adapter = $this->createCachePool();
+        $adapter->warmUp($data);
+
+        $values = eval(substr(file_get_contents(self::$file), 6));
+
+        $this->assertSame($expected, $values, 'Warm up should create a PHP file that OPCache can load in memory');
+    }
+}
+
+class PhpArrayAdapterWrapper extends PhpArrayAdapter
+{
+    protected $data = [];
+
+    public function save(CacheItemInterface $item)
+    {
+        (\Closure::bind(function () use ($item) {
+            $key = $item->getKey();
+            $this->keys[$key] = $id = \count($this->values);
+            $this->data[$key] = $this->values[$id] = $item->get();
+            $this->warmUp($this->data);
+            list($this->keys, $this->values) = eval(substr(file_get_contents($this->file), 6));
+        }, $this, PhpArrayAdapter::class))();
+
+        return true;
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php b/vendor/symfony/cache/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php
new file mode 100644
index 0000000..d8e2017
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php
@@ -0,0 +1,49 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
+
+/**
+ * @group time-sensitive
+ */
+class PhpArrayAdapterWithFallbackTest extends AdapterTestCase
+{
+    protected $skippedTests = [
+        'testGetItemInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.',
+        'testGetItemsInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.',
+        'testHasItemInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.',
+        'testDeleteItemInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.',
+        'testDeleteItemsInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.',
+        'testPrune' => 'PhpArrayAdapter just proxies',
+    ];
+
+    protected static $file;
+
+    public static function setUpBeforeClass(): void
+    {
+        self::$file = sys_get_temp_dir().'/symfony-cache/php-array-adapter-test.php';
+    }
+
+    protected function tearDown(): void
+    {
+        if (file_exists(sys_get_temp_dir().'/symfony-cache')) {
+            FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
+        }
+    }
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        return new PhpArrayAdapter(self::$file, new FilesystemAdapter('php-array-fallback', $defaultLifetime));
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/PhpFilesAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/PhpFilesAdapterTest.php
new file mode 100644
index 0000000..dec63a6
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/PhpFilesAdapterTest.php
@@ -0,0 +1,43 @@
+<?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\Cache\Tests\Adapter;
+
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
+
+/**
+ * @group time-sensitive
+ */
+class PhpFilesAdapterTest extends AdapterTestCase
+{
+    protected $skippedTests = [
+        'testDefaultLifeTime' => 'PhpFilesAdapter does not allow configuring a default lifetime.',
+    ];
+
+    public function createCachePool()
+    {
+        return new PhpFilesAdapter('sf-cache');
+    }
+
+    public static function tearDownAfterClass(): void
+    {
+        FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
+    }
+
+    protected function isPruned(CacheItemPoolInterface $cache, $name)
+    {
+        $getFileMethod = (new \ReflectionObject($cache))->getMethod('getFile');
+        $getFileMethod->setAccessible(true);
+
+        return !file_exists($getFileMethod->invoke($cache, $name));
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/PredisAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/PredisAdapterTest.php
new file mode 100644
index 0000000..9ced661
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/PredisAdapterTest.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\Cache\Tests\Adapter;
+
+use Predis\Connection\StreamConnection;
+use Symfony\Component\Cache\Adapter\RedisAdapter;
+
+class PredisAdapterTest extends AbstractRedisAdapterTest
+{
+    public static function setUpBeforeClass(): void
+    {
+        parent::setUpBeforeClass();
+        self::$redis = new \Predis\Client(['host' => getenv('REDIS_HOST')]);
+    }
+
+    public function testCreateConnection()
+    {
+        $redisHost = getenv('REDIS_HOST');
+
+        $redis = RedisAdapter::createConnection('redis://'.$redisHost.'/1', ['class' => \Predis\Client::class, 'timeout' => 3]);
+        $this->assertInstanceOf(\Predis\Client::class, $redis);
+
+        $connection = $redis->getConnection();
+        $this->assertInstanceOf(StreamConnection::class, $connection);
+
+        $params = [
+            'scheme' => 'tcp',
+            'host' => $redisHost,
+            'port' => 6379,
+            'persistent' => 0,
+            'timeout' => 3,
+            'read_write_timeout' => 0,
+            'tcp_nodelay' => true,
+            'database' => '1',
+        ];
+        $this->assertSame($params, $connection->getParameters()->toArray());
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/PredisClusterAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/PredisClusterAdapterTest.php
new file mode 100644
index 0000000..63fb7ec
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/PredisClusterAdapterTest.php
@@ -0,0 +1,26 @@
+<?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\Cache\Tests\Adapter;
+
+class PredisClusterAdapterTest extends AbstractRedisAdapterTest
+{
+    public static function setUpBeforeClass(): void
+    {
+        parent::setUpBeforeClass();
+        self::$redis = new \Predis\Client([['host' => getenv('REDIS_HOST')]]);
+    }
+
+    public static function tearDownAfterClass(): void
+    {
+        self::$redis = null;
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/PredisRedisClusterAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/PredisRedisClusterAdapterTest.php
new file mode 100644
index 0000000..52a515d
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/PredisRedisClusterAdapterTest.php
@@ -0,0 +1,31 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\RedisAdapter;
+
+class PredisRedisClusterAdapterTest extends AbstractRedisAdapterTest
+{
+    public static function setUpBeforeClass(): void
+    {
+        if (!$hosts = getenv('REDIS_CLUSTER_HOSTS')) {
+            self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.');
+        }
+
+        self::$redis = RedisAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']', ['class' => \Predis\Client::class, 'redis_cluster' => true]);
+    }
+
+    public static function tearDownAfterClass(): void
+    {
+        self::$redis = null;
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/PredisTagAwareAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/PredisTagAwareAdapterTest.php
new file mode 100644
index 0000000..e685d76
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/PredisTagAwareAdapterTest.php
@@ -0,0 +1,34 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
+use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
+
+class PredisTagAwareAdapterTest extends PredisAdapterTest
+{
+    use TagAwareTestTrait;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite';
+    }
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        $this->assertInstanceOf(\Predis\Client::class, self::$redis);
+        $adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
+
+        return $adapter;
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/PredisTagAwareClusterAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/PredisTagAwareClusterAdapterTest.php
new file mode 100644
index 0000000..8c604c1
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/PredisTagAwareClusterAdapterTest.php
@@ -0,0 +1,34 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
+use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
+
+class PredisTagAwareClusterAdapterTest extends PredisClusterAdapterTest
+{
+    use TagAwareTestTrait;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite';
+    }
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        $this->assertInstanceOf(\Predis\Client::class, self::$redis);
+        $adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
+
+        return $adapter;
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/PredisTagAwareRedisClusterAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/PredisTagAwareRedisClusterAdapterTest.php
new file mode 100644
index 0000000..e8d2ea6
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/PredisTagAwareRedisClusterAdapterTest.php
@@ -0,0 +1,34 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
+use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
+
+class PredisTagAwareRedisClusterAdapterTest extends PredisRedisClusterAdapterTest
+{
+    use TagAwareTestTrait;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite';
+    }
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        $this->assertInstanceOf(\Predis\Client::class, self::$redis);
+        $adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
+
+        return $adapter;
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/ProxyAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/ProxyAdapterTest.php
new file mode 100644
index 0000000..24f92ca
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/ProxyAdapterTest.php
@@ -0,0 +1,74 @@
+<?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\Cache\Tests\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Adapter\ProxyAdapter;
+use Symfony\Component\Cache\CacheItem;
+
+/**
+ * @group time-sensitive
+ */
+class ProxyAdapterTest extends AdapterTestCase
+{
+    protected $skippedTests = [
+        'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayAdapter is not.',
+        'testSaveWithoutExpire' => 'Assumes a shared cache which ArrayAdapter is not.',
+        'testPrune' => 'ProxyAdapter just proxies',
+    ];
+
+    public function createCachePool($defaultLifetime = 0, $testMethod = null)
+    {
+        if ('testGetMetadata' === $testMethod) {
+            return new ProxyAdapter(new FilesystemAdapter(), '', $defaultLifetime);
+        }
+
+        return new ProxyAdapter(new ArrayAdapter(), '', $defaultLifetime);
+    }
+
+    public function testProxyfiedItem()
+    {
+        $this->expectException('Exception');
+        $this->expectExceptionMessage('OK bar');
+        $item = new CacheItem();
+        $pool = new ProxyAdapter(new TestingArrayAdapter($item));
+
+        $proxyItem = $pool->getItem('foo');
+
+        $this->assertNotSame($item, $proxyItem);
+        $pool->save($proxyItem->set('bar'));
+    }
+}
+
+class TestingArrayAdapter extends ArrayAdapter
+{
+    private $item;
+
+    public function __construct(CacheItemInterface $item)
+    {
+        $this->item = $item;
+    }
+
+    public function getItem($key)
+    {
+        return $this->item;
+    }
+
+    public function save(CacheItemInterface $item)
+    {
+        if ($item === $this->item) {
+            throw new \Exception('OK '.$item->get());
+        }
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/Psr16AdapterTest.php b/vendor/symfony/cache/Tests/Adapter/Psr16AdapterTest.php
new file mode 100644
index 0000000..09c55e6
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/Psr16AdapterTest.php
@@ -0,0 +1,42 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Adapter\Psr16Adapter;
+use Symfony\Component\Cache\Psr16Cache;
+
+/**
+ * @group time-sensitive
+ */
+class Psr16AdapterTest extends AdapterTestCase
+{
+    protected $skippedTests = [
+        'testPrune' => 'Psr16adapter just proxies',
+    ];
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        return new Psr16Adapter(new Psr16Cache(new FilesystemAdapter()), '', $defaultLifetime);
+    }
+
+    public function testValidCacheKeyWithNamespace()
+    {
+        $cache = new Psr16Adapter(new Psr16Cache(new ArrayAdapter()), 'some_namespace', 0);
+        $item = $cache->getItem('my_key');
+        $item->set('someValue');
+        $cache->save($item);
+
+        $this->assertTrue($cache->getItem('my_key')->isHit(), 'Stored item is successfully retrieved.');
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/RedisAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/RedisAdapterTest.php
new file mode 100644
index 0000000..b039289
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/RedisAdapterTest.php
@@ -0,0 +1,108 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\AbstractAdapter;
+use Symfony\Component\Cache\Adapter\RedisAdapter;
+use Symfony\Component\Cache\Traits\RedisProxy;
+
+class RedisAdapterTest extends AbstractRedisAdapterTest
+{
+    public static function setUpBeforeClass(): void
+    {
+        parent::setUpBeforeClass();
+        self::$redis = AbstractAdapter::createConnection('redis://'.getenv('REDIS_HOST'), ['lazy' => true]);
+    }
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        $adapter = parent::createCachePool($defaultLifetime);
+        $this->assertInstanceOf(RedisProxy::class, self::$redis);
+
+        return $adapter;
+    }
+
+    /**
+     * @dataProvider provideValidSchemes
+     */
+    public function testCreateConnection($dsnScheme)
+    {
+        $redis = RedisAdapter::createConnection($dsnScheme.':?host[h1]&host[h2]&host[/foo:]');
+        $this->assertInstanceOf(\RedisArray::class, $redis);
+        $this->assertSame(['h1:6379', 'h2:6379', '/foo'], $redis->_hosts());
+        @$redis = null; // some versions of phpredis connect on destruct, let's silence the warning
+
+        $redisHost = getenv('REDIS_HOST');
+
+        $redis = RedisAdapter::createConnection($dsnScheme.'://'.$redisHost);
+        $this->assertInstanceOf(\Redis::class, $redis);
+        $this->assertTrue($redis->isConnected());
+        $this->assertSame(0, $redis->getDbNum());
+
+        $redis = RedisAdapter::createConnection($dsnScheme.'://'.$redisHost.'/2');
+        $this->assertSame(2, $redis->getDbNum());
+
+        $redis = RedisAdapter::createConnection($dsnScheme.'://'.$redisHost, ['timeout' => 3]);
+        $this->assertEquals(3, $redis->getTimeout());
+
+        $redis = RedisAdapter::createConnection($dsnScheme.'://'.$redisHost.'?timeout=4');
+        $this->assertEquals(4, $redis->getTimeout());
+
+        $redis = RedisAdapter::createConnection($dsnScheme.'://'.$redisHost, ['read_timeout' => 5]);
+        $this->assertEquals(5, $redis->getReadTimeout());
+    }
+
+    /**
+     * @dataProvider provideFailedCreateConnection
+     */
+    public function testFailedCreateConnection($dsn)
+    {
+        $this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
+        $this->expectExceptionMessage('Redis connection failed');
+        RedisAdapter::createConnection($dsn);
+    }
+
+    public function provideFailedCreateConnection()
+    {
+        return [
+            ['redis://localhost:1234'],
+            ['redis://foo@localhost'],
+            ['redis://localhost/123'],
+        ];
+    }
+
+    /**
+     * @dataProvider provideInvalidCreateConnection
+     */
+    public function testInvalidCreateConnection($dsn)
+    {
+        $this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
+        $this->expectExceptionMessage('Invalid Redis DSN');
+        RedisAdapter::createConnection($dsn);
+    }
+
+    public function provideValidSchemes()
+    {
+        return [
+            ['redis'],
+            ['rediss'],
+        ];
+    }
+
+    public function provideInvalidCreateConnection()
+    {
+        return [
+            ['foo://localhost'],
+            ['redis://'],
+        ];
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/RedisArrayAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/RedisArrayAdapterTest.php
new file mode 100644
index 0000000..63ade36
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/RedisArrayAdapterTest.php
@@ -0,0 +1,24 @@
+<?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\Cache\Tests\Adapter;
+
+class RedisArrayAdapterTest extends AbstractRedisAdapterTest
+{
+    public static function setUpBeforeClass(): void
+    {
+        parent::setupBeforeClass();
+        if (!class_exists('RedisArray')) {
+            self::markTestSkipped('The RedisArray class is required.');
+        }
+        self::$redis = new \RedisArray([getenv('REDIS_HOST')], ['lazy_connect' => true]);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/RedisClusterAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/RedisClusterAdapterTest.php
new file mode 100644
index 0000000..34dfae1
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/RedisClusterAdapterTest.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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\AbstractAdapter;
+use Symfony\Component\Cache\Adapter\RedisAdapter;
+use Symfony\Component\Cache\Traits\RedisClusterProxy;
+
+class RedisClusterAdapterTest extends AbstractRedisAdapterTest
+{
+    public static function setUpBeforeClass(): void
+    {
+        if (!class_exists('RedisCluster')) {
+            self::markTestSkipped('The RedisCluster class is required.');
+        }
+        if (!$hosts = getenv('REDIS_CLUSTER_HOSTS')) {
+            self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.');
+        }
+
+        self::$redis = AbstractAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']', ['lazy' => true, 'redis_cluster' => true]);
+    }
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        $this->assertInstanceOf(RedisClusterProxy::class, self::$redis);
+        $adapter = new RedisAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
+
+        return $adapter;
+    }
+
+    /**
+     * @dataProvider provideFailedCreateConnection
+     */
+    public function testFailedCreateConnection($dsn)
+    {
+        $this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
+        $this->expectExceptionMessage('Redis connection failed');
+        RedisAdapter::createConnection($dsn);
+    }
+
+    public function provideFailedCreateConnection()
+    {
+        return [
+            ['redis://localhost:1234?redis_cluster=1'],
+            ['redis://foo@localhost?redis_cluster=1'],
+            ['redis://localhost/123?redis_cluster=1'],
+        ];
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/RedisTagAwareAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/RedisTagAwareAdapterTest.php
new file mode 100644
index 0000000..ef14081
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/RedisTagAwareAdapterTest.php
@@ -0,0 +1,35 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
+use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
+use Symfony\Component\Cache\Traits\RedisProxy;
+
+class RedisTagAwareAdapterTest extends RedisAdapterTest
+{
+    use TagAwareTestTrait;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite';
+    }
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        $this->assertInstanceOf(RedisProxy::class, self::$redis);
+        $adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
+
+        return $adapter;
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/RedisTagAwareArrayAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/RedisTagAwareArrayAdapterTest.php
new file mode 100644
index 0000000..7c98020
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/RedisTagAwareArrayAdapterTest.php
@@ -0,0 +1,34 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
+use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
+
+class RedisTagAwareArrayAdapterTest extends RedisArrayAdapterTest
+{
+    use TagAwareTestTrait;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite';
+    }
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        $this->assertInstanceOf(\RedisArray::class, self::$redis);
+        $adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
+
+        return $adapter;
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/RedisTagAwareClusterAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/RedisTagAwareClusterAdapterTest.php
new file mode 100644
index 0000000..7b7d680
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/RedisTagAwareClusterAdapterTest.php
@@ -0,0 +1,35 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
+use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
+use Symfony\Component\Cache\Traits\RedisClusterProxy;
+
+class RedisTagAwareClusterAdapterTest extends RedisClusterAdapterTest
+{
+    use TagAwareTestTrait;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite';
+    }
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        $this->assertInstanceOf(RedisClusterProxy::class, self::$redis);
+        $adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
+
+        return $adapter;
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/SimpleCacheAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/SimpleCacheAdapterTest.php
new file mode 100644
index 0000000..8097e49
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/SimpleCacheAdapterTest.php
@@ -0,0 +1,42 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\SimpleCacheAdapter;
+use Symfony\Component\Cache\Simple\ArrayCache;
+use Symfony\Component\Cache\Simple\FilesystemCache;
+
+/**
+ * @group time-sensitive
+ * @group legacy
+ */
+class SimpleCacheAdapterTest extends AdapterTestCase
+{
+    protected $skippedTests = [
+        'testPrune' => 'SimpleCache just proxies',
+    ];
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        return new SimpleCacheAdapter(new FilesystemCache(), '', $defaultLifetime);
+    }
+
+    public function testValidCacheKeyWithNamespace()
+    {
+        $cache = new SimpleCacheAdapter(new ArrayCache(), 'some_namespace', 0);
+        $item = $cache->getItem('my_key');
+        $item->set('someValue');
+        $cache->save($item);
+
+        $this->assertTrue($cache->getItem('my_key')->isHit(), 'Stored item is successfully retrieved.');
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/TagAwareAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/TagAwareAdapterTest.php
new file mode 100644
index 0000000..b4e1ebb
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/TagAwareAdapterTest.php
@@ -0,0 +1,223 @@
+<?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\Cache\Tests\Adapter;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Cache\CacheItemInterface;
+use Symfony\Component\Cache\Adapter\AdapterInterface;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Adapter\TagAwareAdapter;
+use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
+
+/**
+ * @group time-sensitive
+ */
+class TagAwareAdapterTest extends AdapterTestCase
+{
+    use TagAwareTestTrait;
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        return new TagAwareAdapter(new FilesystemAdapter('', $defaultLifetime));
+    }
+
+    public static function tearDownAfterClass(): void
+    {
+        FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
+    }
+
+    /**
+     * Test feature specific to TagAwareAdapter as it implicit needs to save deferred when also saving expiry info.
+     */
+    public function testInvalidateCommitsSeperatePools()
+    {
+        $pool1 = $this->createCachePool();
+
+        $foo = $pool1->getItem('foo');
+        $foo->tag('tag');
+
+        $pool1->saveDeferred($foo->set('foo'));
+        $pool1->invalidateTags(['tag']);
+
+        $pool2 = $this->createCachePool();
+        $foo = $pool2->getItem('foo');
+
+        $this->assertTrue($foo->isHit());
+    }
+
+    public function testPrune()
+    {
+        $cache = new TagAwareAdapter($this->getPruneableMock());
+        $this->assertTrue($cache->prune());
+
+        $cache = new TagAwareAdapter($this->getNonPruneableMock());
+        $this->assertFalse($cache->prune());
+
+        $cache = new TagAwareAdapter($this->getFailingPruneableMock());
+        $this->assertFalse($cache->prune());
+    }
+
+    public function testKnownTagVersionsTtl()
+    {
+        $itemsPool = new FilesystemAdapter('', 10);
+        $tagsPool = $this
+            ->getMockBuilder(AdapterInterface::class)
+            ->getMock();
+
+        $pool = new TagAwareAdapter($itemsPool, $tagsPool, 10);
+
+        $item = $pool->getItem('foo');
+        $item->tag(['baz']);
+        $item->expiresAfter(100);
+
+        $tag = $this->getMockBuilder(CacheItemInterface::class)->getMock();
+        $tag->expects(self::exactly(2))->method('get')->willReturn(10);
+
+        $tagsPool->expects(self::exactly(2))->method('getItems')->willReturn([
+            'baz'.TagAwareAdapter::TAGS_PREFIX => $tag,
+        ]);
+
+        $pool->save($item);
+        $this->assertTrue($pool->getItem('foo')->isHit());
+        $this->assertTrue($pool->getItem('foo')->isHit());
+
+        sleep(20);
+
+        $this->assertTrue($pool->getItem('foo')->isHit());
+
+        sleep(5);
+
+        $this->assertTrue($pool->getItem('foo')->isHit());
+    }
+
+    public function testTagEntryIsCreatedForItemWithoutTags()
+    {
+        $pool = $this->createCachePool();
+
+        $itemKey = 'foo';
+        $item = $pool->getItem($itemKey);
+        $pool->save($item);
+
+        $adapter = new FilesystemAdapter();
+        $this->assertTrue($adapter->hasItem(TagAwareAdapter::TAGS_PREFIX.$itemKey));
+    }
+
+    public function testHasItemReturnsFalseWhenPoolDoesNotHaveItemTags()
+    {
+        $pool = $this->createCachePool();
+
+        $itemKey = 'foo';
+        $item = $pool->getItem($itemKey);
+        $pool->save($item);
+
+        $anotherPool = $this->createCachePool();
+
+        $adapter = new FilesystemAdapter();
+        $adapter->deleteItem(TagAwareAdapter::TAGS_PREFIX.$itemKey); //simulate item losing tags pair
+
+        $this->assertFalse($anotherPool->hasItem($itemKey));
+    }
+
+    public function testGetItemReturnsCacheMissWhenPoolDoesNotHaveItemTags()
+    {
+        $pool = $this->createCachePool();
+
+        $itemKey = 'foo';
+        $item = $pool->getItem($itemKey);
+        $pool->save($item);
+
+        $anotherPool = $this->createCachePool();
+
+        $adapter = new FilesystemAdapter();
+        $adapter->deleteItem(TagAwareAdapter::TAGS_PREFIX.$itemKey); //simulate item losing tags pair
+
+        $item = $anotherPool->getItem($itemKey);
+        $this->assertFalse($item->isHit());
+    }
+
+    public function testHasItemReturnsFalseWhenPoolDoesNotHaveItemAndOnlyHasTags()
+    {
+        $pool = $this->createCachePool();
+
+        $itemKey = 'foo';
+        $item = $pool->getItem($itemKey);
+        $pool->save($item);
+
+        $anotherPool = $this->createCachePool();
+
+        $adapter = new FilesystemAdapter();
+        $adapter->deleteItem($itemKey); //simulate losing item but keeping tags
+
+        $this->assertFalse($anotherPool->hasItem($itemKey));
+    }
+
+    public function testGetItemReturnsCacheMissWhenPoolDoesNotHaveItemAndOnlyHasTags()
+    {
+        $pool = $this->createCachePool();
+
+        $itemKey = 'foo';
+        $item = $pool->getItem($itemKey);
+        $pool->save($item);
+
+        $anotherPool = $this->createCachePool();
+
+        $adapter = new FilesystemAdapter();
+        $adapter->deleteItem($itemKey); //simulate losing item but keeping tags
+
+        $item = $anotherPool->getItem($itemKey);
+        $this->assertFalse($item->isHit());
+    }
+
+    /**
+     * @return MockObject|PruneableCacheInterface
+     */
+    private function getPruneableMock()
+    {
+        $pruneable = $this
+            ->getMockBuilder(PruneableCacheInterface::class)
+            ->getMock();
+
+        $pruneable
+            ->expects($this->atLeastOnce())
+            ->method('prune')
+            ->willReturn(true);
+
+        return $pruneable;
+    }
+
+    /**
+     * @return MockObject|PruneableCacheInterface
+     */
+    private function getFailingPruneableMock()
+    {
+        $pruneable = $this
+            ->getMockBuilder(PruneableCacheInterface::class)
+            ->getMock();
+
+        $pruneable
+            ->expects($this->atLeastOnce())
+            ->method('prune')
+            ->willReturn(false);
+
+        return $pruneable;
+    }
+
+    /**
+     * @return MockObject|AdapterInterface
+     */
+    private function getNonPruneableMock()
+    {
+        return $this
+            ->getMockBuilder(AdapterInterface::class)
+            ->getMock();
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/TagAwareAndProxyAdapterIntegrationTest.php b/vendor/symfony/cache/Tests/Adapter/TagAwareAndProxyAdapterIntegrationTest.php
new file mode 100644
index 0000000..b11c1f2
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/TagAwareAndProxyAdapterIntegrationTest.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Symfony\Component\Cache\Tests\Adapter;
+
+use PHPUnit\Framework\TestCase;
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\Cache\Adapter\ProxyAdapter;
+use Symfony\Component\Cache\Adapter\TagAwareAdapter;
+use Symfony\Component\Cache\Tests\Fixtures\ExternalAdapter;
+
+class TagAwareAndProxyAdapterIntegrationTest extends TestCase
+{
+    /**
+     * @dataProvider dataProvider
+     */
+    public function testIntegrationUsingProxiedAdapter(CacheItemPoolInterface $proxiedAdapter)
+    {
+        $cache = new TagAwareAdapter(new ProxyAdapter($proxiedAdapter));
+
+        $item = $cache->getItem('foo');
+        $item->tag(['tag1', 'tag2']);
+        $item->set('bar');
+        $cache->save($item);
+
+        $this->assertSame('bar', $cache->getItem('foo')->get());
+    }
+
+    public function dataProvider()
+    {
+        return [
+            [new ArrayAdapter()],
+            // also testing with a non-AdapterInterface implementation
+            // because the ProxyAdapter behaves slightly different for those
+            [new ExternalAdapter()],
+        ];
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/TraceableAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/TraceableAdapterTest.php
new file mode 100644
index 0000000..35eba7d
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/TraceableAdapterTest.php
@@ -0,0 +1,191 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Adapter\TraceableAdapter;
+
+/**
+ * @group time-sensitive
+ */
+class TraceableAdapterTest extends AdapterTestCase
+{
+    protected $skippedTests = [
+        'testPrune' => 'TraceableAdapter just proxies',
+    ];
+
+    public function createCachePool($defaultLifetime = 0)
+    {
+        return new TraceableAdapter(new FilesystemAdapter('', $defaultLifetime));
+    }
+
+    public function testGetItemMissTrace()
+    {
+        $pool = $this->createCachePool();
+        $pool->getItem('k');
+        $calls = $pool->getCalls();
+        $this->assertCount(1, $calls);
+
+        $call = $calls[0];
+        $this->assertSame('getItem', $call->name);
+        $this->assertSame(['k' => false], $call->result);
+        $this->assertSame(0, $call->hits);
+        $this->assertSame(1, $call->misses);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+
+    public function testGetItemHitTrace()
+    {
+        $pool = $this->createCachePool();
+        $item = $pool->getItem('k')->set('foo');
+        $pool->save($item);
+        $pool->getItem('k');
+        $calls = $pool->getCalls();
+        $this->assertCount(3, $calls);
+
+        $call = $calls[2];
+        $this->assertSame(1, $call->hits);
+        $this->assertSame(0, $call->misses);
+    }
+
+    public function testGetItemsMissTrace()
+    {
+        $pool = $this->createCachePool();
+        $arg = ['k0', 'k1'];
+        $items = $pool->getItems($arg);
+        foreach ($items as $item) {
+        }
+        $calls = $pool->getCalls();
+        $this->assertCount(1, $calls);
+
+        $call = $calls[0];
+        $this->assertSame('getItems', $call->name);
+        $this->assertSame(['k0' => false, 'k1' => false], $call->result);
+        $this->assertSame(2, $call->misses);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+
+    public function testHasItemMissTrace()
+    {
+        $pool = $this->createCachePool();
+        $pool->hasItem('k');
+        $calls = $pool->getCalls();
+        $this->assertCount(1, $calls);
+
+        $call = $calls[0];
+        $this->assertSame('hasItem', $call->name);
+        $this->assertSame(['k' => false], $call->result);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+
+    public function testHasItemHitTrace()
+    {
+        $pool = $this->createCachePool();
+        $item = $pool->getItem('k')->set('foo');
+        $pool->save($item);
+        $pool->hasItem('k');
+        $calls = $pool->getCalls();
+        $this->assertCount(3, $calls);
+
+        $call = $calls[2];
+        $this->assertSame('hasItem', $call->name);
+        $this->assertSame(['k' => true], $call->result);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+
+    public function testDeleteItemTrace()
+    {
+        $pool = $this->createCachePool();
+        $pool->deleteItem('k');
+        $calls = $pool->getCalls();
+        $this->assertCount(1, $calls);
+
+        $call = $calls[0];
+        $this->assertSame('deleteItem', $call->name);
+        $this->assertSame(['k' => true], $call->result);
+        $this->assertSame(0, $call->hits);
+        $this->assertSame(0, $call->misses);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+
+    public function testDeleteItemsTrace()
+    {
+        $pool = $this->createCachePool();
+        $arg = ['k0', 'k1'];
+        $pool->deleteItems($arg);
+        $calls = $pool->getCalls();
+        $this->assertCount(1, $calls);
+
+        $call = $calls[0];
+        $this->assertSame('deleteItems', $call->name);
+        $this->assertSame(['keys' => $arg, 'result' => true], $call->result);
+        $this->assertSame(0, $call->hits);
+        $this->assertSame(0, $call->misses);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+
+    public function testSaveTrace()
+    {
+        $pool = $this->createCachePool();
+        $item = $pool->getItem('k')->set('foo');
+        $pool->save($item);
+        $calls = $pool->getCalls();
+        $this->assertCount(2, $calls);
+
+        $call = $calls[1];
+        $this->assertSame('save', $call->name);
+        $this->assertSame(['k' => true], $call->result);
+        $this->assertSame(0, $call->hits);
+        $this->assertSame(0, $call->misses);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+
+    public function testSaveDeferredTrace()
+    {
+        $pool = $this->createCachePool();
+        $item = $pool->getItem('k')->set('foo');
+        $pool->saveDeferred($item);
+        $calls = $pool->getCalls();
+        $this->assertCount(2, $calls);
+
+        $call = $calls[1];
+        $this->assertSame('saveDeferred', $call->name);
+        $this->assertSame(['k' => true], $call->result);
+        $this->assertSame(0, $call->hits);
+        $this->assertSame(0, $call->misses);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+
+    public function testCommitTrace()
+    {
+        $pool = $this->createCachePool();
+        $pool->commit();
+        $calls = $pool->getCalls();
+        $this->assertCount(1, $calls);
+
+        $call = $calls[0];
+        $this->assertSame('commit', $call->name);
+        $this->assertTrue($call->result);
+        $this->assertSame(0, $call->hits);
+        $this->assertSame(0, $call->misses);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Adapter/TraceableTagAwareAdapterTest.php b/vendor/symfony/cache/Tests/Adapter/TraceableTagAwareAdapterTest.php
new file mode 100644
index 0000000..5cd4185
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Adapter/TraceableTagAwareAdapterTest.php
@@ -0,0 +1,37 @@
+<?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\Cache\Tests\Adapter;
+
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Adapter\TagAwareAdapter;
+use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter;
+
+/**
+ * @group time-sensitive
+ */
+class TraceableTagAwareAdapterTest extends TraceableAdapterTest
+{
+    public function testInvalidateTags()
+    {
+        $pool = new TraceableTagAwareAdapter(new TagAwareAdapter(new FilesystemAdapter()));
+        $pool->invalidateTags(['foo']);
+        $calls = $pool->getCalls();
+        $this->assertCount(1, $calls);
+
+        $call = $calls[0];
+        $this->assertSame('invalidateTags', $call->name);
+        $this->assertSame(0, $call->hits);
+        $this->assertSame(0, $call->misses);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/CacheItemTest.php b/vendor/symfony/cache/Tests/CacheItemTest.php
new file mode 100644
index 0000000..b36b634
--- /dev/null
+++ b/vendor/symfony/cache/Tests/CacheItemTest.php
@@ -0,0 +1,96 @@
+<?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\Cache\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Cache\CacheItem;
+
+class CacheItemTest extends TestCase
+{
+    public function testValidKey()
+    {
+        $this->assertSame('foo', CacheItem::validateKey('foo'));
+    }
+
+    /**
+     * @dataProvider provideInvalidKey
+     */
+    public function testInvalidKey($key)
+    {
+        $this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
+        $this->expectExceptionMessage('Cache key');
+        CacheItem::validateKey($key);
+    }
+
+    public function provideInvalidKey()
+    {
+        return [
+            [''],
+            ['{'],
+            ['}'],
+            ['('],
+            [')'],
+            ['/'],
+            ['\\'],
+            ['@'],
+            [':'],
+            [true],
+            [null],
+            [1],
+            [1.1],
+            [[[]]],
+            [new \Exception('foo')],
+        ];
+    }
+
+    public function testTag()
+    {
+        $item = new CacheItem();
+        $r = new \ReflectionProperty($item, 'isTaggable');
+        $r->setAccessible(true);
+        $r->setValue($item, true);
+
+        $this->assertSame($item, $item->tag('foo'));
+        $this->assertSame($item, $item->tag(['bar', 'baz']));
+
+        (\Closure::bind(function () use ($item) {
+            $this->assertSame(['foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz'], $item->newMetadata[CacheItem::METADATA_TAGS]);
+        }, $this, CacheItem::class))();
+    }
+
+    /**
+     * @dataProvider provideInvalidKey
+     */
+    public function testInvalidTag($tag)
+    {
+        $this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
+        $this->expectExceptionMessage('Cache tag');
+        $item = new CacheItem();
+        $r = new \ReflectionProperty($item, 'isTaggable');
+        $r->setAccessible(true);
+        $r->setValue($item, true);
+
+        $item->tag($tag);
+    }
+
+    public function testNonTaggableItem()
+    {
+        $this->expectException('Symfony\Component\Cache\Exception\LogicException');
+        $this->expectExceptionMessage('Cache item "foo" comes from a non tag-aware pool: you cannot tag it.');
+        $item = new CacheItem();
+        $r = new \ReflectionProperty($item, 'key');
+        $r->setAccessible(true);
+        $r->setValue($item, 'foo');
+
+        $item->tag([]);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/DependencyInjection/CacheCollectorPassTest.php b/vendor/symfony/cache/Tests/DependencyInjection/CacheCollectorPassTest.php
new file mode 100644
index 0000000..7e77491
--- /dev/null
+++ b/vendor/symfony/cache/Tests/DependencyInjection/CacheCollectorPassTest.php
@@ -0,0 +1,49 @@
+<?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\Cache\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Adapter\TagAwareAdapter;
+use Symfony\Component\Cache\Adapter\TraceableAdapter;
+use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter;
+use Symfony\Component\Cache\DataCollector\CacheDataCollector;
+use Symfony\Component\Cache\DependencyInjection\CacheCollectorPass;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+class CacheCollectorPassTest extends TestCase
+{
+    public function testProcess()
+    {
+        $container = new ContainerBuilder();
+        $container
+            ->register('fs', FilesystemAdapter::class)
+            ->addTag('cache.pool');
+        $container
+            ->register('tagged_fs', TagAwareAdapter::class)
+            ->addArgument(new Reference('fs'))
+            ->addTag('cache.pool');
+
+        $collector = $container->register('data_collector.cache', CacheDataCollector::class);
+        (new CacheCollectorPass())->process($container);
+
+        $this->assertEquals([
+            ['addInstance', ['fs', new Reference('fs')]],
+            ['addInstance', ['tagged_fs', new Reference('tagged_fs')]],
+        ], $collector->getMethodCalls());
+
+        $this->assertSame(TraceableAdapter::class, $container->findDefinition('fs')->getClass());
+        $this->assertSame(TraceableTagAwareAdapter::class, $container->getDefinition('tagged_fs')->getClass());
+        $this->assertFalse($collector->isPublic(), 'The "data_collector.cache" should be private after processing');
+    }
+}
diff --git a/vendor/symfony/cache/Tests/DependencyInjection/CachePoolClearerPassTest.php b/vendor/symfony/cache/Tests/DependencyInjection/CachePoolClearerPassTest.php
new file mode 100644
index 0000000..533aa14
--- /dev/null
+++ b/vendor/symfony/cache/Tests/DependencyInjection/CachePoolClearerPassTest.php
@@ -0,0 +1,73 @@
+<?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\Cache\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Cache\DependencyInjection\CachePoolClearerPass;
+use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
+use Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass;
+use Symfony\Component\DependencyInjection\Compiler\RepeatedPass;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer;
+
+class CachePoolClearerPassTest extends TestCase
+{
+    public function testPoolRefsAreWeak()
+    {
+        $container = new ContainerBuilder();
+        $container->setParameter('kernel.container_class', 'app');
+        $container->setParameter('kernel.project_dir', 'foo');
+
+        $globalClearer = new Definition(Psr6CacheClearer::class);
+        $container->setDefinition('cache.global_clearer', $globalClearer);
+
+        $publicPool = new Definition();
+        $publicPool->addArgument('namespace');
+        $publicPool->addTag('cache.pool', ['clearer' => 'clearer_alias']);
+        $container->setDefinition('public.pool', $publicPool);
+
+        $publicPool = new Definition();
+        $publicPool->addArgument('namespace');
+        $publicPool->addTag('cache.pool', ['clearer' => 'clearer_alias', 'name' => 'pool2']);
+        $container->setDefinition('public.pool2', $publicPool);
+
+        $privatePool = new Definition();
+        $privatePool->setPublic(false);
+        $privatePool->addArgument('namespace');
+        $privatePool->addTag('cache.pool', ['clearer' => 'clearer_alias']);
+        $container->setDefinition('private.pool', $privatePool);
+
+        $clearer = new Definition();
+        $container->setDefinition('clearer', $clearer);
+        $container->setAlias('clearer_alias', 'clearer');
+
+        $pass = new RemoveUnusedDefinitionsPass();
+        foreach ($container->getCompiler()->getPassConfig()->getRemovingPasses() as $removingPass) {
+            if ($removingPass instanceof RepeatedPass) {
+                $pass->setRepeatedPass(new RepeatedPass([$pass]));
+                break;
+            }
+        }
+        foreach ([new CachePoolPass(), $pass, new CachePoolClearerPass()] as $pass) {
+            $pass->process($container);
+        }
+
+        $expected = [[
+            'public.pool' => new Reference('public.pool'),
+            'pool2' => new Reference('public.pool2'),
+        ]];
+        $this->assertEquals($expected, $clearer->getArguments());
+        $this->assertEquals($expected, $globalClearer->getArguments());
+    }
+}
diff --git a/vendor/symfony/cache/Tests/DependencyInjection/CachePoolPassTest.php b/vendor/symfony/cache/Tests/DependencyInjection/CachePoolPassTest.php
new file mode 100644
index 0000000..e763dab
--- /dev/null
+++ b/vendor/symfony/cache/Tests/DependencyInjection/CachePoolPassTest.php
@@ -0,0 +1,177 @@
+<?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\Cache\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\Cache\Adapter\RedisAdapter;
+use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
+use Symfony\Component\DependencyInjection\ChildDefinition;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
+
+class CachePoolPassTest extends TestCase
+{
+    private $cachePoolPass;
+
+    protected function setUp(): void
+    {
+        $this->cachePoolPass = new CachePoolPass();
+    }
+
+    public function testNamespaceArgumentIsReplaced()
+    {
+        $container = new ContainerBuilder();
+        $container->setParameter('kernel.container_class', 'app');
+        $container->setParameter('kernel.project_dir', 'foo');
+        $adapter = new Definition();
+        $adapter->setAbstract(true);
+        $adapter->addTag('cache.pool');
+        $container->setDefinition('app.cache_adapter', $adapter);
+        $container->setAlias('app.cache_adapter_alias', 'app.cache_adapter');
+        $cachePool = new ChildDefinition('app.cache_adapter_alias');
+        $cachePool->addArgument(null);
+        $cachePool->addTag('cache.pool');
+        $container->setDefinition('app.cache_pool', $cachePool);
+
+        $this->cachePoolPass->process($container);
+
+        $this->assertSame('z3X945Jbf5', $cachePool->getArgument(0));
+    }
+
+    public function testNamespaceArgumentIsSeededWithAdapterClassName()
+    {
+        $container = new ContainerBuilder();
+        $container->setParameter('kernel.container_class', 'app');
+        $container->setParameter('kernel.project_dir', 'foo');
+        $adapter = new Definition();
+        $adapter->setAbstract(true);
+        $adapter->addTag('cache.pool');
+        $adapter->setClass(RedisAdapter::class);
+        $container->setDefinition('app.cache_adapter', $adapter);
+        $container->setAlias('app.cache_adapter_alias', 'app.cache_adapter');
+        $cachePool = new ChildDefinition('app.cache_adapter_alias');
+        $cachePool->addArgument(null);
+        $cachePool->addTag('cache.pool');
+        $container->setDefinition('app.cache_pool', $cachePool);
+
+        $this->cachePoolPass->process($container);
+
+        $this->assertSame('xmOJ8gqF-Y', $cachePool->getArgument(0));
+    }
+
+    public function testNamespaceArgumentIsSeededWithAdapterClassNameWithoutAffectingOtherCachePools()
+    {
+        $container = new ContainerBuilder();
+        $container->setParameter('kernel.container_class', 'app');
+        $container->setParameter('kernel.project_dir', 'foo');
+        $adapter = new Definition();
+        $adapter->setAbstract(true);
+        $adapter->addTag('cache.pool');
+        $adapter->setClass(RedisAdapter::class);
+        $container->setDefinition('app.cache_adapter', $adapter);
+        $container->setAlias('app.cache_adapter_alias', 'app.cache_adapter');
+
+        $otherCachePool = new ChildDefinition('app.cache_adapter_alias');
+        $otherCachePool->addArgument(null);
+        $otherCachePool->addTag('cache.pool');
+        $container->setDefinition('app.other_cache_pool', $otherCachePool);
+
+        $cachePool = new ChildDefinition('app.cache_adapter_alias');
+        $cachePool->addArgument(null);
+        $cachePool->addTag('cache.pool');
+        $container->setDefinition('app.cache_pool', $cachePool);
+
+        $this->cachePoolPass->process($container);
+
+        $this->assertSame('xmOJ8gqF-Y', $cachePool->getArgument(0));
+    }
+
+    public function testNamespaceArgumentIsNotReplacedIfArrayAdapterIsUsed()
+    {
+        $container = new ContainerBuilder();
+        $container->setParameter('kernel.container_class', 'app');
+        $container->setParameter('kernel.project_dir', 'foo');
+
+        $container->register('cache.adapter.array', ArrayAdapter::class)->addArgument(0);
+
+        $cachePool = new ChildDefinition('cache.adapter.array');
+        $cachePool->addTag('cache.pool');
+        $container->setDefinition('app.cache_pool', $cachePool);
+
+        $this->cachePoolPass->process($container);
+
+        $this->assertCount(0, $container->getDefinition('app.cache_pool')->getArguments());
+    }
+
+    public function testArgsAreReplaced()
+    {
+        $container = new ContainerBuilder();
+        $container->setParameter('kernel.container_class', 'app');
+        $container->setParameter('cache.prefix.seed', 'foo');
+        $cachePool = new Definition();
+        $cachePool->addTag('cache.pool', [
+            'provider' => 'foobar',
+            'default_lifetime' => 3,
+        ]);
+        $cachePool->addArgument(null);
+        $cachePool->addArgument(null);
+        $cachePool->addArgument(null);
+        $container->setDefinition('app.cache_pool', $cachePool);
+
+        $this->cachePoolPass->process($container);
+
+        $this->assertInstanceOf(Reference::class, $cachePool->getArgument(0));
+        $this->assertSame('foobar', (string) $cachePool->getArgument(0));
+        $this->assertSame('tQNhcV-8xa', $cachePool->getArgument(1));
+        $this->assertSame(3, $cachePool->getArgument(2));
+    }
+
+    public function testWithNameAttribute()
+    {
+        $container = new ContainerBuilder();
+        $container->setParameter('kernel.container_class', 'app');
+        $container->setParameter('cache.prefix.seed', 'foo');
+        $cachePool = new Definition();
+        $cachePool->addTag('cache.pool', [
+            'name' => 'foobar',
+            'provider' => 'foobar',
+        ]);
+        $cachePool->addArgument(null);
+        $cachePool->addArgument(null);
+        $cachePool->addArgument(null);
+        $container->setDefinition('app.cache_pool', $cachePool);
+
+        $this->cachePoolPass->process($container);
+
+        $this->assertSame('+naTpPa4Sm', $cachePool->getArgument(1));
+    }
+
+    public function testThrowsExceptionWhenCachePoolTagHasUnknownAttributes()
+    {
+        $this->expectException('InvalidArgumentException');
+        $this->expectExceptionMessage('Invalid "cache.pool" tag for service "app.cache_pool": accepted attributes are');
+        $container = new ContainerBuilder();
+        $container->setParameter('kernel.container_class', 'app');
+        $container->setParameter('kernel.project_dir', 'foo');
+        $adapter = new Definition();
+        $adapter->setAbstract(true);
+        $adapter->addTag('cache.pool');
+        $container->setDefinition('app.cache_adapter', $adapter);
+        $cachePool = new ChildDefinition('app.cache_adapter');
+        $cachePool->addTag('cache.pool', ['foobar' => 123]);
+        $container->setDefinition('app.cache_pool', $cachePool);
+
+        $this->cachePoolPass->process($container);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/DependencyInjection/CachePoolPrunerPassTest.php b/vendor/symfony/cache/Tests/DependencyInjection/CachePoolPrunerPassTest.php
new file mode 100644
index 0000000..2a1ab49
--- /dev/null
+++ b/vendor/symfony/cache/Tests/DependencyInjection/CachePoolPrunerPassTest.php
@@ -0,0 +1,70 @@
+<?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\Cache\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
+use Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass;
+use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+class CachePoolPrunerPassTest extends TestCase
+{
+    public function testCompilerPassReplacesCommandArgument()
+    {
+        $container = new ContainerBuilder();
+        $container->register('console.command.cache_pool_prune')->addArgument([]);
+        $container->register('pool.foo', FilesystemAdapter::class)->addTag('cache.pool');
+        $container->register('pool.bar', PhpFilesAdapter::class)->addTag('cache.pool');
+
+        $pass = new CachePoolPrunerPass();
+        $pass->process($container);
+
+        $expected = [
+            'pool.foo' => new Reference('pool.foo'),
+            'pool.bar' => new Reference('pool.bar'),
+        ];
+        $argument = $container->getDefinition('console.command.cache_pool_prune')->getArgument(0);
+
+        $this->assertInstanceOf(IteratorArgument::class, $argument);
+        $this->assertEquals($expected, $argument->getValues());
+    }
+
+    public function testCompilePassIsIgnoredIfCommandDoesNotExist()
+    {
+        $container = new ContainerBuilder();
+
+        $definitionsBefore = \count($container->getDefinitions());
+        $aliasesBefore = \count($container->getAliases());
+
+        $pass = new CachePoolPrunerPass();
+        $pass->process($container);
+
+        // the container is untouched (i.e. no new definitions or aliases)
+        $this->assertCount($definitionsBefore, $container->getDefinitions());
+        $this->assertCount($aliasesBefore, $container->getAliases());
+    }
+
+    public function testCompilerPassThrowsOnInvalidDefinitionClass()
+    {
+        $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException');
+        $this->expectExceptionMessage('Class "Symfony\Component\Cache\Tests\DependencyInjection\NotFound" used for service "pool.not-found" cannot be found.');
+        $container = new ContainerBuilder();
+        $container->register('console.command.cache_pool_prune')->addArgument([]);
+        $container->register('pool.not-found', NotFound::class)->addTag('cache.pool');
+
+        $pass = new CachePoolPrunerPass();
+        $pass->process($container);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/DoctrineProviderTest.php b/vendor/symfony/cache/Tests/DoctrineProviderTest.php
new file mode 100644
index 0000000..91a5516
--- /dev/null
+++ b/vendor/symfony/cache/Tests/DoctrineProviderTest.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\Cache\Tests;
+
+use Doctrine\Common\Cache\CacheProvider;
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\Cache\DoctrineProvider;
+
+class DoctrineProviderTest extends TestCase
+{
+    public function testProvider()
+    {
+        $pool = new ArrayAdapter();
+        $cache = new DoctrineProvider($pool);
+
+        $this->assertInstanceOf(CacheProvider::class, $cache);
+
+        $key = '{}()/\@:';
+
+        $this->assertTrue($cache->delete($key));
+        $this->assertFalse($cache->contains($key));
+
+        $this->assertTrue($cache->save($key, 'bar'));
+        $this->assertTrue($cache->contains($key));
+        $this->assertSame('bar', $cache->fetch($key));
+
+        $this->assertTrue($cache->delete($key));
+        $this->assertFalse($cache->fetch($key));
+        $this->assertTrue($cache->save($key, 'bar'));
+
+        $cache->flushAll();
+        $this->assertFalse($cache->fetch($key));
+        $this->assertFalse($cache->contains($key));
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Fixtures/ArrayCache.php b/vendor/symfony/cache/Tests/Fixtures/ArrayCache.php
new file mode 100644
index 0000000..95b39d5
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Fixtures/ArrayCache.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Symfony\Component\Cache\Tests\Fixtures;
+
+use Doctrine\Common\Cache\CacheProvider;
+
+class ArrayCache extends CacheProvider
+{
+    private $data = [];
+
+    protected function doFetch($id)
+    {
+        return $this->doContains($id) ? $this->data[$id][0] : false;
+    }
+
+    protected function doContains($id)
+    {
+        if (!isset($this->data[$id])) {
+            return false;
+        }
+
+        $expiry = $this->data[$id][1];
+
+        return !$expiry || microtime(true) < $expiry || !$this->doDelete($id);
+    }
+
+    protected function doSave($id, $data, $lifeTime = 0)
+    {
+        $this->data[$id] = [$data, $lifeTime ? microtime(true) + $lifeTime : false];
+
+        return true;
+    }
+
+    protected function doDelete($id)
+    {
+        unset($this->data[$id]);
+
+        return true;
+    }
+
+    protected function doFlush()
+    {
+        $this->data = [];
+
+        return true;
+    }
+
+    protected function doGetStats()
+    {
+        return null;
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Fixtures/ExternalAdapter.php b/vendor/symfony/cache/Tests/Fixtures/ExternalAdapter.php
new file mode 100644
index 0000000..deb0b3b
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Fixtures/ExternalAdapter.php
@@ -0,0 +1,76 @@
+<?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\Cache\Tests\Fixtures;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
+
+/**
+ * Adapter not implementing the {@see \Symfony\Component\Cache\Adapter\AdapterInterface}.
+ *
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ */
+class ExternalAdapter implements CacheItemPoolInterface
+{
+    private $cache;
+
+    public function __construct(int $defaultLifetime = 0)
+    {
+        $this->cache = new ArrayAdapter($defaultLifetime);
+    }
+
+    public function getItem($key)
+    {
+        return $this->cache->getItem($key);
+    }
+
+    public function getItems(array $keys = [])
+    {
+        return $this->cache->getItems($keys);
+    }
+
+    public function hasItem($key)
+    {
+        return $this->cache->hasItem($key);
+    }
+
+    public function clear()
+    {
+        return $this->cache->clear();
+    }
+
+    public function deleteItem($key)
+    {
+        return $this->cache->deleteItem($key);
+    }
+
+    public function deleteItems(array $keys)
+    {
+        return $this->cache->deleteItems($keys);
+    }
+
+    public function save(CacheItemInterface $item)
+    {
+        return $this->cache->save($item);
+    }
+
+    public function saveDeferred(CacheItemInterface $item)
+    {
+        return $this->cache->saveDeferred($item);
+    }
+
+    public function commit()
+    {
+        return $this->cache->commit();
+    }
+}
diff --git a/vendor/symfony/cache/Tests/LockRegistryTest.php b/vendor/symfony/cache/Tests/LockRegistryTest.php
new file mode 100644
index 0000000..0771347
--- /dev/null
+++ b/vendor/symfony/cache/Tests/LockRegistryTest.php
@@ -0,0 +1,26 @@
+<?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\Cache\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Cache\LockRegistry;
+
+class LockRegistryTest extends TestCase
+{
+    public function testFiles()
+    {
+        $lockFiles = LockRegistry::setFiles([]);
+        LockRegistry::setFiles($lockFiles);
+        $expected = array_map('realpath', glob(__DIR__.'/../Adapter/*'));
+        $this->assertSame($expected, $lockFiles);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Marshaller/DefaultMarshallerTest.php b/vendor/symfony/cache/Tests/Marshaller/DefaultMarshallerTest.php
new file mode 100644
index 0000000..141291d
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Marshaller/DefaultMarshallerTest.php
@@ -0,0 +1,112 @@
+<?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\Cache\Tests\Marshaller;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+
+class DefaultMarshallerTest extends TestCase
+{
+    public function testSerialize()
+    {
+        $marshaller = new DefaultMarshaller();
+        $values = [
+            'a' => 123,
+            'b' => function () {},
+        ];
+
+        $expected = ['a' => \extension_loaded('igbinary') && \PHP_VERSION_ID !== 70400 ? igbinary_serialize(123) : serialize(123)];
+        $this->assertSame($expected, $marshaller->marshall($values, $failed));
+        $this->assertSame(['b'], $failed);
+    }
+
+    public function testNativeUnserialize()
+    {
+        $marshaller = new DefaultMarshaller();
+        $this->assertNull($marshaller->unmarshall(serialize(null)));
+        $this->assertFalse($marshaller->unmarshall(serialize(false)));
+        $this->assertSame('', $marshaller->unmarshall(serialize('')));
+        $this->assertSame(0, $marshaller->unmarshall(serialize(0)));
+    }
+
+    /**
+     * @requires extension igbinary
+     */
+    public function testIgbinaryUnserialize()
+    {
+        if (\PHP_VERSION_ID === 70400) {
+            $this->markTestSkipped('igbinary is not compatible with PHP 7.4.0.');
+        }
+
+        $marshaller = new DefaultMarshaller();
+        $this->assertNull($marshaller->unmarshall(igbinary_serialize(null)));
+        $this->assertFalse($marshaller->unmarshall(igbinary_serialize(false)));
+        $this->assertSame('', $marshaller->unmarshall(igbinary_serialize('')));
+        $this->assertSame(0, $marshaller->unmarshall(igbinary_serialize(0)));
+    }
+
+    public function testNativeUnserializeNotFoundClass()
+    {
+        $this->expectException('DomainException');
+        $this->expectExceptionMessage('Class not found: NotExistingClass');
+        $marshaller = new DefaultMarshaller();
+        $marshaller->unmarshall('O:16:"NotExistingClass":0:{}');
+    }
+
+    /**
+     * @requires extension igbinary
+     */
+    public function testIgbinaryUnserializeNotFoundClass()
+    {
+        if (\PHP_VERSION_ID === 70400) {
+            $this->markTestSkipped('igbinary is not compatible with PHP 7.4.0.');
+        }
+
+        $this->expectException('DomainException');
+        $this->expectExceptionMessage('Class not found: NotExistingClass');
+        $marshaller = new DefaultMarshaller();
+        $marshaller->unmarshall(rawurldecode('%00%00%00%02%17%10NotExistingClass%14%00'));
+    }
+
+    public function testNativeUnserializeInvalid()
+    {
+        $this->expectException('DomainException');
+        $this->expectExceptionMessage('unserialize(): Error at offset 0 of 3 bytes');
+        $marshaller = new DefaultMarshaller();
+        set_error_handler(function () { return false; });
+        try {
+            @$marshaller->unmarshall(':::');
+        } finally {
+            restore_error_handler();
+        }
+    }
+
+    /**
+     * @requires extension igbinary
+     */
+    public function testIgbinaryUnserializeInvalid()
+    {
+        if (\PHP_VERSION_ID === 70400) {
+            $this->markTestSkipped('igbinary is not compatible with PHP 7.4.0');
+        }
+
+        $this->expectException('DomainException');
+        $this->expectExceptionMessage('igbinary_unserialize_zval: unknown type \'61\', position 5');
+        $marshaller = new DefaultMarshaller();
+        set_error_handler(function () { return false; });
+        try {
+            @$marshaller->unmarshall(rawurldecode('%00%00%00%02abc'));
+        } finally {
+            restore_error_handler();
+        }
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Psr16CacheTest.php b/vendor/symfony/cache/Tests/Psr16CacheTest.php
new file mode 100644
index 0000000..7774e1d
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Psr16CacheTest.php
@@ -0,0 +1,168 @@
+<?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\Cache\Tests;
+
+use Cache\IntegrationTests\SimpleCacheTest;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Psr16Cache;
+
+/**
+ * @group time-sensitive
+ */
+class Psr16CacheTest extends SimpleCacheTest
+{
+    protected function setUp(): void
+    {
+        parent::setUp();
+
+        if (\array_key_exists('testPrune', $this->skippedTests)) {
+            return;
+        }
+
+        $pool = $this->createSimpleCache();
+        if ($pool instanceof Psr16Cache) {
+            $pool = ((array) $pool)[sprintf("\0%s\0pool", Psr16Cache::class)];
+        }
+
+        if (!$pool instanceof PruneableInterface) {
+            $this->skippedTests['testPrune'] = 'Not a pruneable cache pool.';
+        }
+    }
+
+    public function createSimpleCache($defaultLifetime = 0)
+    {
+        return new Psr16Cache(new FilesystemAdapter('', $defaultLifetime));
+    }
+
+    public static function validKeys()
+    {
+        return array_merge(parent::validKeys(), [["a\0b"]]);
+    }
+
+    public function testDefaultLifeTime()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $cache = $this->createSimpleCache(2);
+        $cache->clear();
+
+        $cache->set('key.dlt', 'value');
+        sleep(1);
+
+        $this->assertSame('value', $cache->get('key.dlt'));
+
+        sleep(2);
+        $this->assertNull($cache->get('key.dlt'));
+
+        $cache->clear();
+    }
+
+    public function testNotUnserializable()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $cache = $this->createSimpleCache();
+        $cache->clear();
+
+        $cache->set('foo', new NotUnserializable());
+
+        $this->assertNull($cache->get('foo'));
+
+        $cache->setMultiple(['foo' => new NotUnserializable()]);
+
+        foreach ($cache->getMultiple(['foo']) as $value) {
+        }
+        $this->assertNull($value);
+
+        $cache->clear();
+    }
+
+    public function testPrune()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        /** @var PruneableInterface|CacheInterface $cache */
+        $cache = $this->createSimpleCache();
+        $cache->clear();
+
+        $cache->set('foo', 'foo-val', new \DateInterval('PT05S'));
+        $cache->set('bar', 'bar-val', new \DateInterval('PT10S'));
+        $cache->set('baz', 'baz-val', new \DateInterval('PT15S'));
+        $cache->set('qux', 'qux-val', new \DateInterval('PT20S'));
+
+        sleep(30);
+        $cache->prune();
+        $this->assertTrue($this->isPruned($cache, 'foo'));
+        $this->assertTrue($this->isPruned($cache, 'bar'));
+        $this->assertTrue($this->isPruned($cache, 'baz'));
+        $this->assertTrue($this->isPruned($cache, 'qux'));
+
+        $cache->set('foo', 'foo-val');
+        $cache->set('bar', 'bar-val', new \DateInterval('PT20S'));
+        $cache->set('baz', 'baz-val', new \DateInterval('PT40S'));
+        $cache->set('qux', 'qux-val', new \DateInterval('PT80S'));
+
+        $cache->prune();
+        $this->assertFalse($this->isPruned($cache, 'foo'));
+        $this->assertFalse($this->isPruned($cache, 'bar'));
+        $this->assertFalse($this->isPruned($cache, 'baz'));
+        $this->assertFalse($this->isPruned($cache, 'qux'));
+
+        sleep(30);
+        $cache->prune();
+        $this->assertFalse($this->isPruned($cache, 'foo'));
+        $this->assertTrue($this->isPruned($cache, 'bar'));
+        $this->assertFalse($this->isPruned($cache, 'baz'));
+        $this->assertFalse($this->isPruned($cache, 'qux'));
+
+        sleep(30);
+        $cache->prune();
+        $this->assertFalse($this->isPruned($cache, 'foo'));
+        $this->assertTrue($this->isPruned($cache, 'baz'));
+        $this->assertFalse($this->isPruned($cache, 'qux'));
+
+        sleep(30);
+        $cache->prune();
+        $this->assertFalse($this->isPruned($cache, 'foo'));
+        $this->assertTrue($this->isPruned($cache, 'qux'));
+
+        $cache->clear();
+    }
+
+    protected function isPruned($cache, $name)
+    {
+        if (Psr16Cache::class !== \get_class($cache)) {
+            $this->fail('Test classes for pruneable caches must implement `isPruned($cache, $name)` method.');
+        }
+
+        $pool = ((array) $cache)[sprintf("\0%s\0pool", Psr16Cache::class)];
+        $getFileMethod = (new \ReflectionObject($pool))->getMethod('getFile');
+        $getFileMethod->setAccessible(true);
+
+        return !file_exists($getFileMethod->invoke($pool, $name));
+    }
+}
+
+class NotUnserializable
+{
+    public function __wakeup()
+    {
+        throw new \Exception(__CLASS__);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/AbstractRedisCacheTest.php b/vendor/symfony/cache/Tests/Simple/AbstractRedisCacheTest.php
new file mode 100644
index 0000000..8171897
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/AbstractRedisCacheTest.php
@@ -0,0 +1,49 @@
+<?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\Cache\Tests\Simple;
+
+use Symfony\Component\Cache\Simple\RedisCache;
+
+/**
+ * @group legacy
+ */
+abstract class AbstractRedisCacheTest extends CacheTestCase
+{
+    protected $skippedTests = [
+        'testSetTtl' => 'Testing expiration slows down the test suite',
+        'testSetMultipleTtl' => 'Testing expiration slows down the test suite',
+        'testDefaultLifeTime' => 'Testing expiration slows down the test suite',
+    ];
+
+    protected static $redis;
+
+    public function createSimpleCache($defaultLifetime = 0)
+    {
+        return new RedisCache(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
+    }
+
+    public static function setUpBeforeClass(): void
+    {
+        if (!\extension_loaded('redis')) {
+            self::markTestSkipped('Extension redis required.');
+        }
+        if (!@((new \Redis())->connect(getenv('REDIS_HOST')))) {
+            $e = error_get_last();
+            self::markTestSkipped($e['message']);
+        }
+    }
+
+    public static function tearDownAfterClass(): void
+    {
+        self::$redis = null;
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/ApcuCacheTest.php b/vendor/symfony/cache/Tests/Simple/ApcuCacheTest.php
new file mode 100644
index 0000000..b322094
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/ApcuCacheTest.php
@@ -0,0 +1,38 @@
+<?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\Cache\Tests\Simple;
+
+use Symfony\Component\Cache\Simple\ApcuCache;
+
+/**
+ * @group legacy
+ */
+class ApcuCacheTest extends CacheTestCase
+{
+    protected $skippedTests = [
+        'testSetTtl' => 'Testing expiration slows down the test suite',
+        'testSetMultipleTtl' => 'Testing expiration slows down the test suite',
+        'testDefaultLifeTime' => 'Testing expiration slows down the test suite',
+    ];
+
+    public function createSimpleCache($defaultLifetime = 0)
+    {
+        if (!\function_exists('apcu_fetch') || !filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) || ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN))) {
+            $this->markTestSkipped('APCu extension is required.');
+        }
+        if ('\\' === \DIRECTORY_SEPARATOR) {
+            $this->markTestSkipped('Fails transiently on Windows.');
+        }
+
+        return new ApcuCache(str_replace('\\', '.', __CLASS__), $defaultLifetime);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/ArrayCacheTest.php b/vendor/symfony/cache/Tests/Simple/ArrayCacheTest.php
new file mode 100644
index 0000000..587304a
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/ArrayCacheTest.php
@@ -0,0 +1,26 @@
+<?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\Cache\Tests\Simple;
+
+use Symfony\Component\Cache\Simple\ArrayCache;
+
+/**
+ * @group time-sensitive
+ * @group legacy
+ */
+class ArrayCacheTest extends CacheTestCase
+{
+    public function createSimpleCache($defaultLifetime = 0)
+    {
+        return new ArrayCache($defaultLifetime);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/CacheTestCase.php b/vendor/symfony/cache/Tests/Simple/CacheTestCase.php
new file mode 100644
index 0000000..d23a0ff
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/CacheTestCase.php
@@ -0,0 +1,141 @@
+<?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\Cache\Tests\Simple;
+
+use Cache\IntegrationTests\SimpleCacheTest;
+use Psr\SimpleCache\CacheInterface;
+use Symfony\Component\Cache\PruneableInterface;
+
+abstract class CacheTestCase extends SimpleCacheTest
+{
+    protected function setUp(): void
+    {
+        parent::setUp();
+
+        if (!\array_key_exists('testPrune', $this->skippedTests) && !$this->createSimpleCache() instanceof PruneableInterface) {
+            $this->skippedTests['testPrune'] = 'Not a pruneable cache pool.';
+        }
+    }
+
+    public static function validKeys()
+    {
+        return array_merge(parent::validKeys(), [["a\0b"]]);
+    }
+
+    public function testDefaultLifeTime()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $cache = $this->createSimpleCache(2);
+        $cache->clear();
+
+        $cache->set('key.dlt', 'value');
+        sleep(1);
+
+        $this->assertSame('value', $cache->get('key.dlt'));
+
+        sleep(2);
+        $this->assertNull($cache->get('key.dlt'));
+
+        $cache->clear();
+    }
+
+    public function testNotUnserializable()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $cache = $this->createSimpleCache();
+        $cache->clear();
+
+        $cache->set('foo', new NotUnserializable());
+
+        $this->assertNull($cache->get('foo'));
+
+        $cache->setMultiple(['foo' => new NotUnserializable()]);
+
+        foreach ($cache->getMultiple(['foo']) as $value) {
+        }
+        $this->assertNull($value);
+
+        $cache->clear();
+    }
+
+    public function testPrune()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        if (!method_exists($this, 'isPruned')) {
+            $this->fail('Test classes for pruneable caches must implement `isPruned($cache, $name)` method.');
+        }
+
+        /** @var PruneableInterface|CacheInterface $cache */
+        $cache = $this->createSimpleCache();
+        $cache->clear();
+
+        $cache->set('foo', 'foo-val', new \DateInterval('PT05S'));
+        $cache->set('bar', 'bar-val', new \DateInterval('PT10S'));
+        $cache->set('baz', 'baz-val', new \DateInterval('PT15S'));
+        $cache->set('qux', 'qux-val', new \DateInterval('PT20S'));
+
+        sleep(30);
+        $cache->prune();
+        $this->assertTrue($this->isPruned($cache, 'foo'));
+        $this->assertTrue($this->isPruned($cache, 'bar'));
+        $this->assertTrue($this->isPruned($cache, 'baz'));
+        $this->assertTrue($this->isPruned($cache, 'qux'));
+
+        $cache->set('foo', 'foo-val');
+        $cache->set('bar', 'bar-val', new \DateInterval('PT20S'));
+        $cache->set('baz', 'baz-val', new \DateInterval('PT40S'));
+        $cache->set('qux', 'qux-val', new \DateInterval('PT80S'));
+
+        $cache->prune();
+        $this->assertFalse($this->isPruned($cache, 'foo'));
+        $this->assertFalse($this->isPruned($cache, 'bar'));
+        $this->assertFalse($this->isPruned($cache, 'baz'));
+        $this->assertFalse($this->isPruned($cache, 'qux'));
+
+        sleep(30);
+        $cache->prune();
+        $this->assertFalse($this->isPruned($cache, 'foo'));
+        $this->assertTrue($this->isPruned($cache, 'bar'));
+        $this->assertFalse($this->isPruned($cache, 'baz'));
+        $this->assertFalse($this->isPruned($cache, 'qux'));
+
+        sleep(30);
+        $cache->prune();
+        $this->assertFalse($this->isPruned($cache, 'foo'));
+        $this->assertTrue($this->isPruned($cache, 'baz'));
+        $this->assertFalse($this->isPruned($cache, 'qux'));
+
+        sleep(30);
+        $cache->prune();
+        $this->assertFalse($this->isPruned($cache, 'foo'));
+        $this->assertTrue($this->isPruned($cache, 'qux'));
+
+        $cache->clear();
+    }
+}
+
+class NotUnserializable
+{
+    public function __wakeup()
+    {
+        throw new \Exception(__CLASS__);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/ChainCacheTest.php b/vendor/symfony/cache/Tests/Simple/ChainCacheTest.php
new file mode 100644
index 0000000..3ec828c
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/ChainCacheTest.php
@@ -0,0 +1,114 @@
+<?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\Cache\Tests\Simple;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\SimpleCache\CacheInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Simple\ArrayCache;
+use Symfony\Component\Cache\Simple\ChainCache;
+use Symfony\Component\Cache\Simple\FilesystemCache;
+
+/**
+ * @group time-sensitive
+ * @group legacy
+ */
+class ChainCacheTest extends CacheTestCase
+{
+    public function createSimpleCache($defaultLifetime = 0)
+    {
+        return new ChainCache([new ArrayCache($defaultLifetime), new FilesystemCache('', $defaultLifetime)], $defaultLifetime);
+    }
+
+    public function testEmptyCachesException()
+    {
+        $this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
+        $this->expectExceptionMessage('At least one cache must be specified.');
+        new ChainCache([]);
+    }
+
+    public function testInvalidCacheException()
+    {
+        $this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
+        $this->expectExceptionMessage('The class "stdClass" does not implement');
+        new ChainCache([new \stdClass()]);
+    }
+
+    public function testPrune()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $cache = new ChainCache([
+            $this->getPruneableMock(),
+            $this->getNonPruneableMock(),
+            $this->getPruneableMock(),
+        ]);
+        $this->assertTrue($cache->prune());
+
+        $cache = new ChainCache([
+            $this->getPruneableMock(),
+            $this->getFailingPruneableMock(),
+            $this->getPruneableMock(),
+        ]);
+        $this->assertFalse($cache->prune());
+    }
+
+    /**
+     * @return MockObject|PruneableCacheInterface
+     */
+    private function getPruneableMock()
+    {
+        $pruneable = $this
+            ->getMockBuilder(PruneableCacheInterface::class)
+            ->getMock();
+
+        $pruneable
+            ->expects($this->atLeastOnce())
+            ->method('prune')
+            ->willReturn(true);
+
+        return $pruneable;
+    }
+
+    /**
+     * @return MockObject|PruneableCacheInterface
+     */
+    private function getFailingPruneableMock()
+    {
+        $pruneable = $this
+            ->getMockBuilder(PruneableCacheInterface::class)
+            ->getMock();
+
+        $pruneable
+            ->expects($this->atLeastOnce())
+            ->method('prune')
+            ->willReturn(false);
+
+        return $pruneable;
+    }
+
+    /**
+     * @return MockObject|CacheInterface
+     */
+    private function getNonPruneableMock()
+    {
+        return $this
+            ->getMockBuilder(CacheInterface::class)
+            ->getMock();
+    }
+}
+
+interface PruneableCacheInterface extends PruneableInterface, CacheInterface
+{
+}
diff --git a/vendor/symfony/cache/Tests/Simple/DoctrineCacheTest.php b/vendor/symfony/cache/Tests/Simple/DoctrineCacheTest.php
new file mode 100644
index 0000000..5d78c00
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/DoctrineCacheTest.php
@@ -0,0 +1,32 @@
+<?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\Cache\Tests\Simple;
+
+use Symfony\Component\Cache\Simple\DoctrineCache;
+use Symfony\Component\Cache\Tests\Fixtures\ArrayCache;
+
+/**
+ * @group time-sensitive
+ * @group legacy
+ */
+class DoctrineCacheTest extends CacheTestCase
+{
+    protected $skippedTests = [
+        'testObjectDoesNotChangeInCache' => 'ArrayCache does not use serialize/unserialize',
+        'testNotUnserializable' => 'ArrayCache does not use serialize/unserialize',
+    ];
+
+    public function createSimpleCache($defaultLifetime = 0)
+    {
+        return new DoctrineCache(new ArrayCache($defaultLifetime), '', $defaultLifetime);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/FilesystemCacheTest.php b/vendor/symfony/cache/Tests/Simple/FilesystemCacheTest.php
new file mode 100644
index 0000000..9f423ba
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/FilesystemCacheTest.php
@@ -0,0 +1,35 @@
+<?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\Cache\Tests\Simple;
+
+use Psr\SimpleCache\CacheInterface;
+use Symfony\Component\Cache\Simple\FilesystemCache;
+
+/**
+ * @group time-sensitive
+ * @group legacy
+ */
+class FilesystemCacheTest extends CacheTestCase
+{
+    public function createSimpleCache($defaultLifetime = 0)
+    {
+        return new FilesystemCache('', $defaultLifetime);
+    }
+
+    protected function isPruned(CacheInterface $cache, $name)
+    {
+        $getFileMethod = (new \ReflectionObject($cache))->getMethod('getFile');
+        $getFileMethod->setAccessible(true);
+
+        return !file_exists($getFileMethod->invoke($cache, $name));
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/MemcachedCacheTest.php b/vendor/symfony/cache/Tests/Simple/MemcachedCacheTest.php
new file mode 100644
index 0000000..3a7b27b
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/MemcachedCacheTest.php
@@ -0,0 +1,175 @@
+<?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\Cache\Tests\Simple;
+
+use Symfony\Component\Cache\Adapter\AbstractAdapter;
+use Symfony\Component\Cache\Simple\MemcachedCache;
+
+/**
+ * @group legacy
+ */
+class MemcachedCacheTest extends CacheTestCase
+{
+    protected $skippedTests = [
+        'testSetTtl' => 'Testing expiration slows down the test suite',
+        'testSetMultipleTtl' => 'Testing expiration slows down the test suite',
+        'testDefaultLifeTime' => 'Testing expiration slows down the test suite',
+    ];
+
+    protected static $client;
+
+    public static function setUpBeforeClass(): void
+    {
+        if (!MemcachedCache::isSupported()) {
+            self::markTestSkipped('Extension memcached >=2.2.0 required.');
+        }
+        self::$client = AbstractAdapter::createConnection('memcached://'.getenv('MEMCACHED_HOST'));
+        self::$client->get('foo');
+        $code = self::$client->getResultCode();
+
+        if (\Memcached::RES_SUCCESS !== $code && \Memcached::RES_NOTFOUND !== $code) {
+            self::markTestSkipped('Memcached error: '.strtolower(self::$client->getResultMessage()));
+        }
+    }
+
+    public function createSimpleCache($defaultLifetime = 0)
+    {
+        $client = $defaultLifetime ? AbstractAdapter::createConnection('memcached://'.getenv('MEMCACHED_HOST'), ['binary_protocol' => false]) : self::$client;
+
+        return new MemcachedCache($client, str_replace('\\', '.', __CLASS__), $defaultLifetime);
+    }
+
+    public function testCreatePersistentConnectionShouldNotDupServerList()
+    {
+        $instance = MemcachedCache::createConnection('memcached://'.getenv('MEMCACHED_HOST'), ['persistent_id' => 'persistent']);
+        $this->assertCount(1, $instance->getServerList());
+
+        $instance = MemcachedCache::createConnection('memcached://'.getenv('MEMCACHED_HOST'), ['persistent_id' => 'persistent']);
+        $this->assertCount(1, $instance->getServerList());
+    }
+
+    public function testOptions()
+    {
+        $client = MemcachedCache::createConnection([], [
+            'libketama_compatible' => false,
+            'distribution' => 'modula',
+            'compression' => true,
+            'serializer' => 'php',
+            'hash' => 'md5',
+        ]);
+
+        $this->assertSame(\Memcached::SERIALIZER_PHP, $client->getOption(\Memcached::OPT_SERIALIZER));
+        $this->assertSame(\Memcached::HASH_MD5, $client->getOption(\Memcached::OPT_HASH));
+        $this->assertTrue($client->getOption(\Memcached::OPT_COMPRESSION));
+        $this->assertSame(0, $client->getOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE));
+        $this->assertSame(\Memcached::DISTRIBUTION_MODULA, $client->getOption(\Memcached::OPT_DISTRIBUTION));
+    }
+
+    /**
+     * @dataProvider provideBadOptions
+     */
+    public function testBadOptions($name, $value)
+    {
+        $this->expectException('ErrorException');
+        $this->expectExceptionMessage('constant(): Couldn\'t find constant Memcached::');
+        MemcachedCache::createConnection([], [$name => $value]);
+    }
+
+    public function provideBadOptions()
+    {
+        return [
+            ['foo', 'bar'],
+            ['hash', 'zyx'],
+            ['serializer', 'zyx'],
+            ['distribution', 'zyx'],
+        ];
+    }
+
+    public function testDefaultOptions()
+    {
+        $this->assertTrue(MemcachedCache::isSupported());
+
+        $client = MemcachedCache::createConnection([]);
+
+        $this->assertTrue($client->getOption(\Memcached::OPT_COMPRESSION));
+        $this->assertSame(1, $client->getOption(\Memcached::OPT_BINARY_PROTOCOL));
+        $this->assertSame(1, $client->getOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE));
+    }
+
+    public function testOptionSerializer()
+    {
+        $this->expectException('Symfony\Component\Cache\Exception\CacheException');
+        $this->expectExceptionMessage('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
+        if (!\Memcached::HAVE_JSON) {
+            $this->markTestSkipped('Memcached::HAVE_JSON required');
+        }
+
+        new MemcachedCache(MemcachedCache::createConnection([], ['serializer' => 'json']));
+    }
+
+    /**
+     * @dataProvider provideServersSetting
+     */
+    public function testServersSetting($dsn, $host, $port)
+    {
+        $client1 = MemcachedCache::createConnection($dsn);
+        $client2 = MemcachedCache::createConnection([$dsn]);
+        $client3 = MemcachedCache::createConnection([[$host, $port]]);
+        $expect = [
+            'host' => $host,
+            'port' => $port,
+        ];
+
+        $f = function ($s) { return ['host' => $s['host'], 'port' => $s['port']]; };
+        $this->assertSame([$expect], array_map($f, $client1->getServerList()));
+        $this->assertSame([$expect], array_map($f, $client2->getServerList()));
+        $this->assertSame([$expect], array_map($f, $client3->getServerList()));
+    }
+
+    public function provideServersSetting()
+    {
+        yield [
+            'memcached://127.0.0.1/50',
+            '127.0.0.1',
+            11211,
+        ];
+        yield [
+            'memcached://localhost:11222?weight=25',
+            'localhost',
+            11222,
+        ];
+        if (filter_var(ini_get('memcached.use_sasl'), FILTER_VALIDATE_BOOLEAN)) {
+            yield [
+                'memcached://user:password@127.0.0.1?weight=50',
+                '127.0.0.1',
+                11211,
+            ];
+        }
+        yield [
+            'memcached:///var/run/memcached.sock?weight=25',
+            '/var/run/memcached.sock',
+            0,
+        ];
+        yield [
+            'memcached:///var/local/run/memcached.socket?weight=25',
+            '/var/local/run/memcached.socket',
+            0,
+        ];
+        if (filter_var(ini_get('memcached.use_sasl'), FILTER_VALIDATE_BOOLEAN)) {
+            yield [
+                'memcached://user:password@/var/local/run/memcached.socket?weight=25',
+                '/var/local/run/memcached.socket',
+                0,
+            ];
+        }
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/MemcachedCacheTextModeTest.php b/vendor/symfony/cache/Tests/Simple/MemcachedCacheTextModeTest.php
new file mode 100644
index 0000000..d68131a
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/MemcachedCacheTextModeTest.php
@@ -0,0 +1,28 @@
+<?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\Cache\Tests\Simple;
+
+use Symfony\Component\Cache\Adapter\AbstractAdapter;
+use Symfony\Component\Cache\Simple\MemcachedCache;
+
+/**
+ * @group legacy
+ */
+class MemcachedCacheTextModeTest extends MemcachedCacheTest
+{
+    public function createSimpleCache($defaultLifetime = 0)
+    {
+        $client = AbstractAdapter::createConnection('memcached://'.getenv('MEMCACHED_HOST'), ['binary_protocol' => false]);
+
+        return new MemcachedCache($client, str_replace('\\', '.', __CLASS__), $defaultLifetime);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/NullCacheTest.php b/vendor/symfony/cache/Tests/Simple/NullCacheTest.php
new file mode 100644
index 0000000..cf0dde9
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/NullCacheTest.php
@@ -0,0 +1,97 @@
+<?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\Cache\Tests\Simple;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Cache\Simple\NullCache;
+
+/**
+ * @group time-sensitive
+ * @group legacy
+ */
+class NullCacheTest extends TestCase
+{
+    public function createCachePool()
+    {
+        return new NullCache();
+    }
+
+    public function testGetItem()
+    {
+        $cache = $this->createCachePool();
+
+        $this->assertNull($cache->get('key'));
+    }
+
+    public function testHas()
+    {
+        $this->assertFalse($this->createCachePool()->has('key'));
+    }
+
+    public function testGetMultiple()
+    {
+        $cache = $this->createCachePool();
+
+        $keys = ['foo', 'bar', 'baz', 'biz'];
+
+        $default = new \stdClass();
+        $items = $cache->getMultiple($keys, $default);
+        $count = 0;
+
+        foreach ($items as $key => $item) {
+            $this->assertContains($key, $keys, 'Cache key can not change.');
+            $this->assertSame($default, $item);
+
+            // Remove $key for $keys
+            foreach ($keys as $k => $v) {
+                if ($v === $key) {
+                    unset($keys[$k]);
+                }
+            }
+
+            ++$count;
+        }
+
+        $this->assertSame(4, $count);
+    }
+
+    public function testClear()
+    {
+        $this->assertTrue($this->createCachePool()->clear());
+    }
+
+    public function testDelete()
+    {
+        $this->assertTrue($this->createCachePool()->delete('key'));
+    }
+
+    public function testDeleteMultiple()
+    {
+        $this->assertTrue($this->createCachePool()->deleteMultiple(['key', 'foo', 'bar']));
+    }
+
+    public function testSet()
+    {
+        $cache = $this->createCachePool();
+
+        $this->assertFalse($cache->set('key', 'val'));
+        $this->assertNull($cache->get('key'));
+    }
+
+    public function testSetMultiple()
+    {
+        $cache = $this->createCachePool();
+
+        $this->assertFalse($cache->setMultiple(['key' => 'val']));
+        $this->assertNull($cache->get('key'));
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/PdoCacheTest.php b/vendor/symfony/cache/Tests/Simple/PdoCacheTest.php
new file mode 100644
index 0000000..c326d38
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/PdoCacheTest.php
@@ -0,0 +1,48 @@
+<?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\Cache\Tests\Simple;
+
+use Symfony\Component\Cache\Simple\PdoCache;
+use Symfony\Component\Cache\Tests\Traits\PdoPruneableTrait;
+
+/**
+ * @group time-sensitive
+ * @group legacy
+ */
+class PdoCacheTest extends CacheTestCase
+{
+    use PdoPruneableTrait;
+
+    protected static $dbFile;
+
+    public static function setUpBeforeClass(): void
+    {
+        if (!\extension_loaded('pdo_sqlite')) {
+            self::markTestSkipped('Extension pdo_sqlite required.');
+        }
+
+        self::$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache');
+
+        $pool = new PdoCache('sqlite:'.self::$dbFile);
+        $pool->createTable();
+    }
+
+    public static function tearDownAfterClass(): void
+    {
+        @unlink(self::$dbFile);
+    }
+
+    public function createSimpleCache($defaultLifetime = 0)
+    {
+        return new PdoCache('sqlite:'.self::$dbFile, 'ns', $defaultLifetime);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/PdoDbalCacheTest.php b/vendor/symfony/cache/Tests/Simple/PdoDbalCacheTest.php
new file mode 100644
index 0000000..2893fee
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/PdoDbalCacheTest.php
@@ -0,0 +1,49 @@
+<?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\Cache\Tests\Simple;
+
+use Doctrine\DBAL\DriverManager;
+use Symfony\Component\Cache\Simple\PdoCache;
+use Symfony\Component\Cache\Tests\Traits\PdoPruneableTrait;
+
+/**
+ * @group time-sensitive
+ * @group legacy
+ */
+class PdoDbalCacheTest extends CacheTestCase
+{
+    use PdoPruneableTrait;
+
+    protected static $dbFile;
+
+    public static function setUpBeforeClass(): void
+    {
+        if (!\extension_loaded('pdo_sqlite')) {
+            self::markTestSkipped('Extension pdo_sqlite required.');
+        }
+
+        self::$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache');
+
+        $pool = new PdoCache(DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile]));
+        $pool->createTable();
+    }
+
+    public static function tearDownAfterClass(): void
+    {
+        @unlink(self::$dbFile);
+    }
+
+    public function createSimpleCache($defaultLifetime = 0)
+    {
+        return new PdoCache(DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile]), '', $defaultLifetime);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/PhpArrayCacheTest.php b/vendor/symfony/cache/Tests/Simple/PhpArrayCacheTest.php
new file mode 100644
index 0000000..c1d4ab2
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/PhpArrayCacheTest.php
@@ -0,0 +1,130 @@
+<?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\Cache\Tests\Simple;
+
+use Symfony\Component\Cache\Simple\NullCache;
+use Symfony\Component\Cache\Simple\PhpArrayCache;
+use Symfony\Component\Cache\Tests\Adapter\FilesystemAdapterTest;
+
+/**
+ * @group time-sensitive
+ * @group legacy
+ */
+class PhpArrayCacheTest extends CacheTestCase
+{
+    protected $skippedTests = [
+        'testBasicUsageWithLongKey' => 'PhpArrayCache does no writes',
+
+        'testDelete' => 'PhpArrayCache does no writes',
+        'testDeleteMultiple' => 'PhpArrayCache does no writes',
+        'testDeleteMultipleGenerator' => 'PhpArrayCache does no writes',
+
+        'testSetTtl' => 'PhpArrayCache does no expiration',
+        'testSetMultipleTtl' => 'PhpArrayCache does no expiration',
+        'testSetExpiredTtl' => 'PhpArrayCache does no expiration',
+        'testSetMultipleExpiredTtl' => 'PhpArrayCache does no expiration',
+
+        'testGetInvalidKeys' => 'PhpArrayCache does no validation',
+        'testGetMultipleInvalidKeys' => 'PhpArrayCache does no validation',
+        'testSetInvalidKeys' => 'PhpArrayCache does no validation',
+        'testDeleteInvalidKeys' => 'PhpArrayCache does no validation',
+        'testDeleteMultipleInvalidKeys' => 'PhpArrayCache does no validation',
+        'testSetInvalidTtl' => 'PhpArrayCache does no validation',
+        'testSetMultipleInvalidKeys' => 'PhpArrayCache does no validation',
+        'testSetMultipleInvalidTtl' => 'PhpArrayCache does no validation',
+        'testHasInvalidKeys' => 'PhpArrayCache does no validation',
+        'testSetValidData' => 'PhpArrayCache does no validation',
+
+        'testDefaultLifeTime' => 'PhpArrayCache does not allow configuring a default lifetime.',
+        'testPrune' => 'PhpArrayCache just proxies',
+    ];
+
+    protected static $file;
+
+    public static function setUpBeforeClass(): void
+    {
+        self::$file = sys_get_temp_dir().'/symfony-cache/php-array-adapter-test.php';
+    }
+
+    protected function tearDown(): void
+    {
+        if (file_exists(sys_get_temp_dir().'/symfony-cache')) {
+            FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
+        }
+    }
+
+    public function createSimpleCache()
+    {
+        return new PhpArrayCacheWrapper(self::$file, new NullCache());
+    }
+
+    public function testStore()
+    {
+        $arrayWithRefs = [];
+        $arrayWithRefs[0] = 123;
+        $arrayWithRefs[1] = &$arrayWithRefs[0];
+
+        $object = (object) [
+            'foo' => 'bar',
+            'foo2' => 'bar2',
+        ];
+
+        $expected = [
+            'null' => null,
+            'serializedString' => serialize($object),
+            'arrayWithRefs' => $arrayWithRefs,
+            'object' => $object,
+            'arrayWithObject' => ['bar' => $object],
+        ];
+
+        $cache = new PhpArrayCache(self::$file, new NullCache());
+        $cache->warmUp($expected);
+
+        foreach ($expected as $key => $value) {
+            $this->assertSame(serialize($value), serialize($cache->get($key)), 'Warm up should create a PHP file that OPCache can load in memory');
+        }
+    }
+
+    public function testStoredFile()
+    {
+        $data = [
+            'integer' => 42,
+            'float' => 42.42,
+            'boolean' => true,
+            'array_simple' => ['foo', 'bar'],
+            'array_associative' => ['foo' => 'bar', 'foo2' => 'bar2'],
+        ];
+        $expected = [
+            [
+                'integer' => 0,
+                'float' => 1,
+                'boolean' => 2,
+                'array_simple' => 3,
+                'array_associative' => 4,
+            ],
+            [
+                0 => 42,
+                1 => 42.42,
+                2 => true,
+                3 => ['foo', 'bar'],
+                4 => ['foo' => 'bar', 'foo2' => 'bar2'],
+            ],
+        ];
+
+        $cache = new PhpArrayCache(self::$file, new NullCache());
+        $cache->warmUp($data);
+
+        $values = eval(substr(file_get_contents(self::$file), 6));
+
+        $this->assertSame($expected, $values, 'Warm up should create a PHP file that OPCache can load in memory');
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/PhpArrayCacheWithFallbackTest.php b/vendor/symfony/cache/Tests/Simple/PhpArrayCacheWithFallbackTest.php
new file mode 100644
index 0000000..7ae814a
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/PhpArrayCacheWithFallbackTest.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\Cache\Tests\Simple;
+
+use Symfony\Component\Cache\Simple\FilesystemCache;
+use Symfony\Component\Cache\Simple\PhpArrayCache;
+use Symfony\Component\Cache\Tests\Adapter\FilesystemAdapterTest;
+
+/**
+ * @group time-sensitive
+ * @group legacy
+ */
+class PhpArrayCacheWithFallbackTest extends CacheTestCase
+{
+    protected $skippedTests = [
+        'testGetInvalidKeys' => 'PhpArrayCache does no validation',
+        'testGetMultipleInvalidKeys' => 'PhpArrayCache does no validation',
+        'testDeleteInvalidKeys' => 'PhpArrayCache does no validation',
+        'testDeleteMultipleInvalidKeys' => 'PhpArrayCache does no validation',
+        //'testSetValidData' => 'PhpArrayCache does no validation',
+        'testSetInvalidKeys' => 'PhpArrayCache does no validation',
+        'testSetInvalidTtl' => 'PhpArrayCache does no validation',
+        'testSetMultipleInvalidKeys' => 'PhpArrayCache does no validation',
+        'testSetMultipleInvalidTtl' => 'PhpArrayCache does no validation',
+        'testHasInvalidKeys' => 'PhpArrayCache does no validation',
+        'testPrune' => 'PhpArrayCache just proxies',
+    ];
+
+    protected static $file;
+
+    public static function setUpBeforeClass(): void
+    {
+        self::$file = sys_get_temp_dir().'/symfony-cache/php-array-adapter-test.php';
+    }
+
+    protected function tearDown(): void
+    {
+        if (file_exists(sys_get_temp_dir().'/symfony-cache')) {
+            FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
+        }
+    }
+
+    public function createSimpleCache($defaultLifetime = 0)
+    {
+        return new PhpArrayCache(self::$file, new FilesystemCache('php-array-fallback', $defaultLifetime));
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/PhpArrayCacheWrapper.php b/vendor/symfony/cache/Tests/Simple/PhpArrayCacheWrapper.php
new file mode 100644
index 0000000..1e102fe
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/PhpArrayCacheWrapper.php
@@ -0,0 +1,46 @@
+<?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\Cache\Tests\Simple;
+
+use Symfony\Component\Cache\Simple\PhpArrayCache;
+
+class PhpArrayCacheWrapper extends PhpArrayCache
+{
+    protected $data = [];
+
+    public function set($key, $value, $ttl = null)
+    {
+        (\Closure::bind(function () use ($key, $value) {
+            $this->data[$key] = $value;
+            $this->warmUp($this->data);
+            list($this->keys, $this->values) = eval(substr(file_get_contents($this->file), 6));
+        }, $this, PhpArrayCache::class))();
+
+        return true;
+    }
+
+    public function setMultiple($values, $ttl = null)
+    {
+        if (!\is_array($values) && !$values instanceof \Traversable) {
+            return parent::setMultiple($values, $ttl);
+        }
+        (\Closure::bind(function () use ($values) {
+            foreach ($values as $key => $value) {
+                $this->data[$key] = $value;
+            }
+            $this->warmUp($this->data);
+            list($this->keys, $this->values) = eval(substr(file_get_contents($this->file), 6));
+        }, $this, PhpArrayCache::class))();
+
+        return true;
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/PhpFilesCacheTest.php b/vendor/symfony/cache/Tests/Simple/PhpFilesCacheTest.php
new file mode 100644
index 0000000..7e40df7
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/PhpFilesCacheTest.php
@@ -0,0 +1,39 @@
+<?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\Cache\Tests\Simple;
+
+use Psr\SimpleCache\CacheInterface;
+use Symfony\Component\Cache\Simple\PhpFilesCache;
+
+/**
+ * @group time-sensitive
+ * @group legacy
+ */
+class PhpFilesCacheTest extends CacheTestCase
+{
+    protected $skippedTests = [
+        'testDefaultLifeTime' => 'PhpFilesCache does not allow configuring a default lifetime.',
+    ];
+
+    public function createSimpleCache()
+    {
+        return new PhpFilesCache('sf-cache');
+    }
+
+    protected function isPruned(CacheInterface $cache, $name)
+    {
+        $getFileMethod = (new \ReflectionObject($cache))->getMethod('getFile');
+        $getFileMethod->setAccessible(true);
+
+        return !file_exists($getFileMethod->invoke($cache, $name));
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/Psr6CacheTest.php b/vendor/symfony/cache/Tests/Simple/Psr6CacheTest.php
new file mode 100644
index 0000000..9fff36e
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/Psr6CacheTest.php
@@ -0,0 +1,31 @@
+<?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\Cache\Tests\Simple;
+
+use Symfony\Component\Cache\Simple\Psr6Cache;
+
+/**
+ * @group legacy
+ */
+abstract class Psr6CacheTest extends CacheTestCase
+{
+    protected $skippedTests = [
+        'testPrune' => 'Psr6Cache just proxies',
+    ];
+
+    public function createSimpleCache($defaultLifetime = 0)
+    {
+        return new Psr6Cache($this->createCacheItemPool($defaultLifetime));
+    }
+
+    abstract protected function createCacheItemPool($defaultLifetime = 0);
+}
diff --git a/vendor/symfony/cache/Tests/Simple/Psr6CacheWithAdapterTest.php b/vendor/symfony/cache/Tests/Simple/Psr6CacheWithAdapterTest.php
new file mode 100644
index 0000000..e5c7a6a
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/Psr6CacheWithAdapterTest.php
@@ -0,0 +1,26 @@
+<?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\Cache\Tests\Simple;
+
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+
+/**
+ * @group time-sensitive
+ * @group legacy
+ */
+class Psr6CacheWithAdapterTest extends Psr6CacheTest
+{
+    protected function createCacheItemPool($defaultLifetime = 0)
+    {
+        return new FilesystemAdapter('', $defaultLifetime);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/Psr6CacheWithoutAdapterTest.php b/vendor/symfony/cache/Tests/Simple/Psr6CacheWithoutAdapterTest.php
new file mode 100644
index 0000000..f987d40
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/Psr6CacheWithoutAdapterTest.php
@@ -0,0 +1,26 @@
+<?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\Cache\Tests\Simple;
+
+use Symfony\Component\Cache\Tests\Fixtures\ExternalAdapter;
+
+/**
+ * @group time-sensitive
+ * @group legacy
+ */
+class Psr6CacheWithoutAdapterTest extends Psr6CacheTest
+{
+    protected function createCacheItemPool($defaultLifetime = 0)
+    {
+        return new ExternalAdapter($defaultLifetime);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/RedisArrayCacheTest.php b/vendor/symfony/cache/Tests/Simple/RedisArrayCacheTest.php
new file mode 100644
index 0000000..834b620
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/RedisArrayCacheTest.php
@@ -0,0 +1,27 @@
+<?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\Cache\Tests\Simple;
+
+/**
+ * @group legacy
+ */
+class RedisArrayCacheTest extends AbstractRedisCacheTest
+{
+    public static function setUpBeforeClass(): void
+    {
+        parent::setupBeforeClass();
+        if (!class_exists('RedisArray')) {
+            self::markTestSkipped('The RedisArray class is required.');
+        }
+        self::$redis = new \RedisArray([getenv('REDIS_HOST')], ['lazy_connect' => true]);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/RedisCacheTest.php b/vendor/symfony/cache/Tests/Simple/RedisCacheTest.php
new file mode 100644
index 0000000..b5792f3
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/RedisCacheTest.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\Cache\Tests\Simple;
+
+use Symfony\Component\Cache\Simple\RedisCache;
+
+/**
+ * @group legacy
+ */
+class RedisCacheTest extends AbstractRedisCacheTest
+{
+    public static function setUpBeforeClass(): void
+    {
+        parent::setupBeforeClass();
+        self::$redis = RedisCache::createConnection('redis://'.getenv('REDIS_HOST'));
+    }
+
+    public function testCreateConnection()
+    {
+        $redisHost = getenv('REDIS_HOST');
+
+        $redis = RedisCache::createConnection('redis://'.$redisHost);
+        $this->assertInstanceOf(\Redis::class, $redis);
+        $this->assertTrue($redis->isConnected());
+        $this->assertSame(0, $redis->getDbNum());
+
+        $redis = RedisCache::createConnection('redis://'.$redisHost.'/2');
+        $this->assertSame(2, $redis->getDbNum());
+
+        $redis = RedisCache::createConnection('redis://'.$redisHost, ['timeout' => 3]);
+        $this->assertEquals(3, $redis->getTimeout());
+
+        $redis = RedisCache::createConnection('redis://'.$redisHost.'?timeout=4');
+        $this->assertEquals(4, $redis->getTimeout());
+
+        $redis = RedisCache::createConnection('redis://'.$redisHost, ['read_timeout' => 5]);
+        $this->assertEquals(5, $redis->getReadTimeout());
+    }
+
+    /**
+     * @dataProvider provideFailedCreateConnection
+     */
+    public function testFailedCreateConnection($dsn)
+    {
+        $this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
+        $this->expectExceptionMessage('Redis connection failed');
+        RedisCache::createConnection($dsn);
+    }
+
+    public function provideFailedCreateConnection()
+    {
+        return [
+            ['redis://localhost:1234'],
+            ['redis://foo@localhost'],
+            ['redis://localhost/123'],
+        ];
+    }
+
+    /**
+     * @dataProvider provideInvalidCreateConnection
+     */
+    public function testInvalidCreateConnection($dsn)
+    {
+        $this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
+        $this->expectExceptionMessage('Invalid Redis DSN');
+        RedisCache::createConnection($dsn);
+    }
+
+    public function provideInvalidCreateConnection()
+    {
+        return [
+            ['foo://localhost'],
+            ['redis://'],
+        ];
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/RedisClusterCacheTest.php b/vendor/symfony/cache/Tests/Simple/RedisClusterCacheTest.php
new file mode 100644
index 0000000..c5115c7
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/RedisClusterCacheTest.php
@@ -0,0 +1,30 @@
+<?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\Cache\Tests\Simple;
+
+/**
+ * @group legacy
+ */
+class RedisClusterCacheTest extends AbstractRedisCacheTest
+{
+    public static function setUpBeforeClass(): void
+    {
+        if (!class_exists('RedisCluster')) {
+            self::markTestSkipped('The RedisCluster class is required.');
+        }
+        if (!$hosts = getenv('REDIS_CLUSTER_HOSTS')) {
+            self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.');
+        }
+
+        self::$redis = new \RedisCluster(null, explode(' ', $hosts));
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Simple/TraceableCacheTest.php b/vendor/symfony/cache/Tests/Simple/TraceableCacheTest.php
new file mode 100644
index 0000000..c2e8a47
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Simple/TraceableCacheTest.php
@@ -0,0 +1,172 @@
+<?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\Cache\Tests\Simple;
+
+use Symfony\Component\Cache\Simple\FilesystemCache;
+use Symfony\Component\Cache\Simple\TraceableCache;
+
+/**
+ * @group time-sensitive
+ * @group legacy
+ */
+class TraceableCacheTest extends CacheTestCase
+{
+    protected $skippedTests = [
+        'testPrune' => 'TraceableCache just proxies',
+    ];
+
+    public function createSimpleCache($defaultLifetime = 0)
+    {
+        return new TraceableCache(new FilesystemCache('', $defaultLifetime));
+    }
+
+    public function testGetMissTrace()
+    {
+        $pool = $this->createSimpleCache();
+        $pool->get('k');
+        $calls = $pool->getCalls();
+        $this->assertCount(1, $calls);
+
+        $call = $calls[0];
+        $this->assertSame('get', $call->name);
+        $this->assertSame(['k' => false], $call->result);
+        $this->assertSame(0, $call->hits);
+        $this->assertSame(1, $call->misses);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+
+    public function testGetHitTrace()
+    {
+        $pool = $this->createSimpleCache();
+        $pool->set('k', 'foo');
+        $pool->get('k');
+        $calls = $pool->getCalls();
+        $this->assertCount(2, $calls);
+
+        $call = $calls[1];
+        $this->assertSame(1, $call->hits);
+        $this->assertSame(0, $call->misses);
+    }
+
+    public function testGetMultipleMissTrace()
+    {
+        $pool = $this->createSimpleCache();
+        $pool->set('k1', 123);
+        $values = $pool->getMultiple(['k0', 'k1']);
+        foreach ($values as $value) {
+        }
+        $calls = $pool->getCalls();
+        $this->assertCount(2, $calls);
+
+        $call = $calls[1];
+        $this->assertSame('getMultiple', $call->name);
+        $this->assertSame(['k1' => true, 'k0' => false], $call->result);
+        $this->assertSame(1, $call->misses);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+
+    public function testHasMissTrace()
+    {
+        $pool = $this->createSimpleCache();
+        $pool->has('k');
+        $calls = $pool->getCalls();
+        $this->assertCount(1, $calls);
+
+        $call = $calls[0];
+        $this->assertSame('has', $call->name);
+        $this->assertSame(['k' => false], $call->result);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+
+    public function testHasHitTrace()
+    {
+        $pool = $this->createSimpleCache();
+        $pool->set('k', 'foo');
+        $pool->has('k');
+        $calls = $pool->getCalls();
+        $this->assertCount(2, $calls);
+
+        $call = $calls[1];
+        $this->assertSame('has', $call->name);
+        $this->assertSame(['k' => true], $call->result);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+
+    public function testDeleteTrace()
+    {
+        $pool = $this->createSimpleCache();
+        $pool->delete('k');
+        $calls = $pool->getCalls();
+        $this->assertCount(1, $calls);
+
+        $call = $calls[0];
+        $this->assertSame('delete', $call->name);
+        $this->assertSame(['k' => true], $call->result);
+        $this->assertSame(0, $call->hits);
+        $this->assertSame(0, $call->misses);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+
+    public function testDeleteMultipleTrace()
+    {
+        $pool = $this->createSimpleCache();
+        $arg = ['k0', 'k1'];
+        $pool->deleteMultiple($arg);
+        $calls = $pool->getCalls();
+        $this->assertCount(1, $calls);
+
+        $call = $calls[0];
+        $this->assertSame('deleteMultiple', $call->name);
+        $this->assertSame(['keys' => $arg, 'result' => true], $call->result);
+        $this->assertSame(0, $call->hits);
+        $this->assertSame(0, $call->misses);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+
+    public function testTraceSetTrace()
+    {
+        $pool = $this->createSimpleCache();
+        $pool->set('k', 'foo');
+        $calls = $pool->getCalls();
+        $this->assertCount(1, $calls);
+
+        $call = $calls[0];
+        $this->assertSame('set', $call->name);
+        $this->assertSame(['k' => true], $call->result);
+        $this->assertSame(0, $call->hits);
+        $this->assertSame(0, $call->misses);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+
+    public function testSetMultipleTrace()
+    {
+        $pool = $this->createSimpleCache();
+        $pool->setMultiple(['k' => 'foo']);
+        $calls = $pool->getCalls();
+        $this->assertCount(1, $calls);
+
+        $call = $calls[0];
+        $this->assertSame('setMultiple', $call->name);
+        $this->assertSame(['keys' => ['k'], 'result' => true], $call->result);
+        $this->assertSame(0, $call->hits);
+        $this->assertSame(0, $call->misses);
+        $this->assertNotEmpty($call->start);
+        $this->assertNotEmpty($call->end);
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Traits/PdoPruneableTrait.php b/vendor/symfony/cache/Tests/Traits/PdoPruneableTrait.php
new file mode 100644
index 0000000..3b1e112
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Traits/PdoPruneableTrait.php
@@ -0,0 +1,34 @@
+<?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\Cache\Tests\Traits;
+
+trait PdoPruneableTrait
+{
+    protected function isPruned($cache, $name)
+    {
+        $o = new \ReflectionObject($cache);
+
+        if (!$o->hasMethod('getConnection')) {
+            self::fail('Cache does not have "getConnection()" method.');
+        }
+
+        $getPdoConn = $o->getMethod('getConnection');
+        $getPdoConn->setAccessible(true);
+
+        /** @var \Doctrine\DBAL\Statement $select */
+        $select = $getPdoConn->invoke($cache)->prepare('SELECT 1 FROM cache_items WHERE item_id LIKE :id');
+        $select->bindValue(':id', sprintf('%%%s', $name));
+        $select->execute();
+
+        return 0 === \count($select->fetchAll(\PDO::FETCH_COLUMN));
+    }
+}
diff --git a/vendor/symfony/cache/Tests/Traits/TagAwareTestTrait.php b/vendor/symfony/cache/Tests/Traits/TagAwareTestTrait.php
new file mode 100644
index 0000000..9c6ce39
--- /dev/null
+++ b/vendor/symfony/cache/Tests/Traits/TagAwareTestTrait.php
@@ -0,0 +1,158 @@
+<?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\Cache\Tests\Traits;
+
+use Symfony\Component\Cache\CacheItem;
+
+/**
+ * Common assertions for TagAware adapters.
+ *
+ * @method \Symfony\Component\Cache\Adapter\TagAwareAdapterInterface createCachePool() Must be implemented by TestCase
+ */
+trait TagAwareTestTrait
+{
+    public function testInvalidTag()
+    {
+        $this->expectException('Psr\Cache\InvalidArgumentException');
+        $pool = $this->createCachePool();
+        $item = $pool->getItem('foo');
+        $item->tag(':');
+    }
+
+    public function testInvalidateTags()
+    {
+        $pool = $this->createCachePool();
+
+        $i0 = $pool->getItem('i0');
+        $i1 = $pool->getItem('i1');
+        $i2 = $pool->getItem('i2');
+        $i3 = $pool->getItem('i3');
+        $foo = $pool->getItem('foo');
+
+        $pool->save($i0->tag('bar'));
+        $pool->save($i1->tag('foo'));
+        $pool->save($i2->tag('foo')->tag('bar'));
+        $pool->save($i3->tag('foo')->tag('baz'));
+        $pool->save($foo);
+
+        $pool->invalidateTags(['bar']);
+
+        $this->assertFalse($pool->getItem('i0')->isHit());
+        $this->assertTrue($pool->getItem('i1')->isHit());
+        $this->assertFalse($pool->getItem('i2')->isHit());
+        $this->assertTrue($pool->getItem('i3')->isHit());
+        $this->assertTrue($pool->getItem('foo')->isHit());
+
+        $pool->invalidateTags(['foo']);
+
+        $this->assertFalse($pool->getItem('i1')->isHit());
+        $this->assertFalse($pool->getItem('i3')->isHit());
+        $this->assertTrue($pool->getItem('foo')->isHit());
+
+        $anotherPoolInstance = $this->createCachePool();
+
+        $this->assertFalse($anotherPoolInstance->getItem('i1')->isHit());
+        $this->assertFalse($anotherPoolInstance->getItem('i3')->isHit());
+        $this->assertTrue($anotherPoolInstance->getItem('foo')->isHit());
+    }
+
+    public function testInvalidateCommits()
+    {
+        $pool = $this->createCachePool();
+
+        $foo = $pool->getItem('foo');
+        $foo->tag('tag');
+
+        $pool->saveDeferred($foo->set('foo'));
+        $pool->invalidateTags(['tag']);
+
+        // ??: This seems to contradict a bit logic in deleteItems, where it does unset($this->deferred[$key]); on key matches
+
+        $foo = $pool->getItem('foo');
+
+        $this->assertTrue($foo->isHit());
+    }
+
+    public function testTagsAreCleanedOnSave()
+    {
+        $pool = $this->createCachePool();
+
+        $i = $pool->getItem('k');
+        $pool->save($i->tag('foo'));
+
+        $i = $pool->getItem('k');
+        $pool->save($i->tag('bar'));
+
+        $pool->invalidateTags(['foo']);
+        $this->assertTrue($pool->getItem('k')->isHit());
+    }
+
+    public function testTagsAreCleanedOnDelete()
+    {
+        $pool = $this->createCachePool();
+
+        $i = $pool->getItem('k');
+        $pool->save($i->tag('foo'));
+        $pool->deleteItem('k');
+
+        $pool->save($pool->getItem('k'));
+        $pool->invalidateTags(['foo']);
+
+        $this->assertTrue($pool->getItem('k')->isHit());
+    }
+
+    public function testTagItemExpiry()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $pool = $this->createCachePool(10);
+
+        $item = $pool->getItem('foo');
+        $item->tag(['baz']);
+        $item->expiresAfter(100);
+
+        $pool->save($item);
+        $pool->invalidateTags(['baz']);
+        $this->assertFalse($pool->getItem('foo')->isHit());
+
+        sleep(20);
+
+        $this->assertFalse($pool->getItem('foo')->isHit());
+    }
+
+    /**
+     * @group legacy
+     */
+    public function testGetPreviousTags()
+    {
+        $pool = $this->createCachePool();
+
+        $i = $pool->getItem('k');
+        $pool->save($i->tag('foo'));
+
+        $i = $pool->getItem('k');
+        $this->assertSame(['foo' => 'foo'], $i->getPreviousTags());
+    }
+
+    public function testGetMetadata()
+    {
+        $pool = $this->createCachePool();
+
+        $i = $pool->getItem('k');
+        $pool->save($i->tag('foo'));
+
+        $i = $pool->getItem('k');
+        $this->assertSame(['foo' => 'foo'], $i->getMetadata()[CacheItem::METADATA_TAGS]);
+    }
+}
diff --git a/vendor/symfony/cache/Traits/AbstractAdapterTrait.php b/vendor/symfony/cache/Traits/AbstractAdapterTrait.php
new file mode 100644
index 0000000..445f865
--- /dev/null
+++ b/vendor/symfony/cache/Traits/AbstractAdapterTrait.php
@@ -0,0 +1,149 @@
+<?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\Cache\Traits;
+
+use Psr\Cache\CacheItemInterface;
+use Symfony\Component\Cache\CacheItem;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+trait AbstractAdapterTrait
+{
+    use AbstractTrait;
+
+    /**
+     * @var \Closure needs to be set by class, signature is function(string <key>, mixed <value>, bool <isHit>)
+     */
+    private $createCacheItem;
+
+    /**
+     * @var \Closure needs to be set by class, signature is function(array <deferred>, string <namespace>, array <&expiredIds>)
+     */
+    private $mergeByLifetime;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItem($key)
+    {
+        if ($this->deferred) {
+            $this->commit();
+        }
+        $id = $this->getId($key);
+
+        $f = $this->createCacheItem;
+        $isHit = false;
+        $value = null;
+
+        try {
+            foreach ($this->doFetch([$id]) as $value) {
+                $isHit = true;
+            }
+        } catch (\Exception $e) {
+            CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
+        }
+
+        return $f($key, $value, $isHit);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getItems(array $keys = [])
+    {
+        if ($this->deferred) {
+            $this->commit();
+        }
+        $ids = [];
+
+        foreach ($keys as $key) {
+            $ids[] = $this->getId($key);
+        }
+        try {
+            $items = $this->doFetch($ids);
+        } catch (\Exception $e) {
+            CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e]);
+            $items = [];
+        }
+        $ids = array_combine($ids, $keys);
+
+        return $this->generateItems($items, $ids);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save(CacheItemInterface $item)
+    {
+        if (!$item instanceof CacheItem) {
+            return false;
+        }
+        $this->deferred[$item->getKey()] = $item;
+
+        return $this->commit();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function saveDeferred(CacheItemInterface $item)
+    {
+        if (!$item instanceof CacheItem) {
+            return false;
+        }
+        $this->deferred[$item->getKey()] = $item;
+
+        return true;
+    }
+
+    public function __sleep()
+    {
+        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+    }
+
+    public function __wakeup()
+    {
+        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+    }
+
+    public function __destruct()
+    {
+        if ($this->deferred) {
+            $this->commit();
+        }
+    }
+
+    private function generateItems($items, &$keys)
+    {
+        $f = $this->createCacheItem;
+
+        try {
+            foreach ($items as $id => $value) {
+                if (!isset($keys[$id])) {
+                    $id = key($keys);
+                }
+                $key = $keys[$id];
+                unset($keys[$id]);
+                yield $key => $f($key, $value, true);
+            }
+        } catch (\Exception $e) {
+            CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e]);
+        }
+
+        foreach ($keys as $key) {
+            yield $key => $f($key, null, false);
+        }
+    }
+}
diff --git a/vendor/symfony/cache/Traits/AbstractTrait.php b/vendor/symfony/cache/Traits/AbstractTrait.php
new file mode 100644
index 0000000..7c8ccab
--- /dev/null
+++ b/vendor/symfony/cache/Traits/AbstractTrait.php
@@ -0,0 +1,284 @@
+<?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\Cache\Traits;
+
+use Psr\Log\LoggerAwareTrait;
+use Symfony\Component\Cache\CacheItem;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+trait AbstractTrait
+{
+    use LoggerAwareTrait;
+
+    private $namespace;
+    private $namespaceVersion = '';
+    private $versioningIsEnabled = false;
+    private $deferred = [];
+    private $ids = [];
+
+    /**
+     * @var int|null The maximum length to enforce for identifiers or null when no limit applies
+     */
+    protected $maxIdLength;
+
+    /**
+     * Fetches several cache items.
+     *
+     * @param array $ids The cache identifiers to fetch
+     *
+     * @return array|\Traversable The corresponding values found in the cache
+     */
+    abstract protected function doFetch(array $ids);
+
+    /**
+     * Confirms if the cache contains specified cache item.
+     *
+     * @param string $id The identifier for which to check existence
+     *
+     * @return bool True if item exists in the cache, false otherwise
+     */
+    abstract protected function doHave($id);
+
+    /**
+     * Deletes all items in the pool.
+     *
+     * @param string $namespace The prefix used for all identifiers managed by this pool
+     *
+     * @return bool True if the pool was successfully cleared, false otherwise
+     */
+    abstract protected function doClear($namespace);
+
+    /**
+     * Removes multiple items from the pool.
+     *
+     * @param array $ids An array of identifiers that should be removed from the pool
+     *
+     * @return bool True if the items were successfully removed, false otherwise
+     */
+    abstract protected function doDelete(array $ids);
+
+    /**
+     * Persists several cache items immediately.
+     *
+     * @param array $values   The values to cache, indexed by their cache identifier
+     * @param int   $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning
+     *
+     * @return array|bool The identifiers that failed to be cached or a boolean stating if caching succeeded or not
+     */
+    abstract protected function doSave(array $values, $lifetime);
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasItem($key)
+    {
+        $id = $this->getId($key);
+
+        if (isset($this->deferred[$key])) {
+            $this->commit();
+        }
+
+        try {
+            return $this->doHave($id);
+        } catch (\Exception $e) {
+            CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached: '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
+
+            return false;
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        $this->deferred = [];
+        if ($cleared = $this->versioningIsEnabled) {
+            $namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), static::NS_SEPARATOR, 5);
+            try {
+                $cleared = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0);
+            } catch (\Exception $e) {
+                $cleared = false;
+            }
+            if ($cleared = true === $cleared || [] === $cleared) {
+                $this->namespaceVersion = $namespaceVersion;
+                $this->ids = [];
+            }
+        }
+
+        try {
+            return $this->doClear($this->namespace) || $cleared;
+        } catch (\Exception $e) {
+            CacheItem::log($this->logger, 'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e]);
+
+            return false;
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItem($key)
+    {
+        return $this->deleteItems([$key]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItems(array $keys)
+    {
+        $ids = [];
+
+        foreach ($keys as $key) {
+            $ids[$key] = $this->getId($key);
+            unset($this->deferred[$key]);
+        }
+
+        try {
+            if ($this->doDelete($ids)) {
+                return true;
+            }
+        } catch (\Exception $e) {
+        }
+
+        $ok = true;
+
+        // When bulk-delete failed, retry each item individually
+        foreach ($ids as $key => $id) {
+            try {
+                $e = null;
+                if ($this->doDelete([$id])) {
+                    continue;
+                }
+            } catch (\Exception $e) {
+            }
+            $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
+            CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]);
+            $ok = false;
+        }
+
+        return $ok;
+    }
+
+    /**
+     * Enables/disables versioning of items.
+     *
+     * When versioning is enabled, clearing the cache is atomic and doesn't require listing existing keys to proceed,
+     * but old keys may need garbage collection and extra round-trips to the back-end are required.
+     *
+     * Calling this method also clears the memoized namespace version and thus forces a resynchonization of it.
+     *
+     * @param bool $enable
+     *
+     * @return bool the previous state of versioning
+     */
+    public function enableVersioning($enable = true)
+    {
+        $wasEnabled = $this->versioningIsEnabled;
+        $this->versioningIsEnabled = (bool) $enable;
+        $this->namespaceVersion = '';
+        $this->ids = [];
+
+        return $wasEnabled;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function reset()
+    {
+        if ($this->deferred) {
+            $this->commit();
+        }
+        $this->namespaceVersion = '';
+        $this->ids = [];
+    }
+
+    /**
+     * Like the native unserialize() function but throws an exception if anything goes wrong.
+     *
+     * @param string $value
+     *
+     * @return mixed
+     *
+     * @throws \Exception
+     *
+     * @deprecated since Symfony 4.2, use DefaultMarshaller instead.
+     */
+    protected static function unserialize($value)
+    {
+        @trigger_error(sprintf('The "%s::unserialize()" method is deprecated since Symfony 4.2, use DefaultMarshaller instead.', __CLASS__), E_USER_DEPRECATED);
+
+        if ('b:0;' === $value) {
+            return false;
+        }
+        $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
+        try {
+            if (false !== $value = unserialize($value)) {
+                return $value;
+            }
+            throw new \DomainException('Failed to unserialize cached value');
+        } catch (\Error $e) {
+            throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
+        } finally {
+            ini_set('unserialize_callback_func', $unserializeCallbackHandler);
+        }
+    }
+
+    private function getId($key)
+    {
+        if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
+            $this->ids = [];
+            $this->namespaceVersion = '1'.static::NS_SEPARATOR;
+            try {
+                foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) {
+                    $this->namespaceVersion = $v;
+                }
+                if ('1'.static::NS_SEPARATOR === $this->namespaceVersion) {
+                    $this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), static::NS_SEPARATOR, 5);
+                    $this->doSave([static::NS_SEPARATOR.$this->namespace => $this->namespaceVersion], 0);
+                }
+            } catch (\Exception $e) {
+            }
+        }
+
+        if (\is_string($key) && isset($this->ids[$key])) {
+            return $this->namespace.$this->namespaceVersion.$this->ids[$key];
+        }
+        CacheItem::validateKey($key);
+        $this->ids[$key] = $key;
+
+        if (null === $this->maxIdLength) {
+            return $this->namespace.$this->namespaceVersion.$key;
+        }
+        if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) {
+            // Use MD5 to favor speed over security, which is not an issue here
+            $this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2));
+            $id = $this->namespace.$this->namespaceVersion.$id;
+        }
+
+        return $id;
+    }
+
+    /**
+     * @internal
+     */
+    public static function handleUnserializeCallback($class)
+    {
+        throw new \DomainException('Class not found: '.$class);
+    }
+}
diff --git a/vendor/symfony/cache/Traits/ApcuTrait.php b/vendor/symfony/cache/Traits/ApcuTrait.php
new file mode 100644
index 0000000..c86b043
--- /dev/null
+++ b/vendor/symfony/cache/Traits/ApcuTrait.php
@@ -0,0 +1,121 @@
+<?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\Cache\Traits;
+
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\CacheException;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+trait ApcuTrait
+{
+    public static function isSupported()
+    {
+        return \function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN);
+    }
+
+    private function init($namespace, $defaultLifetime, $version)
+    {
+        if (!static::isSupported()) {
+            throw new CacheException('APCu is not enabled');
+        }
+        if ('cli' === \PHP_SAPI) {
+            ini_set('apc.use_request_time', 0);
+        }
+        parent::__construct($namespace, $defaultLifetime);
+
+        if (null !== $version) {
+            CacheItem::validateKey($version);
+
+            if (!apcu_exists($version.'@'.$namespace)) {
+                $this->doClear($namespace);
+                apcu_add($version.'@'.$namespace, null);
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doFetch(array $ids)
+    {
+        $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
+        try {
+            $values = [];
+            foreach (apcu_fetch($ids, $ok) ?: [] as $k => $v) {
+                if (null !== $v || $ok) {
+                    $values[$k] = $v;
+                }
+            }
+
+            return $values;
+        } catch (\Error $e) {
+            throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
+        } finally {
+            ini_set('unserialize_callback_func', $unserializeCallbackHandler);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doHave($id)
+    {
+        return apcu_exists($id);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doClear($namespace)
+    {
+        return isset($namespace[0]) && class_exists('APCuIterator', false) && ('cli' !== \PHP_SAPI || filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN))
+            ? apcu_delete(new \APCuIterator(sprintf('/^%s/', preg_quote($namespace, '/')), APC_ITER_KEY))
+            : apcu_clear_cache();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDelete(array $ids)
+    {
+        foreach ($ids as $id) {
+            apcu_delete($id);
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doSave(array $values, $lifetime)
+    {
+        try {
+            if (false === $failures = apcu_store($values, null, $lifetime)) {
+                $failures = $values;
+            }
+
+            return array_keys($failures);
+        } catch (\Throwable $e) {
+            if (1 === \count($values)) {
+                // Workaround https://github.com/krakjoe/apcu/issues/170
+                apcu_delete(key($values));
+            }
+
+            throw $e;
+        }
+    }
+}
diff --git a/vendor/symfony/cache/Traits/ArrayTrait.php b/vendor/symfony/cache/Traits/ArrayTrait.php
new file mode 100644
index 0000000..497504c
--- /dev/null
+++ b/vendor/symfony/cache/Traits/ArrayTrait.php
@@ -0,0 +1,165 @@
+<?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\Cache\Traits;
+
+use Psr\Log\LoggerAwareTrait;
+use Symfony\Component\Cache\CacheItem;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+trait ArrayTrait
+{
+    use LoggerAwareTrait;
+
+    private $storeSerialized;
+    private $values = [];
+    private $expiries = [];
+
+    /**
+     * Returns all cached values, with cache miss as null.
+     *
+     * @return array
+     */
+    public function getValues()
+    {
+        if (!$this->storeSerialized) {
+            return $this->values;
+        }
+
+        $values = $this->values;
+        foreach ($values as $k => $v) {
+            if (null === $v || 'N;' === $v) {
+                continue;
+            }
+            if (!\is_string($v) || !isset($v[2]) || ':' !== $v[1]) {
+                $values[$k] = serialize($v);
+            }
+        }
+
+        return $values;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasItem($key)
+    {
+        if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > microtime(true)) {
+            return true;
+        }
+        CacheItem::validateKey($key);
+
+        return isset($this->expiries[$key]) && !$this->deleteItem($key);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        $this->values = $this->expiries = [];
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteItem($key)
+    {
+        if (!\is_string($key) || !isset($this->expiries[$key])) {
+            CacheItem::validateKey($key);
+        }
+        unset($this->values[$key], $this->expiries[$key]);
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function reset()
+    {
+        $this->clear();
+    }
+
+    private function generateItems(array $keys, $now, $f)
+    {
+        foreach ($keys as $i => $key) {
+            if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) {
+                $this->values[$key] = $value = null;
+            } else {
+                $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
+            }
+            unset($keys[$i]);
+
+            yield $key => $f($key, $value, $isHit);
+        }
+
+        foreach ($keys as $key) {
+            yield $key => $f($key, null, false);
+        }
+    }
+
+    private function freeze($value, $key)
+    {
+        if (null === $value) {
+            return 'N;';
+        }
+        if (\is_string($value)) {
+            // Serialize strings if they could be confused with serialized objects or arrays
+            if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
+                return serialize($value);
+            }
+        } elseif (!is_scalar($value)) {
+            try {
+                $serialized = serialize($value);
+            } catch (\Exception $e) {
+                $type = \is_object($value) ? \get_class($value) : \gettype($value);
+                $message = sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage());
+                CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]);
+
+                return null;
+            }
+            // Keep value serialized if it contains any objects or any internal references
+            if ('C' === $serialized[0] || 'O' === $serialized[0] || preg_match('/;[OCRr]:[1-9]/', $serialized)) {
+                return $serialized;
+            }
+        }
+
+        return $value;
+    }
+
+    private function unfreeze(string $key, bool &$isHit)
+    {
+        if ('N;' === $value = $this->values[$key]) {
+            return null;
+        }
+        if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
+            try {
+                $value = unserialize($value);
+            } catch (\Exception $e) {
+                CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
+                $value = false;
+            }
+            if (false === $value) {
+                $this->values[$key] = $value = null;
+                $isHit = false;
+            }
+        }
+
+        return $value;
+    }
+}
diff --git a/vendor/symfony/cache/Traits/ContractsTrait.php b/vendor/symfony/cache/Traits/ContractsTrait.php
new file mode 100644
index 0000000..c5827c3
--- /dev/null
+++ b/vendor/symfony/cache/Traits/ContractsTrait.php
@@ -0,0 +1,97 @@
+<?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\Cache\Traits;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Cache\Adapter\AdapterInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\LockRegistry;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Cache\CacheTrait;
+use Symfony\Contracts\Cache\ItemInterface;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+trait ContractsTrait
+{
+    use CacheTrait {
+        doGet as private contractsGet;
+    }
+
+    private $callbackWrapper = [LockRegistry::class, 'compute'];
+    private $computing = [];
+
+    /**
+     * Wraps the callback passed to ->get() in a callable.
+     *
+     * @return callable the previous callback wrapper
+     */
+    public function setCallbackWrapper(?callable $callbackWrapper): callable
+    {
+        $previousWrapper = $this->callbackWrapper;
+        $this->callbackWrapper = $callbackWrapper ?? function (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) {
+            return $callback($item, $save);
+        };
+
+        return $previousWrapper;
+    }
+
+    private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null)
+    {
+        if (0 > $beta = $beta ?? 1.0) {
+            throw new InvalidArgumentException(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', \get_class($this), $beta));
+        }
+
+        static $setMetadata;
+
+        $setMetadata = $setMetadata ?? \Closure::bind(
+            static function (CacheItem $item, float $startTime, ?array &$metadata) {
+                if ($item->expiry > $endTime = microtime(true)) {
+                    $item->newMetadata[CacheItem::METADATA_EXPIRY] = $metadata[CacheItem::METADATA_EXPIRY] = $item->expiry;
+                    $item->newMetadata[CacheItem::METADATA_CTIME] = $metadata[CacheItem::METADATA_CTIME] = (int) ceil(1000 * ($endTime - $startTime));
+                } else {
+                    unset($metadata[CacheItem::METADATA_EXPIRY], $metadata[CacheItem::METADATA_CTIME]);
+                }
+            },
+            null,
+            CacheItem::class
+        );
+
+        return $this->contractsGet($pool, $key, function (CacheItem $item, bool &$save) use ($pool, $callback, $setMetadata, &$metadata, $key) {
+            // don't wrap nor save recursive calls
+            if (isset($this->computing[$key])) {
+                $value = $callback($item, $save);
+                $save = false;
+
+                return $value;
+            }
+
+            $this->computing[$key] = $key;
+            $startTime = microtime(true);
+
+            try {
+                $value = ($this->callbackWrapper)($callback, $item, $save, $pool, function (CacheItem $item) use ($setMetadata, $startTime, &$metadata) {
+                    $setMetadata($item, $startTime, $metadata);
+                }, $this->logger ?? null);
+                $setMetadata($item, $startTime, $metadata);
+
+                return $value;
+            } finally {
+                unset($this->computing[$key]);
+            }
+        }, $beta, $metadata, $this->logger ?? null);
+    }
+}
diff --git a/vendor/symfony/cache/Traits/DoctrineTrait.php b/vendor/symfony/cache/Traits/DoctrineTrait.php
new file mode 100644
index 0000000..c87ecab
--- /dev/null
+++ b/vendor/symfony/cache/Traits/DoctrineTrait.php
@@ -0,0 +1,98 @@
+<?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\Cache\Traits;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+trait DoctrineTrait
+{
+    private $provider;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function reset()
+    {
+        parent::reset();
+        $this->provider->setNamespace($this->provider->getNamespace());
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doFetch(array $ids)
+    {
+        $unserializeCallbackHandler = ini_set('unserialize_callback_func', parent::class.'::handleUnserializeCallback');
+        try {
+            return $this->provider->fetchMultiple($ids);
+        } catch (\Error $e) {
+            $trace = $e->getTrace();
+
+            if (isset($trace[0]['function']) && !isset($trace[0]['class'])) {
+                switch ($trace[0]['function']) {
+                    case 'unserialize':
+                    case 'apcu_fetch':
+                    case 'apc_fetch':
+                        throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
+                }
+            }
+
+            throw $e;
+        } finally {
+            ini_set('unserialize_callback_func', $unserializeCallbackHandler);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doHave($id)
+    {
+        return $this->provider->contains($id);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doClear($namespace)
+    {
+        $namespace = $this->provider->getNamespace();
+
+        return isset($namespace[0])
+            ? $this->provider->deleteAll()
+            : $this->provider->flushAll();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDelete(array $ids)
+    {
+        $ok = true;
+        foreach ($ids as $id) {
+            $ok = $this->provider->delete($id) && $ok;
+        }
+
+        return $ok;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doSave(array $values, $lifetime)
+    {
+        return $this->provider->saveMultiple($values, $lifetime);
+    }
+}
diff --git a/vendor/symfony/cache/Traits/FilesystemCommonTrait.php b/vendor/symfony/cache/Traits/FilesystemCommonTrait.php
new file mode 100644
index 0000000..37e1fd1
--- /dev/null
+++ b/vendor/symfony/cache/Traits/FilesystemCommonTrait.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\Cache\Traits;
+
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+trait FilesystemCommonTrait
+{
+    private $directory;
+    private $tmp;
+
+    private function init($namespace, $directory)
+    {
+        if (!isset($directory[0])) {
+            $directory = sys_get_temp_dir().'/symfony-cache';
+        } else {
+            $directory = realpath($directory) ?: $directory;
+        }
+        if (isset($namespace[0])) {
+            if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) {
+                throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
+            }
+            $directory .= \DIRECTORY_SEPARATOR.$namespace;
+        }
+        if (!file_exists($directory)) {
+            @mkdir($directory, 0777, true);
+        }
+        $directory .= \DIRECTORY_SEPARATOR;
+        // On Windows the whole path is limited to 258 chars
+        if ('\\' === \DIRECTORY_SEPARATOR && \strlen($directory) > 234) {
+            throw new InvalidArgumentException(sprintf('Cache directory too long (%s)', $directory));
+        }
+
+        $this->directory = $directory;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doClear($namespace)
+    {
+        $ok = true;
+
+        foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS)) as $file) {
+            $ok = ($file->isDir() || $this->doUnlink($file) || !file_exists($file)) && $ok;
+        }
+
+        return $ok;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDelete(array $ids)
+    {
+        $ok = true;
+
+        foreach ($ids as $id) {
+            $file = $this->getFile($id);
+            $ok = (!file_exists($file) || $this->doUnlink($file) || !file_exists($file)) && $ok;
+        }
+
+        return $ok;
+    }
+
+    protected function doUnlink($file)
+    {
+        return @unlink($file);
+    }
+
+    private function write($file, $data, $expiresAt = null)
+    {
+        set_error_handler(__CLASS__.'::throwError');
+        try {
+            if (null === $this->tmp) {
+                $this->tmp = $this->directory.uniqid('', true);
+            }
+            file_put_contents($this->tmp, $data);
+
+            if (null !== $expiresAt) {
+                touch($this->tmp, $expiresAt);
+            }
+
+            return rename($this->tmp, $file);
+        } finally {
+            restore_error_handler();
+        }
+    }
+
+    private function getFile($id, $mkdir = false, string $directory = null)
+    {
+        // Use MD5 to favor speed over security, which is not an issue here
+        $hash = str_replace('/', '-', base64_encode(hash('md5', static::class.$id, true)));
+        $dir = ($directory ?? $this->directory).strtoupper($hash[0].\DIRECTORY_SEPARATOR.$hash[1].\DIRECTORY_SEPARATOR);
+
+        if ($mkdir && !file_exists($dir)) {
+            @mkdir($dir, 0777, true);
+        }
+
+        return $dir.substr($hash, 2, 20);
+    }
+
+    /**
+     * @internal
+     */
+    public static function throwError($type, $message, $file, $line)
+    {
+        throw new \ErrorException($message, 0, $type, $file, $line);
+    }
+
+    public function __sleep()
+    {
+        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+    }
+
+    public function __wakeup()
+    {
+        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+    }
+
+    public function __destruct()
+    {
+        if (method_exists(parent::class, '__destruct')) {
+            parent::__destruct();
+        }
+        if (null !== $this->tmp && file_exists($this->tmp)) {
+            unlink($this->tmp);
+        }
+    }
+}
diff --git a/vendor/symfony/cache/Traits/FilesystemTrait.php b/vendor/symfony/cache/Traits/FilesystemTrait.php
new file mode 100644
index 0000000..9c444f9
--- /dev/null
+++ b/vendor/symfony/cache/Traits/FilesystemTrait.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\Cache\Traits;
+
+use Symfony\Component\Cache\Exception\CacheException;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ * @author Rob Frawley 2nd <rmf@src.run>
+ *
+ * @internal
+ */
+trait FilesystemTrait
+{
+    use FilesystemCommonTrait;
+
+    private $marshaller;
+
+    /**
+     * @return bool
+     */
+    public function prune()
+    {
+        $time = time();
+        $pruned = true;
+
+        foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
+            if (!$h = @fopen($file, 'rb')) {
+                continue;
+            }
+
+            if (($expiresAt = (int) fgets($h)) && $time >= $expiresAt) {
+                fclose($h);
+                $pruned = @unlink($file) && !file_exists($file) && $pruned;
+            } else {
+                fclose($h);
+            }
+        }
+
+        return $pruned;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doFetch(array $ids)
+    {
+        $values = [];
+        $now = time();
+
+        foreach ($ids as $id) {
+            $file = $this->getFile($id);
+            if (!file_exists($file) || !$h = @fopen($file, 'rb')) {
+                continue;
+            }
+            if (($expiresAt = (int) fgets($h)) && $now >= $expiresAt) {
+                fclose($h);
+                @unlink($file);
+            } else {
+                $i = rawurldecode(rtrim(fgets($h)));
+                $value = stream_get_contents($h);
+                fclose($h);
+                if ($i === $id) {
+                    $values[$id] = $this->marshaller->unmarshall($value);
+                }
+            }
+        }
+
+        return $values;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doHave($id)
+    {
+        $file = $this->getFile($id);
+
+        return file_exists($file) && (@filemtime($file) > time() || $this->doFetch([$id]));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doSave(array $values, $lifetime)
+    {
+        $expiresAt = $lifetime ? (time() + $lifetime) : 0;
+        $values = $this->marshaller->marshall($values, $failed);
+
+        foreach ($values as $id => $value) {
+            if (!$this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".$value, $expiresAt)) {
+                $failed[] = $id;
+            }
+        }
+
+        if ($failed && !is_writable($this->directory)) {
+            throw new CacheException(sprintf('Cache directory is not writable (%s)', $this->directory));
+        }
+
+        return $failed;
+    }
+}
diff --git a/vendor/symfony/cache/Traits/MemcachedTrait.php b/vendor/symfony/cache/Traits/MemcachedTrait.php
new file mode 100644
index 0000000..ccfc66b
--- /dev/null
+++ b/vendor/symfony/cache/Traits/MemcachedTrait.php
@@ -0,0 +1,329 @@
+<?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\Cache\Traits;
+
+use Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+
+/**
+ * @author Rob Frawley 2nd <rmf@src.run>
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+trait MemcachedTrait
+{
+    private static $defaultClientOptions = [
+        'persistent_id' => null,
+        'username' => null,
+        'password' => null,
+        \Memcached::OPT_SERIALIZER => \Memcached::SERIALIZER_PHP,
+    ];
+
+    private $marshaller;
+    private $client;
+    private $lazyClient;
+
+    public static function isSupported()
+    {
+        return \extension_loaded('memcached') && version_compare(phpversion('memcached'), '2.2.0', '>=');
+    }
+
+    private function init(\Memcached $client, $namespace, $defaultLifetime, ?MarshallerInterface $marshaller)
+    {
+        if (!static::isSupported()) {
+            throw new CacheException('Memcached >= 2.2.0 is required');
+        }
+        if ('Memcached' === \get_class($client)) {
+            $opt = $client->getOption(\Memcached::OPT_SERIALIZER);
+            if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) {
+                throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
+            }
+            $this->maxIdLength -= \strlen($client->getOption(\Memcached::OPT_PREFIX_KEY));
+            $this->client = $client;
+        } else {
+            $this->lazyClient = $client;
+        }
+
+        parent::__construct($namespace, $defaultLifetime);
+        $this->enableVersioning();
+        $this->marshaller = $marshaller ?? new DefaultMarshaller();
+    }
+
+    /**
+     * Creates a Memcached instance.
+     *
+     * By default, the binary protocol, no block, and libketama compatible options are enabled.
+     *
+     * Examples for servers:
+     * - 'memcached://user:pass@localhost?weight=33'
+     * - [['localhost', 11211, 33]]
+     *
+     * @param array[]|string|string[] $servers An array of servers, a DSN, or an array of DSNs
+     * @param array                   $options An array of options
+     *
+     * @return \Memcached
+     *
+     * @throws \ErrorException When invalid options or servers are provided
+     */
+    public static function createConnection($servers, array $options = [])
+    {
+        if (\is_string($servers)) {
+            $servers = [$servers];
+        } elseif (!\is_array($servers)) {
+            throw new InvalidArgumentException(sprintf('MemcachedAdapter::createClient() expects array or string as first argument, %s given.', \gettype($servers)));
+        }
+        if (!static::isSupported()) {
+            throw new CacheException('Memcached >= 2.2.0 is required');
+        }
+        set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); });
+        try {
+            $options += static::$defaultClientOptions;
+            $client = new \Memcached($options['persistent_id']);
+            $username = $options['username'];
+            $password = $options['password'];
+
+            // parse any DSN in $servers
+            foreach ($servers as $i => $dsn) {
+                if (\is_array($dsn)) {
+                    continue;
+                }
+                if (0 !== strpos($dsn, 'memcached:')) {
+                    throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s does not start with "memcached:"', $dsn));
+                }
+                $params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) {
+                    if (!empty($m[2])) {
+                        list($username, $password) = explode(':', $m[2], 2) + [1 => null];
+                    }
+
+                    return 'file:'.($m[1] ?? '');
+                }, $dsn);
+                if (false === $params = parse_url($params)) {
+                    throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
+                }
+                $query = $hosts = [];
+                if (isset($params['query'])) {
+                    parse_str($params['query'], $query);
+
+                    if (isset($query['host'])) {
+                        if (!\is_array($hosts = $query['host'])) {
+                            throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
+                        }
+                        foreach ($hosts as $host => $weight) {
+                            if (false === $port = strrpos($host, ':')) {
+                                $hosts[$host] = [$host, 11211, (int) $weight];
+                            } else {
+                                $hosts[$host] = [substr($host, 0, $port), (int) substr($host, 1 + $port), (int) $weight];
+                            }
+                        }
+                        $hosts = array_values($hosts);
+                        unset($query['host']);
+                    }
+                    if ($hosts && !isset($params['host']) && !isset($params['path'])) {
+                        unset($servers[$i]);
+                        $servers = array_merge($servers, $hosts);
+                        continue;
+                    }
+                }
+                if (!isset($params['host']) && !isset($params['path'])) {
+                    throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
+                }
+                if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
+                    $params['weight'] = $m[1];
+                    $params['path'] = substr($params['path'], 0, -\strlen($m[0]));
+                }
+                $params += [
+                    'host' => isset($params['host']) ? $params['host'] : $params['path'],
+                    'port' => isset($params['host']) ? 11211 : null,
+                    'weight' => 0,
+                ];
+                if ($query) {
+                    $params += $query;
+                    $options = $query + $options;
+                }
+
+                $servers[$i] = [$params['host'], $params['port'], $params['weight']];
+
+                if ($hosts) {
+                    $servers = array_merge($servers, $hosts);
+                }
+            }
+
+            // set client's options
+            unset($options['persistent_id'], $options['username'], $options['password'], $options['weight'], $options['lazy']);
+            $options = array_change_key_case($options, CASE_UPPER);
+            $client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
+            $client->setOption(\Memcached::OPT_NO_BLOCK, true);
+            $client->setOption(\Memcached::OPT_TCP_NODELAY, true);
+            if (!\array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !\array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) {
+                $client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
+            }
+            foreach ($options as $name => $value) {
+                if (\is_int($name)) {
+                    continue;
+                }
+                if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) {
+                    $value = \constant('Memcached::'.$name.'_'.strtoupper($value));
+                }
+                $opt = \constant('Memcached::OPT_'.$name);
+
+                unset($options[$name]);
+                $options[$opt] = $value;
+            }
+            $client->setOptions($options);
+
+            // set client's servers, taking care of persistent connections
+            if (!$client->isPristine()) {
+                $oldServers = [];
+                foreach ($client->getServerList() as $server) {
+                    $oldServers[] = [$server['host'], $server['port']];
+                }
+
+                $newServers = [];
+                foreach ($servers as $server) {
+                    if (1 < \count($server)) {
+                        $server = array_values($server);
+                        unset($server[2]);
+                        $server[1] = (int) $server[1];
+                    }
+                    $newServers[] = $server;
+                }
+
+                if ($oldServers !== $newServers) {
+                    $client->resetServerList();
+                    $client->addServers($servers);
+                }
+            } else {
+                $client->addServers($servers);
+            }
+
+            if (null !== $username || null !== $password) {
+                if (!method_exists($client, 'setSaslAuthData')) {
+                    trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.');
+                }
+                $client->setSaslAuthData($username, $password);
+            }
+
+            return $client;
+        } finally {
+            restore_error_handler();
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doSave(array $values, $lifetime)
+    {
+        if (!$values = $this->marshaller->marshall($values, $failed)) {
+            return $failed;
+        }
+
+        if ($lifetime && $lifetime > 30 * 86400) {
+            $lifetime += time();
+        }
+
+        $encodedValues = [];
+        foreach ($values as $key => $value) {
+            $encodedValues[rawurlencode($key)] = $value;
+        }
+
+        return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)) ? $failed : false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doFetch(array $ids)
+    {
+        try {
+            $encodedIds = array_map('rawurlencode', $ids);
+
+            $encodedResult = $this->checkResultCode($this->getClient()->getMulti($encodedIds));
+
+            $result = [];
+            foreach ($encodedResult as $key => $value) {
+                $result[rawurldecode($key)] = $this->marshaller->unmarshall($value);
+            }
+
+            return $result;
+        } catch (\Error $e) {
+            throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doHave($id)
+    {
+        return false !== $this->getClient()->get(rawurlencode($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode());
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDelete(array $ids)
+    {
+        $ok = true;
+        $encodedIds = array_map('rawurlencode', $ids);
+        foreach ($this->checkResultCode($this->getClient()->deleteMulti($encodedIds)) as $result) {
+            if (\Memcached::RES_SUCCESS !== $result && \Memcached::RES_NOTFOUND !== $result) {
+                $ok = false;
+                break;
+            }
+        }
+
+        return $ok;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doClear($namespace)
+    {
+        return '' === $namespace && $this->getClient()->flush();
+    }
+
+    private function checkResultCode($result)
+    {
+        $code = $this->client->getResultCode();
+
+        if (\Memcached::RES_SUCCESS === $code || \Memcached::RES_NOTFOUND === $code) {
+            return $result;
+        }
+
+        throw new CacheException(sprintf('MemcachedAdapter client error: %s.', strtolower($this->client->getResultMessage())));
+    }
+
+    /**
+     * @return \Memcached
+     */
+    private function getClient()
+    {
+        if ($this->client) {
+            return $this->client;
+        }
+
+        $opt = $this->lazyClient->getOption(\Memcached::OPT_SERIALIZER);
+        if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) {
+            throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
+        }
+        if ('' !== $prefix = (string) $this->lazyClient->getOption(\Memcached::OPT_PREFIX_KEY)) {
+            throw new CacheException(sprintf('MemcachedAdapter: "prefix_key" option must be empty when using proxified connections, "%s" given.', $prefix));
+        }
+
+        return $this->client = $this->lazyClient;
+    }
+}
diff --git a/vendor/symfony/cache/Traits/PdoTrait.php b/vendor/symfony/cache/Traits/PdoTrait.php
new file mode 100644
index 0000000..ec34e72
--- /dev/null
+++ b/vendor/symfony/cache/Traits/PdoTrait.php
@@ -0,0 +1,424 @@
+<?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\Cache\Traits;
+
+use Doctrine\DBAL\Connection;
+use Doctrine\DBAL\DBALException;
+use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
+use Doctrine\DBAL\Exception\TableNotFoundException;
+use Doctrine\DBAL\Schema\Schema;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+
+/**
+ * @internal
+ */
+trait PdoTrait
+{
+    private $marshaller;
+    private $conn;
+    private $dsn;
+    private $driver;
+    private $serverVersion;
+    private $table = 'cache_items';
+    private $idCol = 'item_id';
+    private $dataCol = 'item_data';
+    private $lifetimeCol = 'item_lifetime';
+    private $timeCol = 'item_time';
+    private $username = '';
+    private $password = '';
+    private $connectionOptions = [];
+    private $namespace;
+
+    private function init($connOrDsn, $namespace, $defaultLifetime, array $options, ?MarshallerInterface $marshaller)
+    {
+        if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) {
+            throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0]));
+        }
+
+        if ($connOrDsn instanceof \PDO) {
+            if (\PDO::ERRMODE_EXCEPTION !== $connOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) {
+                throw new InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__));
+            }
+
+            $this->conn = $connOrDsn;
+        } elseif ($connOrDsn instanceof Connection) {
+            $this->conn = $connOrDsn;
+        } elseif (\is_string($connOrDsn)) {
+            $this->dsn = $connOrDsn;
+        } else {
+            throw new InvalidArgumentException(sprintf('"%s" requires PDO or Doctrine\DBAL\Connection instance or DSN string as first argument, "%s" given.', __CLASS__, \is_object($connOrDsn) ? \get_class($connOrDsn) : \gettype($connOrDsn)));
+        }
+
+        $this->table = isset($options['db_table']) ? $options['db_table'] : $this->table;
+        $this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol;
+        $this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol;
+        $this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol;
+        $this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol;
+        $this->username = isset($options['db_username']) ? $options['db_username'] : $this->username;
+        $this->password = isset($options['db_password']) ? $options['db_password'] : $this->password;
+        $this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions;
+        $this->namespace = $namespace;
+        $this->marshaller = $marshaller ?? new DefaultMarshaller();
+
+        parent::__construct($namespace, $defaultLifetime);
+    }
+
+    /**
+     * Creates the table to store cache items which can be called once for setup.
+     *
+     * Cache ID are saved in a column of maximum length 255. Cache data is
+     * saved in a BLOB.
+     *
+     * @throws \PDOException    When the table already exists
+     * @throws DBALException    When the table already exists
+     * @throws \DomainException When an unsupported PDO driver is used
+     */
+    public function createTable()
+    {
+        // connect if we are not yet
+        $conn = $this->getConnection();
+
+        if ($conn instanceof Connection) {
+            $types = [
+                'mysql' => 'binary',
+                'sqlite' => 'text',
+                'pgsql' => 'string',
+                'oci' => 'string',
+                'sqlsrv' => 'string',
+            ];
+            if (!isset($types[$this->driver])) {
+                throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
+            }
+
+            $schema = new Schema();
+            $table = $schema->createTable($this->table);
+            $table->addColumn($this->idCol, $types[$this->driver], ['length' => 255]);
+            $table->addColumn($this->dataCol, 'blob', ['length' => 16777215]);
+            $table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]);
+            $table->addColumn($this->timeCol, 'integer', ['unsigned' => true]);
+            $table->setPrimaryKey([$this->idCol]);
+
+            foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
+                $conn->exec($sql);
+            }
+
+            return;
+        }
+
+        switch ($this->driver) {
+            case 'mysql':
+                // We use varbinary for the ID column because it prevents unwanted conversions:
+                // - character set conversions between server and client
+                // - trailing space removal
+                // - case-insensitivity
+                // - language processing like é == e
+                $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(255) NOT NULL PRIMARY KEY, $this->dataCol MEDIUMBLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB";
+                break;
+            case 'sqlite':
+                $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
+                break;
+            case 'pgsql':
+                $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
+                break;
+            case 'oci':
+                $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(255) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
+                break;
+            case 'sqlsrv':
+                $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
+                break;
+            default:
+                throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
+        }
+
+        $conn->exec($sql);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function prune()
+    {
+        $deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= :time";
+
+        if ('' !== $this->namespace) {
+            $deleteSql .= " AND $this->idCol LIKE :namespace";
+        }
+
+        try {
+            $delete = $this->getConnection()->prepare($deleteSql);
+        } catch (TableNotFoundException $e) {
+            return true;
+        }
+        $delete->bindValue(':time', time(), \PDO::PARAM_INT);
+
+        if ('' !== $this->namespace) {
+            $delete->bindValue(':namespace', sprintf('%s%%', $this->namespace), \PDO::PARAM_STR);
+        }
+        try {
+            return $delete->execute();
+        } catch (TableNotFoundException $e) {
+            return true;
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doFetch(array $ids)
+    {
+        $now = time();
+        $expired = [];
+
+        $sql = str_pad('', (\count($ids) << 1) - 1, '?,');
+        $sql = "SELECT $this->idCol, CASE WHEN $this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ? THEN $this->dataCol ELSE NULL END FROM $this->table WHERE $this->idCol IN ($sql)";
+        $stmt = $this->getConnection()->prepare($sql);
+        $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT);
+        foreach ($ids as $id) {
+            $stmt->bindValue(++$i, $id);
+        }
+        $stmt->execute();
+
+        while ($row = $stmt->fetch(\PDO::FETCH_NUM)) {
+            if (null === $row[1]) {
+                $expired[] = $row[0];
+            } else {
+                yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]);
+            }
+        }
+
+        if ($expired) {
+            $sql = str_pad('', (\count($expired) << 1) - 1, '?,');
+            $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ? AND $this->idCol IN ($sql)";
+            $stmt = $this->getConnection()->prepare($sql);
+            $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT);
+            foreach ($expired as $id) {
+                $stmt->bindValue(++$i, $id);
+            }
+            $stmt->execute();
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doHave($id)
+    {
+        $sql = "SELECT 1 FROM $this->table WHERE $this->idCol = :id AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > :time)";
+        $stmt = $this->getConnection()->prepare($sql);
+
+        $stmt->bindValue(':id', $id);
+        $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
+        $stmt->execute();
+
+        return (bool) $stmt->fetchColumn();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doClear($namespace)
+    {
+        $conn = $this->getConnection();
+
+        if ('' === $namespace) {
+            if ('sqlite' === $this->driver) {
+                $sql = "DELETE FROM $this->table";
+            } else {
+                $sql = "TRUNCATE TABLE $this->table";
+            }
+        } else {
+            $sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'";
+        }
+
+        try {
+            $conn->exec($sql);
+        } catch (TableNotFoundException $e) {
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDelete(array $ids)
+    {
+        $sql = str_pad('', (\count($ids) << 1) - 1, '?,');
+        $sql = "DELETE FROM $this->table WHERE $this->idCol IN ($sql)";
+        try {
+            $stmt = $this->getConnection()->prepare($sql);
+            $stmt->execute(array_values($ids));
+        } catch (TableNotFoundException $e) {
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doSave(array $values, $lifetime)
+    {
+        if (!$values = $this->marshaller->marshall($values, $failed)) {
+            return $failed;
+        }
+
+        $conn = $this->getConnection();
+        $driver = $this->driver;
+        $insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
+
+        switch (true) {
+            case 'mysql' === $driver:
+                $sql = $insertSql." ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)";
+                break;
+            case 'oci' === $driver:
+                // DUAL is Oracle specific dummy table
+                $sql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ".
+                    "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
+                    "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?";
+                break;
+            case 'sqlsrv' === $driver && version_compare($this->getServerVersion(), '10', '>='):
+                // MERGE is only available since SQL Server 2008 and must be terminated by semicolon
+                // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
+                $sql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ".
+                    "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
+                    "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;";
+                break;
+            case 'sqlite' === $driver:
+                $sql = 'INSERT OR REPLACE'.substr($insertSql, 6);
+                break;
+            case 'pgsql' === $driver && version_compare($this->getServerVersion(), '9.5', '>='):
+                $sql = $insertSql." ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)";
+                break;
+            default:
+                $driver = null;
+                $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id";
+                break;
+        }
+
+        $now = time();
+        $lifetime = $lifetime ?: null;
+        try {
+            $stmt = $conn->prepare($sql);
+        } catch (TableNotFoundException $e) {
+            if (!$conn->isTransactionActive() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
+                $this->createTable();
+            }
+            $stmt = $conn->prepare($sql);
+        }
+
+        if ('sqlsrv' === $driver || 'oci' === $driver) {
+            $stmt->bindParam(1, $id);
+            $stmt->bindParam(2, $id);
+            $stmt->bindParam(3, $data, \PDO::PARAM_LOB);
+            $stmt->bindValue(4, $lifetime, \PDO::PARAM_INT);
+            $stmt->bindValue(5, $now, \PDO::PARAM_INT);
+            $stmt->bindParam(6, $data, \PDO::PARAM_LOB);
+            $stmt->bindValue(7, $lifetime, \PDO::PARAM_INT);
+            $stmt->bindValue(8, $now, \PDO::PARAM_INT);
+        } else {
+            $stmt->bindParam(':id', $id);
+            $stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
+            $stmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT);
+            $stmt->bindValue(':time', $now, \PDO::PARAM_INT);
+        }
+        if (null === $driver) {
+            $insertStmt = $conn->prepare($insertSql);
+
+            $insertStmt->bindParam(':id', $id);
+            $insertStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
+            $insertStmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT);
+            $insertStmt->bindValue(':time', $now, \PDO::PARAM_INT);
+        }
+
+        foreach ($values as $id => $data) {
+            try {
+                $stmt->execute();
+            } catch (TableNotFoundException $e) {
+                if (!$conn->isTransactionActive() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
+                    $this->createTable();
+                }
+                $stmt->execute();
+            }
+            if (null === $driver && !$stmt->rowCount()) {
+                try {
+                    $insertStmt->execute();
+                } catch (DBALException $e) {
+                } catch (\PDOException $e) {
+                    // A concurrent write won, let it be
+                }
+            }
+        }
+
+        return $failed;
+    }
+
+    /**
+     * @return \PDO|Connection
+     */
+    private function getConnection()
+    {
+        if (null === $this->conn) {
+            $this->conn = new \PDO($this->dsn, $this->username, $this->password, $this->connectionOptions);
+            $this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+        }
+        if (null === $this->driver) {
+            if ($this->conn instanceof \PDO) {
+                $this->driver = $this->conn->getAttribute(\PDO::ATTR_DRIVER_NAME);
+            } else {
+                switch ($this->driver = $this->conn->getDriver()->getName()) {
+                    case 'mysqli':
+                    case 'pdo_mysql':
+                    case 'drizzle_pdo_mysql':
+                        $this->driver = 'mysql';
+                        break;
+                    case 'pdo_sqlite':
+                        $this->driver = 'sqlite';
+                        break;
+                    case 'pdo_pgsql':
+                        $this->driver = 'pgsql';
+                        break;
+                    case 'oci8':
+                    case 'pdo_oracle':
+                        $this->driver = 'oci';
+                        break;
+                    case 'pdo_sqlsrv':
+                        $this->driver = 'sqlsrv';
+                        break;
+                }
+            }
+        }
+
+        return $this->conn;
+    }
+
+    /**
+     * @return string
+     */
+    private function getServerVersion()
+    {
+        if (null === $this->serverVersion) {
+            $conn = $this->conn instanceof \PDO ? $this->conn : $this->conn->getWrappedConnection();
+            if ($conn instanceof \PDO) {
+                $this->serverVersion = $conn->getAttribute(\PDO::ATTR_SERVER_VERSION);
+            } elseif ($conn instanceof ServerInfoAwareConnection) {
+                $this->serverVersion = $conn->getServerVersion();
+            } else {
+                $this->serverVersion = '0';
+            }
+        }
+
+        return $this->serverVersion;
+    }
+}
diff --git a/vendor/symfony/cache/Traits/PhpArrayTrait.php b/vendor/symfony/cache/Traits/PhpArrayTrait.php
new file mode 100644
index 0000000..4395de0
--- /dev/null
+++ b/vendor/symfony/cache/Traits/PhpArrayTrait.php
@@ -0,0 +1,152 @@
+<?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\Cache\Traits;
+
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\VarExporter\VarExporter;
+
+/**
+ * @author Titouan Galopin <galopintitouan@gmail.com>
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+trait PhpArrayTrait
+{
+    use ProxyTrait;
+
+    private $file;
+    private $keys;
+    private $values;
+
+    /**
+     * Store an array of cached values.
+     *
+     * @param array $values The cached values
+     */
+    public function warmUp(array $values)
+    {
+        if (file_exists($this->file)) {
+            if (!is_file($this->file)) {
+                throw new InvalidArgumentException(sprintf('Cache path exists and is not a file: %s.', $this->file));
+            }
+
+            if (!is_writable($this->file)) {
+                throw new InvalidArgumentException(sprintf('Cache file is not writable: %s.', $this->file));
+            }
+        } else {
+            $directory = \dirname($this->file);
+
+            if (!is_dir($directory) && !@mkdir($directory, 0777, true)) {
+                throw new InvalidArgumentException(sprintf('Cache directory does not exist and cannot be created: %s.', $directory));
+            }
+
+            if (!is_writable($directory)) {
+                throw new InvalidArgumentException(sprintf('Cache directory is not writable: %s.', $directory));
+            }
+        }
+
+        $dumpedValues = '';
+        $dumpedMap = [];
+        $dump = <<<'EOF'
+<?php
+
+// This file has been auto-generated by the Symfony Cache Component.
+
+return [[
+
+
+EOF;
+
+        foreach ($values as $key => $value) {
+            CacheItem::validateKey(\is_int($key) ? (string) $key : $key);
+            $isStaticValue = true;
+
+            if (null === $value) {
+                $value = "'N;'";
+            } elseif (\is_object($value) || \is_array($value)) {
+                try {
+                    $value = VarExporter::export($value, $isStaticValue);
+                } catch (\Exception $e) {
+                    throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e);
+                }
+            } elseif (\is_string($value)) {
+                // Wrap "N;" in a closure to not confuse it with an encoded `null`
+                if ('N;' === $value) {
+                    $isStaticValue = false;
+                }
+                $value = var_export($value, true);
+            } elseif (!is_scalar($value)) {
+                throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \gettype($value)));
+            } else {
+                $value = var_export($value, true);
+            }
+
+            if (!$isStaticValue) {
+                $value = str_replace("\n", "\n    ", $value);
+                $value = "static function () {\n    return {$value};\n}";
+            }
+            $hash = hash('md5', $value);
+
+            if (null === $id = $dumpedMap[$hash] ?? null) {
+                $id = $dumpedMap[$hash] = \count($dumpedMap);
+                $dumpedValues .= "{$id} => {$value},\n";
+            }
+
+            $dump .= var_export($key, true)." => {$id},\n";
+        }
+
+        $dump .= "\n], [\n\n{$dumpedValues}\n]];\n";
+
+        $tmpFile = uniqid($this->file, true);
+
+        file_put_contents($tmpFile, $dump);
+        @chmod($tmpFile, 0666 & ~umask());
+        unset($serialized, $value, $dump);
+
+        @rename($tmpFile, $this->file);
+
+        $this->initialize();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear()
+    {
+        $this->keys = $this->values = [];
+
+        $cleared = @unlink($this->file) || !file_exists($this->file);
+
+        return $this->pool->clear() && $cleared;
+    }
+
+    /**
+     * Load the cache file.
+     */
+    private function initialize()
+    {
+        if (!file_exists($this->file)) {
+            $this->keys = $this->values = [];
+
+            return;
+        }
+        $values = (include $this->file) ?: [[], []];
+
+        if (2 !== \count($values) || !isset($values[0], $values[1])) {
+            $this->keys = $this->values = [];
+        } else {
+            list($this->keys, $this->values) = $values;
+        }
+    }
+}
diff --git a/vendor/symfony/cache/Traits/PhpFilesTrait.php b/vendor/symfony/cache/Traits/PhpFilesTrait.php
new file mode 100644
index 0000000..5ed4d60
--- /dev/null
+++ b/vendor/symfony/cache/Traits/PhpFilesTrait.php
@@ -0,0 +1,282 @@
+<?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\Cache\Traits;
+
+use Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\VarExporter\VarExporter;
+
+/**
+ * @author Piotr Stankowski <git@trakos.pl>
+ * @author Nicolas Grekas <p@tchwork.com>
+ * @author Rob Frawley 2nd <rmf@src.run>
+ *
+ * @internal
+ */
+trait PhpFilesTrait
+{
+    use FilesystemCommonTrait {
+        doClear as private doCommonClear;
+        doDelete as private doCommonDelete;
+    }
+
+    private $includeHandler;
+    private $appendOnly;
+    private $values = [];
+    private $files = [];
+
+    private static $startTime;
+
+    public static function isSupported()
+    {
+        self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
+
+        return \function_exists('opcache_invalidate') && ('cli' !== \PHP_SAPI || filter_var(ini_get('opcache.enable_cli'), FILTER_VALIDATE_BOOLEAN)) && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN);
+    }
+
+    /**
+     * @return bool
+     */
+    public function prune()
+    {
+        $time = time();
+        $pruned = true;
+        $getExpiry = true;
+
+        set_error_handler($this->includeHandler);
+        try {
+            foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
+                try {
+                    if (\is_array($expiresAt = include $file)) {
+                        $expiresAt = $expiresAt[0];
+                    }
+                } catch (\ErrorException $e) {
+                    $expiresAt = $time;
+                }
+
+                if ($time >= $expiresAt) {
+                    $pruned = $this->doUnlink($file) && !file_exists($file) && $pruned;
+                }
+            }
+        } finally {
+            restore_error_handler();
+        }
+
+        return $pruned;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doFetch(array $ids)
+    {
+        if ($this->appendOnly) {
+            $now = 0;
+            $missingIds = [];
+        } else {
+            $now = time();
+            $missingIds = $ids;
+            $ids = [];
+        }
+        $values = [];
+
+        begin:
+        $getExpiry = false;
+
+        foreach ($ids as $id) {
+            if (null === $value = $this->values[$id] ?? null) {
+                $missingIds[] = $id;
+            } elseif ('N;' === $value) {
+                $values[$id] = null;
+            } elseif (!\is_object($value)) {
+                $values[$id] = $value;
+            } elseif (!$value instanceof LazyValue) {
+                // calling a Closure is for @deprecated BC and should be removed in Symfony 5.0
+                $values[$id] = $value();
+            } elseif (false === $values[$id] = include $value->file) {
+                unset($values[$id], $this->values[$id]);
+                $missingIds[] = $id;
+            }
+            if (!$this->appendOnly) {
+                unset($this->values[$id]);
+            }
+        }
+
+        if (!$missingIds) {
+            return $values;
+        }
+
+        set_error_handler($this->includeHandler);
+        try {
+            $getExpiry = true;
+
+            foreach ($missingIds as $k => $id) {
+                try {
+                    $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
+
+                    if (\is_array($expiresAt = include $file)) {
+                        [$expiresAt, $this->values[$id]] = $expiresAt;
+                    } elseif ($now < $expiresAt) {
+                        $this->values[$id] = new LazyValue($file);
+                    }
+
+                    if ($now >= $expiresAt) {
+                        unset($this->values[$id], $missingIds[$k]);
+                    }
+                } catch (\ErrorException $e) {
+                    unset($missingIds[$k]);
+                }
+            }
+        } finally {
+            restore_error_handler();
+        }
+
+        $ids = $missingIds;
+        $missingIds = [];
+        goto begin;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doHave($id)
+    {
+        if ($this->appendOnly && isset($this->values[$id])) {
+            return true;
+        }
+
+        set_error_handler($this->includeHandler);
+        try {
+            $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
+            $getExpiry = true;
+
+            if (\is_array($expiresAt = include $file)) {
+                [$expiresAt, $value] = $expiresAt;
+            } elseif ($this->appendOnly) {
+                $value = new LazyValue($file);
+            }
+        } catch (\ErrorException $e) {
+            return false;
+        } finally {
+            restore_error_handler();
+        }
+        if ($this->appendOnly) {
+            $now = 0;
+            $this->values[$id] = $value;
+        } else {
+            $now = time();
+        }
+
+        return $now < $expiresAt;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doSave(array $values, $lifetime)
+    {
+        $ok = true;
+        $expiry = $lifetime ? time() + $lifetime : 'PHP_INT_MAX';
+        $allowCompile = self::isSupported();
+
+        foreach ($values as $key => $value) {
+            unset($this->values[$key]);
+            $isStaticValue = true;
+            if (null === $value) {
+                $value = "'N;'";
+            } elseif (\is_object($value) || \is_array($value)) {
+                try {
+                    $value = VarExporter::export($value, $isStaticValue);
+                } catch (\Exception $e) {
+                    throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e);
+                }
+            } elseif (\is_string($value)) {
+                // Wrap "N;" in a closure to not confuse it with an encoded `null`
+                if ('N;' === $value) {
+                    $isStaticValue = false;
+                }
+                $value = var_export($value, true);
+            } elseif (!is_scalar($value)) {
+                throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \gettype($value)));
+            } else {
+                $value = var_export($value, true);
+            }
+
+            if (!$isStaticValue) {
+                // We cannot use a closure here because of https://bugs.php.net/76982
+                $value = str_replace('\Symfony\Component\VarExporter\Internal\\', '', $value);
+                $value = "<?php\n\nnamespace Symfony\Component\VarExporter\Internal;\n\nreturn \$getExpiry ? {$expiry} : {$value};\n";
+            } else {
+                $value = "<?php return [{$expiry}, {$value}];\n";
+            }
+
+            $file = $this->files[$key] = $this->getFile($key, true);
+            // Since OPcache only compiles files older than the script execution start, set the file's mtime in the past
+            $ok = $this->write($file, $value, self::$startTime - 10) && $ok;
+
+            if ($allowCompile) {
+                @opcache_invalidate($file, true);
+                @opcache_compile_file($file);
+            }
+        }
+
+        if (!$ok && !is_writable($this->directory)) {
+            throw new CacheException(sprintf('Cache directory is not writable (%s)', $this->directory));
+        }
+
+        return $ok;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doClear($namespace)
+    {
+        $this->values = [];
+
+        return $this->doCommonClear($namespace);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDelete(array $ids)
+    {
+        foreach ($ids as $id) {
+            unset($this->values[$id]);
+        }
+
+        return $this->doCommonDelete($ids);
+    }
+
+    protected function doUnlink($file)
+    {
+        if (self::isSupported()) {
+            @opcache_invalidate($file, true);
+        }
+
+        return @unlink($file);
+    }
+}
+
+/**
+ * @internal
+ */
+class LazyValue
+{
+    public $file;
+
+    public function __construct($file)
+    {
+        $this->file = $file;
+    }
+}
diff --git a/vendor/symfony/cache/Traits/ProxyTrait.php b/vendor/symfony/cache/Traits/ProxyTrait.php
new file mode 100644
index 0000000..c86f360
--- /dev/null
+++ b/vendor/symfony/cache/Traits/ProxyTrait.php
@@ -0,0 +1,43 @@
+<?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\Cache\Traits;
+
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+trait ProxyTrait
+{
+    private $pool;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function prune()
+    {
+        return $this->pool instanceof PruneableInterface && $this->pool->prune();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function reset()
+    {
+        if ($this->pool instanceof ResetInterface) {
+            $this->pool->reset();
+        }
+    }
+}
diff --git a/vendor/symfony/cache/Traits/RedisClusterProxy.php b/vendor/symfony/cache/Traits/RedisClusterProxy.php
new file mode 100644
index 0000000..b4cef59
--- /dev/null
+++ b/vendor/symfony/cache/Traits/RedisClusterProxy.php
@@ -0,0 +1,63 @@
+<?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\Cache\Traits;
+
+/**
+ * @author Alessandro Chitolina <alekitto@gmail.com>
+ *
+ * @internal
+ */
+class RedisClusterProxy
+{
+    private $redis;
+    private $initializer;
+
+    public function __construct(\Closure $initializer)
+    {
+        $this->initializer = $initializer;
+    }
+
+    public function __call($method, array $args)
+    {
+        $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+        return $this->redis->{$method}(...$args);
+    }
+
+    public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+    {
+        $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+        return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount);
+    }
+
+    public function scan(&$iIterator, $strPattern = null, $iCount = null)
+    {
+        $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+        return $this->redis->scan($iIterator, $strPattern, $iCount);
+    }
+
+    public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+    {
+        $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+        return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount);
+    }
+
+    public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+    {
+        $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+        return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount);
+    }
+}
diff --git a/vendor/symfony/cache/Traits/RedisProxy.php b/vendor/symfony/cache/Traits/RedisProxy.php
new file mode 100644
index 0000000..2b0b857
--- /dev/null
+++ b/vendor/symfony/cache/Traits/RedisProxy.php
@@ -0,0 +1,65 @@
+<?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\Cache\Traits;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+class RedisProxy
+{
+    private $redis;
+    private $initializer;
+    private $ready = false;
+
+    public function __construct(\Redis $redis, \Closure $initializer)
+    {
+        $this->redis = $redis;
+        $this->initializer = $initializer;
+    }
+
+    public function __call($method, array $args)
+    {
+        $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
+
+        return $this->redis->{$method}(...$args);
+    }
+
+    public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+    {
+        $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
+
+        return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount);
+    }
+
+    public function scan(&$iIterator, $strPattern = null, $iCount = null)
+    {
+        $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
+
+        return $this->redis->scan($iIterator, $strPattern, $iCount);
+    }
+
+    public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+    {
+        $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
+
+        return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount);
+    }
+
+    public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+    {
+        $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
+
+        return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount);
+    }
+}
diff --git a/vendor/symfony/cache/Traits/RedisTrait.php b/vendor/symfony/cache/Traits/RedisTrait.php
new file mode 100644
index 0000000..2981f86
--- /dev/null
+++ b/vendor/symfony/cache/Traits/RedisTrait.php
@@ -0,0 +1,489 @@
+<?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\Cache\Traits;
+
+use Predis\Connection\Aggregate\ClusterInterface;
+use Predis\Connection\Aggregate\RedisCluster;
+use Predis\Response\Status;
+use Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+
+/**
+ * @author Aurimas Niekis <aurimas@niekis.lt>
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+trait RedisTrait
+{
+    private static $defaultConnectionOptions = [
+        'class' => null,
+        'persistent' => 0,
+        'persistent_id' => null,
+        'timeout' => 30,
+        'read_timeout' => 0,
+        'retry_interval' => 0,
+        'compression' => true,
+        'tcp_keepalive' => 0,
+        'lazy' => null,
+        'redis_cluster' => false,
+        'dbindex' => 0,
+        'failover' => 'none',
+    ];
+    private $redis;
+    private $marshaller;
+
+    /**
+     * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient
+     */
+    private function init($redisClient, $namespace, $defaultLifetime, ?MarshallerInterface $marshaller)
+    {
+        parent::__construct($namespace, $defaultLifetime);
+
+        if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) {
+            throw new InvalidArgumentException(sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
+        }
+        if (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \RedisCluster && !$redisClient instanceof \Predis\ClientInterface && !$redisClient instanceof RedisProxy && !$redisClient instanceof RedisClusterProxy) {
+            throw new InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, %s given.', __METHOD__, \is_object($redisClient) ? \get_class($redisClient) : \gettype($redisClient)));
+        }
+        $this->redis = $redisClient;
+        $this->marshaller = $marshaller ?? new DefaultMarshaller();
+    }
+
+    /**
+     * Creates a Redis connection using a DSN configuration.
+     *
+     * Example DSN:
+     *   - redis://localhost
+     *   - redis://example.com:1234
+     *   - redis://secret@example.com/13
+     *   - redis:///var/run/redis.sock
+     *   - redis://secret@/var/run/redis.sock/13
+     *
+     * @param string $dsn
+     * @param array  $options See self::$defaultConnectionOptions
+     *
+     * @throws InvalidArgumentException when the DSN is invalid
+     *
+     * @return \Redis|\RedisCluster|\Predis\ClientInterface According to the "class" option
+     */
+    public static function createConnection($dsn, array $options = [])
+    {
+        if (0 === strpos($dsn, 'redis:')) {
+            $scheme = 'redis';
+        } elseif (0 === strpos($dsn, 'rediss:')) {
+            $scheme = 'rediss';
+        } else {
+            throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s does not start with "redis:" or "rediss".', $dsn));
+        }
+
+        if (!\extension_loaded('redis') && !class_exists(\Predis\Client::class)) {
+            throw new CacheException(sprintf('Cannot find the "redis" extension nor the "predis/predis" package: %s', $dsn));
+        }
+
+        $params = preg_replace_callback('#^'.$scheme.':(//)?(?:(?:[^:@]*+:)?([^@]*+)@)?#', function ($m) use (&$auth) {
+            if (isset($m[2])) {
+                $auth = $m[2];
+            }
+
+            return 'file:'.($m[1] ?? '');
+        }, $dsn);
+
+        if (false === $params = parse_url($params)) {
+            throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
+        }
+
+        $query = $hosts = [];
+
+        if (isset($params['query'])) {
+            parse_str($params['query'], $query);
+
+            if (isset($query['host'])) {
+                if (!\is_array($hosts = $query['host'])) {
+                    throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
+                }
+                foreach ($hosts as $host => $parameters) {
+                    if (\is_string($parameters)) {
+                        parse_str($parameters, $parameters);
+                    }
+                    if (false === $i = strrpos($host, ':')) {
+                        $hosts[$host] = ['scheme' => 'tcp', 'host' => $host, 'port' => 6379] + $parameters;
+                    } elseif ($port = (int) substr($host, 1 + $i)) {
+                        $hosts[$host] = ['scheme' => 'tcp', 'host' => substr($host, 0, $i), 'port' => $port] + $parameters;
+                    } else {
+                        $hosts[$host] = ['scheme' => 'unix', 'path' => substr($host, 0, $i)] + $parameters;
+                    }
+                }
+                $hosts = array_values($hosts);
+            }
+        }
+
+        if (isset($params['host']) || isset($params['path'])) {
+            if (!isset($params['dbindex']) && isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
+                $params['dbindex'] = $m[1];
+                $params['path'] = substr($params['path'], 0, -\strlen($m[0]));
+            }
+
+            if (isset($params['host'])) {
+                array_unshift($hosts, ['scheme' => 'tcp', 'host' => $params['host'], 'port' => $params['port'] ?? 6379]);
+            } else {
+                array_unshift($hosts, ['scheme' => 'unix', 'path' => $params['path']]);
+            }
+        }
+
+        if (!$hosts) {
+            throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
+        }
+
+        $params += $query + $options + self::$defaultConnectionOptions;
+
+        if (null === $params['class'] && \extension_loaded('redis')) {
+            $class = $params['redis_cluster'] ? \RedisCluster::class : (1 < \count($hosts) ? \RedisArray::class : \Redis::class);
+        } else {
+            $class = null === $params['class'] ? \Predis\Client::class : $params['class'];
+        }
+
+        if (is_a($class, \Redis::class, true)) {
+            $connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect';
+            $redis = new $class();
+
+            $initializer = function ($redis) use ($connect, $params, $dsn, $auth, $hosts) {
+                try {
+                    @$redis->{$connect}($hosts[0]['host'] ?? $hosts[0]['path'], $hosts[0]['port'] ?? null, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval']);
+                } catch (\RedisException $e) {
+                    throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e->getMessage(), $dsn));
+                }
+
+                set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
+                $isConnected = $redis->isConnected();
+                restore_error_handler();
+                if (!$isConnected) {
+                    $error = preg_match('/^Redis::p?connect\(\): (.*)/', $error, $error) ? sprintf(' (%s)', $error[1]) : '';
+                    throw new InvalidArgumentException(sprintf('Redis connection failed%s: %s', $error, $dsn));
+                }
+
+                if ((null !== $auth && !$redis->auth($auth))
+                    || ($params['dbindex'] && !$redis->select($params['dbindex']))
+                    || ($params['read_timeout'] && !$redis->setOption(\Redis::OPT_READ_TIMEOUT, $params['read_timeout']))
+                ) {
+                    $e = preg_replace('/^ERR /', '', $redis->getLastError());
+                    throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e, $dsn));
+                }
+
+                if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
+                    $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
+                }
+                if ($params['compression'] && \defined('Redis::COMPRESSION_LZF')) {
+                    $redis->setOption(\Redis::OPT_COMPRESSION, \Redis::COMPRESSION_LZF);
+                }
+
+                return true;
+            };
+
+            if ($params['lazy']) {
+                $redis = new RedisProxy($redis, $initializer);
+            } else {
+                $initializer($redis);
+            }
+        } elseif (is_a($class, \RedisArray::class, true)) {
+            foreach ($hosts as $i => $host) {
+                $hosts[$i] = 'tcp' === $host['scheme'] ? $host['host'].':'.$host['port'] : $host['path'];
+            }
+            $params['lazy_connect'] = $params['lazy'] ?? true;
+            $params['connect_timeout'] = $params['timeout'];
+
+            try {
+                $redis = new $class($hosts, $params);
+            } catch (\RedisClusterException $e) {
+                throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e->getMessage(), $dsn));
+            }
+
+            if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
+                $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
+            }
+            if ($params['compression'] && \defined('Redis::COMPRESSION_LZF')) {
+                $redis->setOption(\Redis::OPT_COMPRESSION, \Redis::COMPRESSION_LZF);
+            }
+        } elseif (is_a($class, \RedisCluster::class, true)) {
+            $initializer = function () use ($class, $params, $dsn, $hosts) {
+                foreach ($hosts as $i => $host) {
+                    $hosts[$i] = 'tcp' === $host['scheme'] ? $host['host'].':'.$host['port'] : $host['path'];
+                }
+
+                try {
+                    $redis = new $class(null, $hosts, $params['timeout'], $params['read_timeout'], (bool) $params['persistent']);
+                } catch (\RedisClusterException $e) {
+                    throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e->getMessage(), $dsn));
+                }
+
+                if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
+                    $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
+                }
+                if ($params['compression'] && \defined('Redis::COMPRESSION_LZF')) {
+                    $redis->setOption(\Redis::OPT_COMPRESSION, \Redis::COMPRESSION_LZF);
+                }
+                switch ($params['failover']) {
+                    case 'error': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_ERROR); break;
+                    case 'distribute': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE); break;
+                    case 'slaves': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES); break;
+                }
+
+                return $redis;
+            };
+
+            $redis = $params['lazy'] ? new RedisClusterProxy($initializer) : $initializer();
+        } elseif (is_a($class, \Predis\ClientInterface::class, true)) {
+            if ($params['redis_cluster']) {
+                $params['cluster'] = 'redis';
+            }
+            $params += ['parameters' => []];
+            $params['parameters'] += [
+                'persistent' => $params['persistent'],
+                'timeout' => $params['timeout'],
+                'read_write_timeout' => $params['read_timeout'],
+                'tcp_nodelay' => true,
+            ];
+            if ($params['dbindex']) {
+                $params['parameters']['database'] = $params['dbindex'];
+            }
+            if (null !== $auth) {
+                $params['parameters']['password'] = $auth;
+            }
+            if (1 === \count($hosts) && !$params['redis_cluster']) {
+                $hosts = $hosts[0];
+            } elseif (\in_array($params['failover'], ['slaves', 'distribute'], true) && !isset($params['replication'])) {
+                $params['replication'] = true;
+                $hosts[0] += ['alias' => 'master'];
+            }
+
+            $redis = new $class($hosts, array_diff_key($params, self::$defaultConnectionOptions));
+        } elseif (class_exists($class, false)) {
+            throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster" nor "Predis\ClientInterface".', $class));
+        } else {
+            throw new InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
+        }
+
+        return $redis;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doFetch(array $ids)
+    {
+        if (!$ids) {
+            return [];
+        }
+
+        $result = [];
+
+        if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) {
+            $values = $this->pipeline(function () use ($ids) {
+                foreach ($ids as $id) {
+                    yield 'get' => [$id];
+                }
+            });
+        } else {
+            $values = array_combine($ids, $this->redis->mget($ids));
+        }
+
+        foreach ($values as $id => $v) {
+            if ($v) {
+                $result[$id] = $this->marshaller->unmarshall($v);
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doHave($id)
+    {
+        return (bool) $this->redis->exists($id);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doClear($namespace)
+    {
+        $cleared = true;
+        if ($this->redis instanceof \Predis\ClientInterface) {
+            $evalArgs = [0, $namespace];
+        } else {
+            $evalArgs = [[$namespace], 0];
+        }
+
+        foreach ($this->getHosts() as $host) {
+            if (!isset($namespace[0])) {
+                $cleared = $host->flushDb() && $cleared;
+                continue;
+            }
+
+            $info = $host->info('Server');
+            $info = isset($info['Server']) ? $info['Server'] : $info;
+
+            if (!version_compare($info['redis_version'], '2.8', '>=')) {
+                // As documented in Redis documentation (http://redis.io/commands/keys) using KEYS
+                // can hang your server when it is executed against large databases (millions of items).
+                // Whenever you hit this scale, you should really consider upgrading to Redis 2.8 or above.
+                $cleared = $host->eval("local keys=redis.call('KEYS',ARGV[1]..'*') for i=1,#keys,5000 do redis.call('DEL',unpack(keys,i,math.min(i+4999,#keys))) end return 1", $evalArgs[0], $evalArgs[1]) && $cleared;
+                continue;
+            }
+
+            $cursor = null;
+            do {
+                $keys = $host instanceof \Predis\ClientInterface ? $host->scan($cursor, 'MATCH', $namespace.'*', 'COUNT', 1000) : $host->scan($cursor, $namespace.'*', 1000);
+                if (isset($keys[1]) && \is_array($keys[1])) {
+                    $cursor = $keys[0];
+                    $keys = $keys[1];
+                }
+                if ($keys) {
+                    $this->doDelete($keys);
+                }
+            } while ($cursor = (int) $cursor);
+        }
+
+        return $cleared;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDelete(array $ids)
+    {
+        if (!$ids) {
+            return true;
+        }
+
+        if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) {
+            $this->pipeline(function () use ($ids) {
+                foreach ($ids as $id) {
+                    yield 'del' => [$id];
+                }
+            })->rewind();
+        } else {
+            $this->redis->del($ids);
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doSave(array $values, $lifetime)
+    {
+        if (!$values = $this->marshaller->marshall($values, $failed)) {
+            return $failed;
+        }
+
+        $results = $this->pipeline(function () use ($values, $lifetime) {
+            foreach ($values as $id => $value) {
+                if (0 >= $lifetime) {
+                    yield 'set' => [$id, $value];
+                } else {
+                    yield 'setEx' => [$id, $lifetime, $value];
+                }
+            }
+        });
+        foreach ($results as $id => $result) {
+            if (true !== $result && (!$result instanceof Status || $result !== Status::get('OK'))) {
+                $failed[] = $id;
+            }
+        }
+
+        return $failed;
+    }
+
+    private function pipeline(\Closure $generator)
+    {
+        $ids = [];
+
+        if ($this->redis instanceof RedisClusterProxy || $this->redis instanceof \RedisCluster || ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof RedisCluster)) {
+            // phpredis & predis don't support pipelining with RedisCluster
+            // see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining
+            // see https://github.com/nrk/predis/issues/267#issuecomment-123781423
+            $results = [];
+            foreach ($generator() as $command => $args) {
+                $results[] = $this->redis->{$command}(...$args);
+                $ids[] = $args[0];
+            }
+        } elseif ($this->redis instanceof \Predis\ClientInterface) {
+            $results = $this->redis->pipeline(function ($redis) use ($generator, &$ids) {
+                foreach ($generator() as $command => $args) {
+                    $redis->{$command}(...$args);
+                    $ids[] = $args[0];
+                }
+            });
+        } elseif ($this->redis instanceof \RedisArray) {
+            $connections = $results = $ids = [];
+            foreach ($generator() as $command => $args) {
+                if (!isset($connections[$h = $this->redis->_target($args[0])])) {
+                    $connections[$h] = [$this->redis->_instance($h), -1];
+                    $connections[$h][0]->multi(\Redis::PIPELINE);
+                }
+                $connections[$h][0]->{$command}(...$args);
+                $results[] = [$h, ++$connections[$h][1]];
+                $ids[] = $args[0];
+            }
+            foreach ($connections as $h => $c) {
+                $connections[$h] = $c[0]->exec();
+            }
+            foreach ($results as $k => list($h, $c)) {
+                $results[$k] = $connections[$h][$c];
+            }
+        } else {
+            $this->redis->multi(\Redis::PIPELINE);
+            foreach ($generator() as $command => $args) {
+                $this->redis->{$command}(...$args);
+                $ids[] = $args[0];
+            }
+            $results = $this->redis->exec();
+        }
+
+        foreach ($ids as $k => $id) {
+            yield $id => $results[$k];
+        }
+    }
+
+    private function getHosts(): array
+    {
+        $hosts = [$this->redis];
+        if ($this->redis instanceof \Predis\ClientInterface) {
+            $connection = $this->redis->getConnection();
+            if ($connection instanceof ClusterInterface && $connection instanceof \Traversable) {
+                $hosts = [];
+                foreach ($connection as $c) {
+                    $hosts[] = new \Predis\Client($c);
+                }
+            }
+        } elseif ($this->redis instanceof \RedisArray) {
+            $hosts = [];
+            foreach ($this->redis->_hosts() as $host) {
+                $hosts[] = $this->redis->_instance($host);
+            }
+        } elseif ($this->redis instanceof RedisClusterProxy || $this->redis instanceof \RedisCluster) {
+            $hosts = [];
+            foreach ($this->redis->_masters() as $host) {
+                $hosts[] = $h = new \Redis();
+                $h->connect($host[0], $host[1]);
+            }
+        }
+
+        return $hosts;
+    }
+}
diff --git a/vendor/symfony/cache/composer.json b/vendor/symfony/cache/composer.json
new file mode 100644
index 0000000..05bed1c
--- /dev/null
+++ b/vendor/symfony/cache/composer.json
@@ -0,0 +1,58 @@
+{
+    "name": "symfony/cache",
+    "type": "library",
+    "description": "Symfony Cache component with PSR-6, PSR-16, and tags",
+    "keywords": ["caching", "psr6"],
+    "homepage": "https://symfony.com",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Nicolas Grekas",
+            "email": "p@tchwork.com"
+        },
+        {
+            "name": "Symfony Community",
+            "homepage": "https://symfony.com/contributors"
+        }
+    ],
+    "provide": {
+        "psr/cache-implementation": "1.0",
+        "psr/simple-cache-implementation": "1.0",
+        "symfony/cache-implementation": "1.0"
+    },
+    "require": {
+        "php": "^7.1.3",
+        "psr/cache": "~1.0",
+        "psr/log": "~1.0",
+        "symfony/cache-contracts": "^1.1",
+        "symfony/service-contracts": "^1.1",
+        "symfony/var-exporter": "^4.2"
+    },
+    "require-dev": {
+        "cache/integration-tests": "dev-master",
+        "doctrine/cache": "~1.6",
+        "doctrine/dbal": "~2.5",
+        "predis/predis": "~1.1",
+        "psr/simple-cache": "^1.0",
+        "symfony/config": "~4.2",
+        "symfony/dependency-injection": "~3.4|~4.1",
+        "symfony/var-dumper": "^4.1.1"
+    },
+    "conflict": {
+        "doctrine/dbal": "<2.5",
+        "symfony/dependency-injection": "<3.4",
+        "symfony/var-dumper": "<3.4"
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Component\\Cache\\": "" },
+        "exclude-from-classmap": [
+            "/Tests/"
+        ]
+    },
+    "minimum-stability": "dev",
+    "extra": {
+        "branch-alias": {
+            "dev-master": "4.3-dev"
+        }
+    }
+}
diff --git a/vendor/symfony/cache/phpunit.xml.dist b/vendor/symfony/cache/phpunit.xml.dist
new file mode 100644
index 0000000..591046c
--- /dev/null
+++ b/vendor/symfony/cache/phpunit.xml.dist
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
+         backupGlobals="false"
+         colors="true"
+         bootstrap="vendor/autoload.php"
+         failOnRisky="true"
+         failOnWarning="true"
+>
+    <php>
+        <ini name="error_reporting" value="-1" />
+        <env name="REDIS_HOST" value="localhost" />
+        <env name="MEMCACHED_HOST" value="localhost" />
+    </php>
+
+    <testsuites>
+        <testsuite name="Symfony Cache Component Test Suite">
+            <directory>./Tests/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory>./</directory>
+            <exclude>
+                <directory>./Tests</directory>
+                <directory>./vendor</directory>
+            </exclude>
+        </whitelist>
+    </filter>
+
+    <listeners>
+        <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener">
+            <arguments>
+                <array>
+                    <element key="time-sensitive">
+                        <array>
+                            <element key="0"><string>Cache\IntegrationTests</string></element>
+                            <element key="1"><string>Doctrine\Common\Cache</string></element>
+                            <element key="2"><string>Symfony\Component\Cache</string></element>
+                            <element key="3"><string>Symfony\Component\Cache\Tests\Fixtures</string></element>
+                            <element key="4"><string>Symfony\Component\Cache\Tests\Traits</string></element>
+                            <element key="5"><string>Symfony\Component\Cache\Traits</string></element>
+                        </array>
+                    </element>
+                </array>
+            </arguments>
+        </listener>
+    </listeners>
+</phpunit>
diff --git a/vendor/symfony/event-dispatcher-contracts/.gitignore b/vendor/symfony/event-dispatcher-contracts/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/vendor/symfony/event-dispatcher-contracts/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/vendor/symfony/event-dispatcher-contracts/Event.php b/vendor/symfony/event-dispatcher-contracts/Event.php
new file mode 100644
index 0000000..84f60f3
--- /dev/null
+++ b/vendor/symfony/event-dispatcher-contracts/Event.php
@@ -0,0 +1,96 @@
+<?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\Contracts\EventDispatcher;
+
+use Psr\EventDispatcher\StoppableEventInterface;
+
+if (interface_exists(StoppableEventInterface::class)) {
+    /**
+     * Event is the base class for classes containing event data.
+     *
+     * This class contains no event data. It is used by events that do not pass
+     * state information to an event handler when an event is raised.
+     *
+     * You can call the method stopPropagation() to abort the execution of
+     * further listeners in your event listener.
+     *
+     * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+     * @author Jonathan Wage <jonwage@gmail.com>
+     * @author Roman Borschel <roman@code-factory.org>
+     * @author Bernhard Schussek <bschussek@gmail.com>
+     * @author Nicolas Grekas <p@tchwork.com>
+     */
+    class Event implements StoppableEventInterface
+    {
+        private $propagationStopped = false;
+
+        /**
+         * Returns whether further event listeners should be triggered.
+         */
+        public function isPropagationStopped(): bool
+        {
+            return $this->propagationStopped;
+        }
+
+        /**
+         * Stops the propagation of the event to further event listeners.
+         *
+         * If multiple event listeners are connected to the same event, no
+         * further event listener will be triggered once any trigger calls
+         * stopPropagation().
+         */
+        public function stopPropagation(): void
+        {
+            $this->propagationStopped = true;
+        }
+    }
+} else {
+    /**
+     * Event is the base class for classes containing event data.
+     *
+     * This class contains no event data. It is used by events that do not pass
+     * state information to an event handler when an event is raised.
+     *
+     * You can call the method stopPropagation() to abort the execution of
+     * further listeners in your event listener.
+     *
+     * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+     * @author Jonathan Wage <jonwage@gmail.com>
+     * @author Roman Borschel <roman@code-factory.org>
+     * @author Bernhard Schussek <bschussek@gmail.com>
+     * @author Nicolas Grekas <p@tchwork.com>
+     */
+    class Event
+    {
+        private $propagationStopped = false;
+
+        /**
+         * Returns whether further event listeners should be triggered.
+         */
+        public function isPropagationStopped(): bool
+        {
+            return $this->propagationStopped;
+        }
+
+        /**
+         * Stops the propagation of the event to further event listeners.
+         *
+         * If multiple event listeners are connected to the same event, no
+         * further event listener will be triggered once any trigger calls
+         * stopPropagation().
+         */
+        public function stopPropagation(): void
+        {
+            $this->propagationStopped = true;
+        }
+    }
+}
diff --git a/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php
new file mode 100644
index 0000000..2d470af
--- /dev/null
+++ b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.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\Contracts\EventDispatcher;
+
+use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface;
+
+if (interface_exists(PsrEventDispatcherInterface::class)) {
+    /**
+     * Allows providing hooks on domain-specific lifecycles by dispatching events.
+     */
+    interface EventDispatcherInterface extends PsrEventDispatcherInterface
+    {
+        /**
+         * Dispatches an event to all registered listeners.
+         *
+         * For BC with Symfony 4, the $eventName argument is not declared explicitly on the
+         * signature of the method. Implementations that are not bound by this BC constraint
+         * MUST declare it explicitly, as allowed by PHP.
+         *
+         * @param object      $event     The event to pass to the event handlers/listeners
+         * @param string|null $eventName The name of the event to dispatch. If not supplied,
+         *                               the class of $event should be used instead.
+         *
+         * @return object The passed $event MUST be returned
+         */
+        public function dispatch($event/*, string $eventName = null*/);
+    }
+} else {
+    /**
+     * Allows providing hooks on domain-specific lifecycles by dispatching events.
+     */
+    interface EventDispatcherInterface
+    {
+        /**
+         * Dispatches an event to all registered listeners.
+         *
+         * For BC with Symfony 4, the $eventName argument is not declared explicitly on the
+         * signature of the method. Implementations that are not bound by this BC constraint
+         * MUST declare it explicitly, as allowed by PHP.
+         *
+         * @param object      $event     The event to pass to the event handlers/listeners
+         * @param string|null $eventName The name of the event to dispatch. If not supplied,
+         *                               the class of $event should be used instead.
+         *
+         * @return object The passed $event MUST be returned
+         */
+        public function dispatch($event/*, string $eventName = null*/);
+    }
+}
diff --git a/vendor/symfony/event-dispatcher-contracts/LICENSE b/vendor/symfony/event-dispatcher-contracts/LICENSE
new file mode 100644
index 0000000..3f853aa
--- /dev/null
+++ b/vendor/symfony/event-dispatcher-contracts/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018-2019 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.
diff --git a/vendor/symfony/event-dispatcher-contracts/README.md b/vendor/symfony/event-dispatcher-contracts/README.md
new file mode 100644
index 0000000..fb051c7
--- /dev/null
+++ b/vendor/symfony/event-dispatcher-contracts/README.md
@@ -0,0 +1,9 @@
+Symfony EventDispatcher Contracts
+=================================
+
+A set of abstractions extracted out of the Symfony components.
+
+Can be used to build on semantics that the Symfony components proved useful - and
+that already have battle tested implementations.
+
+See https://github.com/symfony/contracts/blob/master/README.md for more information.
diff --git a/vendor/symfony/event-dispatcher-contracts/composer.json b/vendor/symfony/event-dispatcher-contracts/composer.json
new file mode 100644
index 0000000..55802a4
--- /dev/null
+++ b/vendor/symfony/event-dispatcher-contracts/composer.json
@@ -0,0 +1,34 @@
+{
+    "name": "symfony/event-dispatcher-contracts",
+    "type": "library",
+    "description": "Generic abstractions related to dispatching event",
+    "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"],
+    "homepage": "https://symfony.com",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Nicolas Grekas",
+            "email": "p@tchwork.com"
+        },
+        {
+            "name": "Symfony Community",
+            "homepage": "https://symfony.com/contributors"
+        }
+    ],
+    "require": {
+        "php": "^7.1.3"
+    },
+    "suggest": {
+        "psr/event-dispatcher": "",
+        "symfony/event-dispatcher-implementation": ""
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Contracts\\EventDispatcher\\": "" }
+    },
+    "minimum-stability": "dev",
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.1-dev"
+        }
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/.gitignore b/vendor/symfony/event-dispatcher/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/vendor/symfony/event-dispatcher/CHANGELOG.md b/vendor/symfony/event-dispatcher/CHANGELOG.md
new file mode 100644
index 0000000..7653cad
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/CHANGELOG.md
@@ -0,0 +1,61 @@
+CHANGELOG
+=========
+
+4.3.0
+-----
+
+ * The signature of the `EventDispatcherInterface::dispatch()` method should be updated to `dispatch($event, string $eventName = null)`, not doing so is deprecated
+ * deprecated the `Event` class, use `Symfony\Contracts\EventDispatcher\Event` instead
+
+4.1.0
+-----
+
+ * added support for invokable event listeners tagged with `kernel.event_listener` by default 
+ * The `TraceableEventDispatcher::getOrphanedEvents()` method has been added.
+ * The `TraceableEventDispatcherInterface` has been deprecated.
+
+4.0.0
+-----
+
+ * removed the `ContainerAwareEventDispatcher` class
+ * added the `reset()` method to the `TraceableEventDispatcherInterface`
+
+3.4.0
+-----
+
+  * Implementing `TraceableEventDispatcherInterface` without the `reset()` method has been deprecated.
+
+3.3.0
+-----
+
+  * The ContainerAwareEventDispatcher class has been deprecated. Use EventDispatcher with closure factories instead.
+
+3.0.0
+-----
+
+  * The method `getListenerPriority($eventName, $listener)` has been added to the
+    `EventDispatcherInterface`.
+  * The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()`
+    and `Event::getName()` have been removed.
+    The event dispatcher and the event name are passed to the listener call.
+
+2.5.0
+-----
+
+ * added Debug\TraceableEventDispatcher (originally in HttpKernel)
+ * changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface
+ * added RegisterListenersPass (originally in HttpKernel)
+
+2.1.0
+-----
+
+ * added TraceableEventDispatcherInterface
+ * added ContainerAwareEventDispatcher
+ * added a reference to the EventDispatcher on the Event
+ * added a reference to the Event name on the event
+ * added fluid interface to the dispatch() method which now returns the Event
+   object
+ * added GenericEvent event class
+ * added the possibility for subscribers to subscribe several times for the
+   same event
+ * added ImmutableEventDispatcher
diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
new file mode 100644
index 0000000..513c6ad
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
@@ -0,0 +1,407 @@
+<?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\EventDispatcher\Debug;
+
+use Psr\EventDispatcher\StoppableEventInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
+use Symfony\Component\EventDispatcher\LegacyEventProxy;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\Stopwatch\Stopwatch;
+use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
+
+/**
+ * Collects some data about event listeners.
+ *
+ * This event dispatcher delegates the dispatching to another one.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class TraceableEventDispatcher implements TraceableEventDispatcherInterface
+{
+    protected $logger;
+    protected $stopwatch;
+
+    private $callStack;
+    private $dispatcher;
+    private $wrappedListeners;
+    private $orphanedEvents;
+    private $requestStack;
+    private $currentRequestHash = '';
+
+    public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null, RequestStack $requestStack = null)
+    {
+        $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
+        $this->stopwatch = $stopwatch;
+        $this->logger = $logger;
+        $this->wrappedListeners = [];
+        $this->orphanedEvents = [];
+        $this->requestStack = $requestStack;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addListener($eventName, $listener, $priority = 0)
+    {
+        $this->dispatcher->addListener($eventName, $listener, $priority);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addSubscriber(EventSubscriberInterface $subscriber)
+    {
+        $this->dispatcher->addSubscriber($subscriber);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function removeListener($eventName, $listener)
+    {
+        if (isset($this->wrappedListeners[$eventName])) {
+            foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
+                if ($wrappedListener->getWrappedListener() === $listener) {
+                    $listener = $wrappedListener;
+                    unset($this->wrappedListeners[$eventName][$index]);
+                    break;
+                }
+            }
+        }
+
+        return $this->dispatcher->removeListener($eventName, $listener);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function removeSubscriber(EventSubscriberInterface $subscriber)
+    {
+        return $this->dispatcher->removeSubscriber($subscriber);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListeners($eventName = null)
+    {
+        return $this->dispatcher->getListeners($eventName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListenerPriority($eventName, $listener)
+    {
+        // we might have wrapped listeners for the event (if called while dispatching)
+        // in that case get the priority by wrapper
+        if (isset($this->wrappedListeners[$eventName])) {
+            foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
+                if ($wrappedListener->getWrappedListener() === $listener) {
+                    return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
+                }
+            }
+        }
+
+        return $this->dispatcher->getListenerPriority($eventName, $listener);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasListeners($eventName = null)
+    {
+        return $this->dispatcher->hasListeners($eventName);
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @param string|null $eventName
+     */
+    public function dispatch($event/*, string $eventName = null*/)
+    {
+        if (null === $this->callStack) {
+            $this->callStack = new \SplObjectStorage();
+        }
+
+        $currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : '';
+        $eventName = 1 < \func_num_args() ? func_get_arg(1) : null;
+
+        if (\is_object($event)) {
+            $eventName = $eventName ?? \get_class($event);
+        } else {
+            @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as first argument is deprecated since Symfony 4.3, pass it second and provide the event object first instead.', EventDispatcherInterface::class), E_USER_DEPRECATED);
+            $swap = $event;
+            $event = $eventName ?? new Event();
+            $eventName = $swap;
+
+            if (!$event instanceof Event) {
+                throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an instance of %s, %s given.', EventDispatcherInterface::class, Event::class, \is_object($event) ? \get_class($event) : \gettype($event)));
+            }
+        }
+
+        if (null !== $this->logger && ($event instanceof Event || $event instanceof ContractsEvent || $event instanceof StoppableEventInterface) && $event->isPropagationStopped()) {
+            $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
+        }
+
+        $this->preProcess($eventName);
+        try {
+            $this->beforeDispatch($eventName, $event);
+            try {
+                $e = $this->stopwatch->start($eventName, 'section');
+                try {
+                    $this->dispatcher->dispatch($event, $eventName);
+                } finally {
+                    if ($e->isStarted()) {
+                        $e->stop();
+                    }
+                }
+            } finally {
+                $this->afterDispatch($eventName, $event);
+            }
+        } finally {
+            $this->currentRequestHash = $currentRequestHash;
+            $this->postProcess($eventName);
+        }
+
+        return $event;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @param Request|null $request The request to get listeners for
+     */
+    public function getCalledListeners(/* Request $request = null */)
+    {
+        if (null === $this->callStack) {
+            return [];
+        }
+
+        $hash = 1 <= \func_num_args() && null !== ($request = func_get_arg(0)) ? spl_object_hash($request) : null;
+        $called = [];
+        foreach ($this->callStack as $listener) {
+            list($eventName, $requestHash) = $this->callStack->getInfo();
+            if (null === $hash || $hash === $requestHash) {
+                $called[] = $listener->getInfo($eventName);
+            }
+        }
+
+        return $called;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @param Request|null $request The request to get listeners for
+     */
+    public function getNotCalledListeners(/* Request $request = null */)
+    {
+        try {
+            $allListeners = $this->getListeners();
+        } catch (\Exception $e) {
+            if (null !== $this->logger) {
+                $this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]);
+            }
+
+            // unable to retrieve the uncalled listeners
+            return [];
+        }
+
+        $hash = 1 <= \func_num_args() && null !== ($request = func_get_arg(0)) ? spl_object_hash($request) : null;
+        $calledListeners = [];
+
+        if (null !== $this->callStack) {
+            foreach ($this->callStack as $calledListener) {
+                list(, $requestHash) = $this->callStack->getInfo();
+
+                if (null === $hash || $hash === $requestHash) {
+                    $calledListeners[] = $calledListener->getWrappedListener();
+                }
+            }
+        }
+
+        $notCalled = [];
+        foreach ($allListeners as $eventName => $listeners) {
+            foreach ($listeners as $listener) {
+                if (!\in_array($listener, $calledListeners, true)) {
+                    if (!$listener instanceof WrappedListener) {
+                        $listener = new WrappedListener($listener, null, $this->stopwatch, $this);
+                    }
+                    $notCalled[] = $listener->getInfo($eventName);
+                }
+            }
+        }
+
+        uasort($notCalled, [$this, 'sortNotCalledListeners']);
+
+        return $notCalled;
+    }
+
+    /**
+     * @param Request|null $request The request to get orphaned events for
+     */
+    public function getOrphanedEvents(/* Request $request = null */): array
+    {
+        if (1 <= \func_num_args() && null !== $request = func_get_arg(0)) {
+            return $this->orphanedEvents[spl_object_hash($request)] ?? [];
+        }
+
+        if (!$this->orphanedEvents) {
+            return [];
+        }
+
+        return array_merge(...array_values($this->orphanedEvents));
+    }
+
+    public function reset()
+    {
+        $this->callStack = null;
+        $this->orphanedEvents = [];
+        $this->currentRequestHash = '';
+    }
+
+    /**
+     * Proxies all method calls to the original event dispatcher.
+     *
+     * @param string $method    The method name
+     * @param array  $arguments The method arguments
+     *
+     * @return mixed
+     */
+    public function __call($method, $arguments)
+    {
+        return $this->dispatcher->{$method}(...$arguments);
+    }
+
+    /**
+     * Called before dispatching the event.
+     *
+     * @param object $event
+     */
+    protected function beforeDispatch(string $eventName, $event)
+    {
+        $this->preDispatch($eventName, $event instanceof Event ? $event : new LegacyEventProxy($event));
+    }
+
+    /**
+     * Called after dispatching the event.
+     *
+     * @param object $event
+     */
+    protected function afterDispatch(string $eventName, $event)
+    {
+        $this->postDispatch($eventName, $event instanceof Event ? $event : new LegacyEventProxy($event));
+    }
+
+    /**
+     * @deprecated since Symfony 4.3, will be removed in 5.0, use beforeDispatch instead
+     */
+    protected function preDispatch($eventName, Event $event)
+    {
+    }
+
+    /**
+     * @deprecated since Symfony 4.3, will be removed in 5.0, use afterDispatch instead
+     */
+    protected function postDispatch($eventName, Event $event)
+    {
+    }
+
+    private function preProcess($eventName)
+    {
+        if (!$this->dispatcher->hasListeners($eventName)) {
+            $this->orphanedEvents[$this->currentRequestHash][] = $eventName;
+
+            return;
+        }
+
+        foreach ($this->dispatcher->getListeners($eventName) as $listener) {
+            $priority = $this->getListenerPriority($eventName, $listener);
+            $wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this);
+            $this->wrappedListeners[$eventName][] = $wrappedListener;
+            $this->dispatcher->removeListener($eventName, $listener);
+            $this->dispatcher->addListener($eventName, $wrappedListener, $priority);
+            $this->callStack->attach($wrappedListener, [$eventName, $this->currentRequestHash]);
+        }
+    }
+
+    private function postProcess($eventName)
+    {
+        unset($this->wrappedListeners[$eventName]);
+        $skipped = false;
+        foreach ($this->dispatcher->getListeners($eventName) as $listener) {
+            if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
+                continue;
+            }
+            // Unwrap listener
+            $priority = $this->getListenerPriority($eventName, $listener);
+            $this->dispatcher->removeListener($eventName, $listener);
+            $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);
+
+            if (null !== $this->logger) {
+                $context = ['event' => $eventName, 'listener' => $listener->getPretty()];
+            }
+
+            if ($listener->wasCalled()) {
+                if (null !== $this->logger) {
+                    $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);
+                }
+            } else {
+                $this->callStack->detach($listener);
+            }
+
+            if (null !== $this->logger && $skipped) {
+                $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
+            }
+
+            if ($listener->stoppedPropagation()) {
+                if (null !== $this->logger) {
+                    $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);
+                }
+
+                $skipped = true;
+            }
+        }
+    }
+
+    private function sortNotCalledListeners(array $a, array $b)
+    {
+        if (0 !== $cmp = strcmp($a['event'], $b['event'])) {
+            return $cmp;
+        }
+
+        if (\is_int($a['priority']) && !\is_int($b['priority'])) {
+            return 1;
+        }
+
+        if (!\is_int($a['priority']) && \is_int($b['priority'])) {
+            return -1;
+        }
+
+        if ($a['priority'] === $b['priority']) {
+            return 0;
+        }
+
+        if ($a['priority'] > $b['priority']) {
+            return -1;
+        }
+
+        return 1;
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php
new file mode 100644
index 0000000..4fedb9a
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php
@@ -0,0 +1,42 @@
+<?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\EventDispatcher\Debug;
+
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * @deprecated since Symfony 4.1
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+interface TraceableEventDispatcherInterface extends EventDispatcherInterface, ResetInterface
+{
+    /**
+     * Gets the called listeners.
+     *
+     * @param Request|null $request The request to get listeners for
+     *
+     * @return array An array of called listeners
+     */
+    public function getCalledListeners(/* Request $request = null */);
+
+    /**
+     * Gets the not called listeners.
+     *
+     * @param Request|null $request The request to get listeners for
+     *
+     * @return array An array of not called listeners
+     */
+    public function getNotCalledListeners(/* Request $request = null */);
+}
diff --git a/vendor/symfony/event-dispatcher/Debug/WrappedListener.php b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php
new file mode 100644
index 0000000..e047639
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php
@@ -0,0 +1,136 @@
+<?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\EventDispatcher\Debug;
+
+use Psr\EventDispatcher\StoppableEventInterface;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\LegacyEventProxy;
+use Symfony\Component\Stopwatch\Stopwatch;
+use Symfony\Component\VarDumper\Caster\ClassStub;
+use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @final since Symfony 4.3: the "Event" type-hint on __invoke() will be replaced by "object" in 5.0
+ */
+class WrappedListener
+{
+    private $listener;
+    private $optimizedListener;
+    private $name;
+    private $called;
+    private $stoppedPropagation;
+    private $stopwatch;
+    private $dispatcher;
+    private $pretty;
+    private $stub;
+    private $priority;
+    private static $hasClassStub;
+
+    public function __construct($listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null)
+    {
+        $this->listener = $listener;
+        $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null);
+        $this->stopwatch = $stopwatch;
+        $this->dispatcher = $dispatcher;
+        $this->called = false;
+        $this->stoppedPropagation = false;
+
+        if (\is_array($listener)) {
+            $this->name = \is_object($listener[0]) ? \get_class($listener[0]) : $listener[0];
+            $this->pretty = $this->name.'::'.$listener[1];
+        } elseif ($listener instanceof \Closure) {
+            $r = new \ReflectionFunction($listener);
+            if (false !== strpos($r->name, '{closure}')) {
+                $this->pretty = $this->name = 'closure';
+            } elseif ($class = $r->getClosureScopeClass()) {
+                $this->name = $class->name;
+                $this->pretty = $this->name.'::'.$r->name;
+            } else {
+                $this->pretty = $this->name = $r->name;
+            }
+        } elseif (\is_string($listener)) {
+            $this->pretty = $this->name = $listener;
+        } else {
+            $this->name = \get_class($listener);
+            $this->pretty = $this->name.'::__invoke';
+        }
+
+        if (null !== $name) {
+            $this->name = $name;
+        }
+
+        if (null === self::$hasClassStub) {
+            self::$hasClassStub = class_exists(ClassStub::class);
+        }
+    }
+
+    public function getWrappedListener()
+    {
+        return $this->listener;
+    }
+
+    public function wasCalled()
+    {
+        return $this->called;
+    }
+
+    public function stoppedPropagation()
+    {
+        return $this->stoppedPropagation;
+    }
+
+    public function getPretty()
+    {
+        return $this->pretty;
+    }
+
+    public function getInfo($eventName)
+    {
+        if (null === $this->stub) {
+            $this->stub = self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()';
+        }
+
+        return [
+            'event' => $eventName,
+            'priority' => null !== $this->priority ? $this->priority : (null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null),
+            'pretty' => $this->pretty,
+            'stub' => $this->stub,
+        ];
+    }
+
+    public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher)
+    {
+        if ($event instanceof LegacyEventProxy) {
+            $event = $event->getEvent();
+        }
+
+        $dispatcher = $this->dispatcher ?: $dispatcher;
+
+        $this->called = true;
+        $this->priority = $dispatcher->getListenerPriority($eventName, $this->listener);
+
+        $e = $this->stopwatch->start($this->name, 'event_listener');
+
+        ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher);
+
+        if ($e->isStarted()) {
+            $e->stop();
+        }
+
+        if (($event instanceof Event || $event instanceof ContractsEvent || $event instanceof StoppableEventInterface) && $event->isPropagationStopped()) {
+            $this->stoppedPropagation = true;
+        }
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php
new file mode 100644
index 0000000..3e6493e
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php
@@ -0,0 +1,152 @@
+<?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\EventDispatcher\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Compiler pass to register tagged services for an event dispatcher.
+ */
+class RegisterListenersPass implements CompilerPassInterface
+{
+    protected $dispatcherService;
+    protected $listenerTag;
+    protected $subscriberTag;
+    protected $eventAliasesParameter;
+
+    private $hotPathEvents = [];
+    private $hotPathTagName;
+
+    public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases')
+    {
+        $this->dispatcherService = $dispatcherService;
+        $this->listenerTag = $listenerTag;
+        $this->subscriberTag = $subscriberTag;
+        $this->eventAliasesParameter = $eventAliasesParameter;
+    }
+
+    public function setHotPathEvents(array $hotPathEvents, $tagName = 'container.hot_path')
+    {
+        $this->hotPathEvents = array_flip($hotPathEvents);
+        $this->hotPathTagName = $tagName;
+
+        return $this;
+    }
+
+    public function process(ContainerBuilder $container)
+    {
+        if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) {
+            return;
+        }
+
+        if ($container->hasParameter($this->eventAliasesParameter)) {
+            $aliases = $container->getParameter($this->eventAliasesParameter);
+            $container->getParameterBag()->remove($this->eventAliasesParameter);
+        } else {
+            $aliases = [];
+        }
+        $definition = $container->findDefinition($this->dispatcherService);
+
+        foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) {
+            foreach ($events as $event) {
+                $priority = isset($event['priority']) ? $event['priority'] : 0;
+
+                if (!isset($event['event'])) {
+                    throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
+                }
+                $event['event'] = $aliases[$event['event']] ?? $event['event'];
+
+                if (!isset($event['method'])) {
+                    $event['method'] = 'on'.preg_replace_callback([
+                        '/(?<=\b)[a-z]/i',
+                        '/[^a-z0-9]/i',
+                    ], function ($matches) { return strtoupper($matches[0]); }, $event['event']);
+                    $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
+
+                    if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) {
+                        $event['method'] = '__invoke';
+                    }
+                }
+
+                $definition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]);
+
+                if (isset($this->hotPathEvents[$event['event']])) {
+                    $container->getDefinition($id)->addTag($this->hotPathTagName);
+                }
+            }
+        }
+
+        $extractingDispatcher = new ExtractingEventDispatcher();
+
+        foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $attributes) {
+            $def = $container->getDefinition($id);
+
+            // We must assume that the class value has been correctly filled, even if the service is created by a factory
+            $class = $def->getClass();
+
+            if (!$r = $container->getReflectionClass($class)) {
+                throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
+            }
+            if (!$r->isSubclassOf(EventSubscriberInterface::class)) {
+                throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class));
+            }
+            $class = $r->name;
+
+            ExtractingEventDispatcher::$aliases = $aliases;
+            ExtractingEventDispatcher::$subscriber = $class;
+            $extractingDispatcher->addSubscriber($extractingDispatcher);
+            foreach ($extractingDispatcher->listeners as $args) {
+                $args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]];
+                $definition->addMethodCall('addListener', $args);
+
+                if (isset($this->hotPathEvents[$args[0]])) {
+                    $container->getDefinition($id)->addTag($this->hotPathTagName);
+                }
+            }
+            $extractingDispatcher->listeners = [];
+            ExtractingEventDispatcher::$aliases = [];
+        }
+    }
+}
+
+/**
+ * @internal
+ */
+class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface
+{
+    public $listeners = [];
+
+    public static $aliases = [];
+    public static $subscriber;
+
+    public function addListener($eventName, $listener, $priority = 0)
+    {
+        $this->listeners[] = [$eventName, $listener[1], $priority];
+    }
+
+    public static function getSubscribedEvents()
+    {
+        $events = [];
+
+        foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) {
+            $events[self::$aliases[$eventName] ?? $eventName] = $params;
+        }
+
+        return $events;
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/Event.php b/vendor/symfony/event-dispatcher/Event.php
new file mode 100644
index 0000000..307c4be
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Event.php
@@ -0,0 +1,38 @@
+<?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\EventDispatcher;
+
+/**
+ * @deprecated since Symfony 4.3, use "Symfony\Contracts\EventDispatcher\Event" instead
+ */
+class Event
+{
+    private $propagationStopped = false;
+
+    /**
+     * @return bool Whether propagation was already stopped for this event
+     *
+     * @deprecated since Symfony 4.3, use "Symfony\Contracts\EventDispatcher\Event" instead
+     */
+    public function isPropagationStopped()
+    {
+        return $this->propagationStopped;
+    }
+
+    /**
+     * @deprecated since Symfony 4.3, use "Symfony\Contracts\EventDispatcher\Event" instead
+     */
+    public function stopPropagation()
+    {
+        $this->propagationStopped = true;
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/EventDispatcher.php b/vendor/symfony/event-dispatcher/EventDispatcher.php
new file mode 100644
index 0000000..ebfd1ec
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/EventDispatcher.php
@@ -0,0 +1,308 @@
+<?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\EventDispatcher;
+
+use Psr\EventDispatcher\StoppableEventInterface;
+use Symfony\Component\EventDispatcher\Debug\WrappedListener;
+use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
+
+/**
+ * The EventDispatcherInterface is the central point of Symfony's event listener system.
+ *
+ * Listeners are registered on the manager and events are dispatched through the
+ * manager.
+ *
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @author Jordan Alliot <jordan.alliot@gmail.com>
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class EventDispatcher implements EventDispatcherInterface
+{
+    private $listeners = [];
+    private $sorted = [];
+    private $optimized;
+
+    public function __construct()
+    {
+        if (__CLASS__ === \get_class($this)) {
+            $this->optimized = [];
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @param string|null $eventName
+     */
+    public function dispatch($event/*, string $eventName = null*/)
+    {
+        $eventName = 1 < \func_num_args() ? func_get_arg(1) : null;
+
+        if (\is_object($event)) {
+            $eventName = $eventName ?? \get_class($event);
+        } elseif (\is_string($event) && (null === $eventName || $eventName instanceof Event)) {
+            @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.', EventDispatcherInterface::class), E_USER_DEPRECATED);
+            $swap = $event;
+            $event = $eventName ?? new Event();
+            $eventName = $swap;
+        } else {
+            throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an object, %s given.', EventDispatcherInterface::class, \is_object($event) ? \get_class($event) : \gettype($event)));
+        }
+
+        if (null !== $this->optimized && null !== $eventName) {
+            $listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName));
+        } else {
+            $listeners = $this->getListeners($eventName);
+        }
+
+        if ($listeners) {
+            $this->callListeners($listeners, $eventName, $event);
+        }
+
+        return $event;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListeners($eventName = null)
+    {
+        if (null !== $eventName) {
+            if (empty($this->listeners[$eventName])) {
+                return [];
+            }
+
+            if (!isset($this->sorted[$eventName])) {
+                $this->sortListeners($eventName);
+            }
+
+            return $this->sorted[$eventName];
+        }
+
+        foreach ($this->listeners as $eventName => $eventListeners) {
+            if (!isset($this->sorted[$eventName])) {
+                $this->sortListeners($eventName);
+            }
+        }
+
+        return array_filter($this->sorted);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListenerPriority($eventName, $listener)
+    {
+        if (empty($this->listeners[$eventName])) {
+            return null;
+        }
+
+        if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
+            $listener[0] = $listener[0]();
+        }
+
+        foreach ($this->listeners[$eventName] as $priority => &$listeners) {
+            foreach ($listeners as &$v) {
+                if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) {
+                    $v[0] = $v[0]();
+                }
+                if ($v === $listener) {
+                    return $priority;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasListeners($eventName = null)
+    {
+        if (null !== $eventName) {
+            return !empty($this->listeners[$eventName]);
+        }
+
+        foreach ($this->listeners as $eventListeners) {
+            if ($eventListeners) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addListener($eventName, $listener, $priority = 0)
+    {
+        $this->listeners[$eventName][$priority][] = $listener;
+        unset($this->sorted[$eventName], $this->optimized[$eventName]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function removeListener($eventName, $listener)
+    {
+        if (empty($this->listeners[$eventName])) {
+            return;
+        }
+
+        if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
+            $listener[0] = $listener[0]();
+        }
+
+        foreach ($this->listeners[$eventName] as $priority => &$listeners) {
+            foreach ($listeners as $k => &$v) {
+                if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) {
+                    $v[0] = $v[0]();
+                }
+                if ($v === $listener) {
+                    unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]);
+                }
+            }
+
+            if (!$listeners) {
+                unset($this->listeners[$eventName][$priority]);
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addSubscriber(EventSubscriberInterface $subscriber)
+    {
+        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
+            if (\is_string($params)) {
+                $this->addListener($eventName, [$subscriber, $params]);
+            } elseif (\is_string($params[0])) {
+                $this->addListener($eventName, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0);
+            } else {
+                foreach ($params as $listener) {
+                    $this->addListener($eventName, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function removeSubscriber(EventSubscriberInterface $subscriber)
+    {
+        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
+            if (\is_array($params) && \is_array($params[0])) {
+                foreach ($params as $listener) {
+                    $this->removeListener($eventName, [$subscriber, $listener[0]]);
+                }
+            } else {
+                $this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]);
+            }
+        }
+    }
+
+    /**
+     * Triggers the listeners of an event.
+     *
+     * This method can be overridden to add functionality that is executed
+     * for each listener.
+     *
+     * @param callable[] $listeners The event listeners
+     * @param string     $eventName The name of the event to dispatch
+     * @param object     $event     The event object to pass to the event handlers/listeners
+     */
+    protected function callListeners(iterable $listeners, string $eventName, $event)
+    {
+        if ($event instanceof Event) {
+            $this->doDispatch($listeners, $eventName, $event);
+
+            return;
+        }
+
+        $stoppable = $event instanceof ContractsEvent || $event instanceof StoppableEventInterface;
+
+        foreach ($listeners as $listener) {
+            if ($stoppable && $event->isPropagationStopped()) {
+                break;
+            }
+            // @deprecated: the ternary operator is part of a BC layer and should be removed in 5.0
+            $listener($listener instanceof WrappedListener ? new LegacyEventProxy($event) : $event, $eventName, $this);
+        }
+    }
+
+    /**
+     * @deprecated since Symfony 4.3, use callListeners() instead
+     */
+    protected function doDispatch($listeners, $eventName, Event $event)
+    {
+        foreach ($listeners as $listener) {
+            if ($event->isPropagationStopped()) {
+                break;
+            }
+            $listener($event, $eventName, $this);
+        }
+    }
+
+    /**
+     * Sorts the internal list of listeners for the given event by priority.
+     */
+    private function sortListeners(string $eventName)
+    {
+        krsort($this->listeners[$eventName]);
+        $this->sorted[$eventName] = [];
+
+        foreach ($this->listeners[$eventName] as &$listeners) {
+            foreach ($listeners as $k => $listener) {
+                if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
+                    $listener[0] = $listener[0]();
+                }
+                $this->sorted[$eventName][] = $listener;
+            }
+        }
+    }
+
+    /**
+     * Optimizes the internal list of listeners for the given event by priority.
+     */
+    private function optimizeListeners(string $eventName): array
+    {
+        krsort($this->listeners[$eventName]);
+        $this->optimized[$eventName] = [];
+
+        foreach ($this->listeners[$eventName] as &$listeners) {
+            foreach ($listeners as &$listener) {
+                $closure = &$this->optimized[$eventName][];
+                if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
+                    $closure = static function (...$args) use (&$listener, &$closure) {
+                        if ($listener[0] instanceof \Closure) {
+                            $listener[0] = $listener[0]();
+                        }
+                        ($closure = \Closure::fromCallable($listener))(...$args);
+                    };
+                } else {
+                    $closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener);
+                }
+            }
+        }
+
+        return $this->optimized[$eventName];
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php
new file mode 100644
index 0000000..ceaa62a
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php
@@ -0,0 +1,82 @@
+<?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\EventDispatcher;
+
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface;
+
+/**
+ * The EventDispatcherInterface is the central point of Symfony's event listener system.
+ * Listeners are registered on the manager and events are dispatched through the
+ * manager.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface EventDispatcherInterface extends ContractsEventDispatcherInterface
+{
+    /**
+     * Adds an event listener that listens on the specified events.
+     *
+     * @param string   $eventName The event to listen on
+     * @param callable $listener  The listener
+     * @param int      $priority  The higher this value, the earlier an event
+     *                            listener will be triggered in the chain (defaults to 0)
+     */
+    public function addListener($eventName, $listener, $priority = 0);
+
+    /**
+     * Adds an event subscriber.
+     *
+     * The subscriber is asked for all the events it is
+     * interested in and added as a listener for these events.
+     */
+    public function addSubscriber(EventSubscriberInterface $subscriber);
+
+    /**
+     * Removes an event listener from the specified events.
+     *
+     * @param string   $eventName The event to remove a listener from
+     * @param callable $listener  The listener to remove
+     */
+    public function removeListener($eventName, $listener);
+
+    public function removeSubscriber(EventSubscriberInterface $subscriber);
+
+    /**
+     * Gets the listeners of a specific event or all listeners sorted by descending priority.
+     *
+     * @param string|null $eventName The name of the event
+     *
+     * @return array The event listeners for the specified event, or all event listeners by event name
+     */
+    public function getListeners($eventName = null);
+
+    /**
+     * Gets the listener priority for a specific event.
+     *
+     * Returns null if the event or the listener does not exist.
+     *
+     * @param string   $eventName The name of the event
+     * @param callable $listener  The listener
+     *
+     * @return int|null The event listener priority
+     */
+    public function getListenerPriority($eventName, $listener);
+
+    /**
+     * Checks whether an event has any registered listeners.
+     *
+     * @param string|null $eventName The name of the event
+     *
+     * @return bool true if the specified event has any listeners, false otherwise
+     */
+    public function hasListeners($eventName = null);
+}
diff --git a/vendor/symfony/event-dispatcher/EventSubscriberInterface.php b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php
new file mode 100644
index 0000000..824f215
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php
@@ -0,0 +1,46 @@
+<?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\EventDispatcher;
+
+/**
+ * An EventSubscriber knows itself what events it is interested in.
+ * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes
+ * {@link getSubscribedEvents} and registers the subscriber as a listener for all
+ * returned events.
+ *
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface EventSubscriberInterface
+{
+    /**
+     * Returns an array of event names this subscriber wants to listen to.
+     *
+     * The array keys are event names and the value can be:
+     *
+     *  * The method name to call (priority defaults to 0)
+     *  * An array composed of the method name to call and the priority
+     *  * An array of arrays composed of the method names to call and respective
+     *    priorities, or 0 if unset
+     *
+     * For instance:
+     *
+     *  * ['eventName' => 'methodName']
+     *  * ['eventName' => ['methodName', $priority]]
+     *  * ['eventName' => [['methodName1', $priority], ['methodName2']]]
+     *
+     * @return array The event names to listen to
+     */
+    public static function getSubscribedEvents();
+}
diff --git a/vendor/symfony/event-dispatcher/GenericEvent.php b/vendor/symfony/event-dispatcher/GenericEvent.php
new file mode 100644
index 0000000..f005e3a
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/GenericEvent.php
@@ -0,0 +1,175 @@
+<?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\EventDispatcher;
+
+/**
+ * Event encapsulation class.
+ *
+ * Encapsulates events thus decoupling the observer from the subject they encapsulate.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
+{
+    protected $subject;
+    protected $arguments;
+
+    /**
+     * Encapsulate an event with $subject and $args.
+     *
+     * @param mixed $subject   The subject of the event, usually an object or a callable
+     * @param array $arguments Arguments to store in the event
+     */
+    public function __construct($subject = null, array $arguments = [])
+    {
+        $this->subject = $subject;
+        $this->arguments = $arguments;
+    }
+
+    /**
+     * Getter for subject property.
+     *
+     * @return mixed The observer subject
+     */
+    public function getSubject()
+    {
+        return $this->subject;
+    }
+
+    /**
+     * Get argument by key.
+     *
+     * @param string $key Key
+     *
+     * @return mixed Contents of array key
+     *
+     * @throws \InvalidArgumentException if key is not found
+     */
+    public function getArgument($key)
+    {
+        if ($this->hasArgument($key)) {
+            return $this->arguments[$key];
+        }
+
+        throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key));
+    }
+
+    /**
+     * Add argument to event.
+     *
+     * @param string $key   Argument name
+     * @param mixed  $value Value
+     *
+     * @return $this
+     */
+    public function setArgument($key, $value)
+    {
+        $this->arguments[$key] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Getter for all arguments.
+     *
+     * @return array
+     */
+    public function getArguments()
+    {
+        return $this->arguments;
+    }
+
+    /**
+     * Set args property.
+     *
+     * @param array $args Arguments
+     *
+     * @return $this
+     */
+    public function setArguments(array $args = [])
+    {
+        $this->arguments = $args;
+
+        return $this;
+    }
+
+    /**
+     * Has argument.
+     *
+     * @param string $key Key of arguments array
+     *
+     * @return bool
+     */
+    public function hasArgument($key)
+    {
+        return \array_key_exists($key, $this->arguments);
+    }
+
+    /**
+     * ArrayAccess for argument getter.
+     *
+     * @param string $key Array key
+     *
+     * @return mixed
+     *
+     * @throws \InvalidArgumentException if key does not exist in $this->args
+     */
+    public function offsetGet($key)
+    {
+        return $this->getArgument($key);
+    }
+
+    /**
+     * ArrayAccess for argument setter.
+     *
+     * @param string $key   Array key to set
+     * @param mixed  $value Value
+     */
+    public function offsetSet($key, $value)
+    {
+        $this->setArgument($key, $value);
+    }
+
+    /**
+     * ArrayAccess for unset argument.
+     *
+     * @param string $key Array key
+     */
+    public function offsetUnset($key)
+    {
+        if ($this->hasArgument($key)) {
+            unset($this->arguments[$key]);
+        }
+    }
+
+    /**
+     * ArrayAccess has argument.
+     *
+     * @param string $key Array key
+     *
+     * @return bool
+     */
+    public function offsetExists($key)
+    {
+        return $this->hasArgument($key);
+    }
+
+    /**
+     * IteratorAggregate for iterating over the object like an array.
+     *
+     * @return \ArrayIterator
+     */
+    public function getIterator()
+    {
+        return new \ArrayIterator($this->arguments);
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php
new file mode 100644
index 0000000..75a7d73
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php
@@ -0,0 +1,102 @@
+<?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\EventDispatcher;
+
+/**
+ * A read-only proxy for an event dispatcher.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class ImmutableEventDispatcher implements EventDispatcherInterface
+{
+    private $dispatcher;
+
+    public function __construct(EventDispatcherInterface $dispatcher)
+    {
+        $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @param string|null $eventName
+     */
+    public function dispatch($event/*, string $eventName = null*/)
+    {
+        $eventName = 1 < \func_num_args() ? func_get_arg(1) : null;
+
+        if (is_scalar($event)) {
+            // deprecated
+            $swap = $event;
+            $event = $eventName ?? new Event();
+            $eventName = $swap;
+        }
+
+        return $this->dispatcher->dispatch($event, $eventName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addListener($eventName, $listener, $priority = 0)
+    {
+        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addSubscriber(EventSubscriberInterface $subscriber)
+    {
+        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function removeListener($eventName, $listener)
+    {
+        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function removeSubscriber(EventSubscriberInterface $subscriber)
+    {
+        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListeners($eventName = null)
+    {
+        return $this->dispatcher->getListeners($eventName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListenerPriority($eventName, $listener)
+    {
+        return $this->dispatcher->getListenerPriority($eventName, $listener);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasListeners($eventName = null)
+    {
+        return $this->dispatcher->hasListeners($eventName);
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/LICENSE b/vendor/symfony/event-dispatcher/LICENSE
new file mode 100644
index 0000000..a677f43
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2019 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.
diff --git a/vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php b/vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php
new file mode 100644
index 0000000..be0d381
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php
@@ -0,0 +1,147 @@
+<?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\EventDispatcher;
+
+use Psr\EventDispatcher\StoppableEventInterface;
+use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface;
+
+/**
+ * A helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch().
+ *
+ * This class should be deprecated in Symfony 5.1
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+final class LegacyEventDispatcherProxy implements EventDispatcherInterface
+{
+    private $dispatcher;
+
+    public static function decorate(?ContractsEventDispatcherInterface $dispatcher): ?ContractsEventDispatcherInterface
+    {
+        if (null === $dispatcher) {
+            return null;
+        }
+        $r = new \ReflectionMethod($dispatcher, 'dispatch');
+        $param2 = $r->getParameters()[1] ?? null;
+
+        if (!$param2 || !$param2->hasType() || $param2->getType()->isBuiltin()) {
+            return $dispatcher;
+        }
+
+        @trigger_error(sprintf('The signature of the "%s::dispatch()" method should be updated to "dispatch($event, string $eventName = null)", not doing so is deprecated since Symfony 4.3.', $r->class), E_USER_DEPRECATED);
+
+        $self = new self();
+        $self->dispatcher = $dispatcher;
+
+        return $self;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @param string|null $eventName
+     *
+     * @return object
+     */
+    public function dispatch($event/*, string $eventName = null*/)
+    {
+        $eventName = 1 < \func_num_args() ? func_get_arg(1) : null;
+
+        if (\is_object($event)) {
+            $eventName = $eventName ?? \get_class($event);
+        } elseif (\is_string($event) && (null === $eventName || $eventName instanceof Event)) {
+            @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.', ContractsEventDispatcherInterface::class), E_USER_DEPRECATED);
+            $swap = $event;
+            $event = $eventName ?? new Event();
+            $eventName = $swap;
+        } else {
+            throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an object, %s given.', ContractsEventDispatcherInterface::class, \is_object($event) ? \get_class($event) : \gettype($event)));
+        }
+
+        $listeners = $this->getListeners($eventName);
+        $stoppable = $event instanceof Event || $event instanceof ContractsEvent || $event instanceof StoppableEventInterface;
+
+        foreach ($listeners as $listener) {
+            if ($stoppable && $event->isPropagationStopped()) {
+                break;
+            }
+            $listener($event, $eventName, $this);
+        }
+
+        return $event;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addListener($eventName, $listener, $priority = 0)
+    {
+        return $this->dispatcher->addListener($eventName, $listener, $priority);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addSubscriber(EventSubscriberInterface $subscriber)
+    {
+        return $this->dispatcher->addSubscriber($subscriber);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function removeListener($eventName, $listener)
+    {
+        return $this->dispatcher->removeListener($eventName, $listener);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function removeSubscriber(EventSubscriberInterface $subscriber)
+    {
+        return $this->dispatcher->removeSubscriber($subscriber);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListeners($eventName = null)
+    {
+        return $this->dispatcher->getListeners($eventName);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getListenerPriority($eventName, $listener)
+    {
+        return $this->dispatcher->getListenerPriority($eventName, $listener);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function hasListeners($eventName = null)
+    {
+        return $this->dispatcher->hasListeners($eventName);
+    }
+
+    /**
+     * Proxies all method calls to the original event dispatcher.
+     */
+    public function __call($method, $arguments)
+    {
+        return $this->dispatcher->{$method}(...$arguments);
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/LegacyEventProxy.php b/vendor/symfony/event-dispatcher/LegacyEventProxy.php
new file mode 100644
index 0000000..cad8cfa
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/LegacyEventProxy.php
@@ -0,0 +1,62 @@
+<?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\EventDispatcher;
+
+use Psr\EventDispatcher\StoppableEventInterface;
+use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
+
+/**
+ * @internal to be removed in 5.0.
+ */
+final class LegacyEventProxy extends Event
+{
+    private $event;
+
+    /**
+     * @param object $event
+     */
+    public function __construct($event)
+    {
+        $this->event = $event;
+    }
+
+    /**
+     * @return object $event
+     */
+    public function getEvent()
+    {
+        return $this->event;
+    }
+
+    public function isPropagationStopped()
+    {
+        if (!$this->event instanceof ContractsEvent && !$this->event instanceof StoppableEventInterface) {
+            return false;
+        }
+
+        return $this->event->isPropagationStopped();
+    }
+
+    public function stopPropagation()
+    {
+        if (!$this->event instanceof ContractsEvent) {
+            return;
+        }
+
+        $this->event->stopPropagation();
+    }
+
+    public function __call($name, $args)
+    {
+        return $this->event->{$name}(...$args);
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/README.md b/vendor/symfony/event-dispatcher/README.md
new file mode 100644
index 0000000..185c3fe
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/README.md
@@ -0,0 +1,15 @@
+EventDispatcher Component
+=========================
+
+The EventDispatcher component provides tools that allow your application
+components to communicate with each other by dispatching events and listening to
+them.
+
+Resources
+---------
+
+  * [Documentation](https://symfony.com/doc/current/components/event_dispatcher/index.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)
diff --git a/vendor/symfony/event-dispatcher/Tests/ChildEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/ChildEventDispatcherTest.php
new file mode 100644
index 0000000..442b88f
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/ChildEventDispatcherTest.php
@@ -0,0 +1,26 @@
+<?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\EventDispatcher\Tests;
+
+use Symfony\Component\EventDispatcher\EventDispatcher;
+
+class ChildEventDispatcherTest extends EventDispatcherTest
+{
+    protected function createEventDispatcher()
+    {
+        return new ChildEventDispatcher();
+    }
+}
+
+class ChildEventDispatcher extends EventDispatcher
+{
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php
new file mode 100644
index 0000000..ea476ee
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php
@@ -0,0 +1,319 @@
+<?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\EventDispatcher\Tests\Debug;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Stopwatch\Stopwatch;
+use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
+
+class TraceableEventDispatcherTest extends TestCase
+{
+    public function testAddRemoveListener()
+    {
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+        $tdispatcher->addListener('foo', $listener = function () {});
+        $listeners = $dispatcher->getListeners('foo');
+        $this->assertCount(1, $listeners);
+        $this->assertSame($listener, $listeners[0]);
+
+        $tdispatcher->removeListener('foo', $listener);
+        $this->assertCount(0, $dispatcher->getListeners('foo'));
+    }
+
+    public function testGetListeners()
+    {
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+        $tdispatcher->addListener('foo', $listener = function () {});
+        $this->assertSame($dispatcher->getListeners('foo'), $tdispatcher->getListeners('foo'));
+    }
+
+    public function testHasListeners()
+    {
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+        $this->assertFalse($dispatcher->hasListeners('foo'));
+        $this->assertFalse($tdispatcher->hasListeners('foo'));
+
+        $tdispatcher->addListener('foo', $listener = function () {});
+        $this->assertTrue($dispatcher->hasListeners('foo'));
+        $this->assertTrue($tdispatcher->hasListeners('foo'));
+    }
+
+    public function testGetListenerPriority()
+    {
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+        $tdispatcher->addListener('foo', function () {}, 123);
+
+        $listeners = $dispatcher->getListeners('foo');
+        $this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0]));
+
+        // Verify that priority is preserved when listener is removed and re-added
+        // in preProcess() and postProcess().
+        $tdispatcher->dispatch(new Event(), 'foo');
+        $listeners = $dispatcher->getListeners('foo');
+        $this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0]));
+    }
+
+    public function testGetListenerPriorityWhileDispatching()
+    {
+        $tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $priorityWhileDispatching = null;
+
+        $listener = function () use ($tdispatcher, &$priorityWhileDispatching, &$listener) {
+            $priorityWhileDispatching = $tdispatcher->getListenerPriority('bar', $listener);
+        };
+
+        $tdispatcher->addListener('bar', $listener, 5);
+        $tdispatcher->dispatch(new Event(), 'bar');
+        $this->assertSame(5, $priorityWhileDispatching);
+    }
+
+    public function testAddRemoveSubscriber()
+    {
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+        $subscriber = new EventSubscriber();
+
+        $tdispatcher->addSubscriber($subscriber);
+        $listeners = $dispatcher->getListeners('foo');
+        $this->assertCount(1, $listeners);
+        $this->assertSame([$subscriber, 'call'], $listeners[0]);
+
+        $tdispatcher->removeSubscriber($subscriber);
+        $this->assertCount(0, $dispatcher->getListeners('foo'));
+    }
+
+    public function testGetCalledListeners()
+    {
+        $tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $tdispatcher->addListener('foo', function () {}, 5);
+
+        $listeners = $tdispatcher->getNotCalledListeners();
+        $this->assertArrayHasKey('stub', $listeners[0]);
+        unset($listeners[0]['stub']);
+        $this->assertEquals([], $tdispatcher->getCalledListeners());
+        $this->assertEquals([['event' => 'foo', 'pretty' => 'closure', 'priority' => 5]], $listeners);
+
+        $tdispatcher->dispatch(new Event(), 'foo');
+
+        $listeners = $tdispatcher->getCalledListeners();
+        $this->assertArrayHasKey('stub', $listeners[0]);
+        unset($listeners[0]['stub']);
+        $this->assertEquals([['event' => 'foo', 'pretty' => 'closure', 'priority' => 5]], $listeners);
+        $this->assertEquals([], $tdispatcher->getNotCalledListeners());
+    }
+
+    public function testClearCalledListeners()
+    {
+        $tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $tdispatcher->addListener('foo', function () {}, 5);
+
+        $tdispatcher->dispatch(new Event(), 'foo');
+        $tdispatcher->reset();
+
+        $listeners = $tdispatcher->getNotCalledListeners();
+        $this->assertArrayHasKey('stub', $listeners[0]);
+        unset($listeners[0]['stub']);
+        $this->assertEquals([], $tdispatcher->getCalledListeners());
+        $this->assertEquals([['event' => 'foo', 'pretty' => 'closure', 'priority' => 5]], $listeners);
+    }
+
+    public function testDispatchContractsEvent()
+    {
+        $expectedEvent = new ContractsEvent();
+        $tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $tdispatcher->addListener('foo', function ($event) use ($expectedEvent) {
+            $this->assertSame($event, $expectedEvent);
+        }, 5);
+        $tdispatcher->dispatch($expectedEvent, 'foo');
+
+        $listeners = $tdispatcher->getCalledListeners();
+        $this->assertArrayHasKey('stub', $listeners[0]);
+    }
+
+    public function testDispatchAfterReset()
+    {
+        $tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $tdispatcher->addListener('foo', function () {}, 5);
+
+        $tdispatcher->reset();
+        $tdispatcher->dispatch(new Event(), 'foo');
+
+        $listeners = $tdispatcher->getCalledListeners();
+        $this->assertArrayHasKey('stub', $listeners[0]);
+    }
+
+    public function testGetCalledListenersNested()
+    {
+        $tdispatcher = null;
+        $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $dispatcher->addListener('foo', function (Event $event, $eventName, $dispatcher) use (&$tdispatcher) {
+            $tdispatcher = $dispatcher;
+            $dispatcher->dispatch(new Event(), 'bar');
+        });
+        $dispatcher->addListener('bar', function (Event $event) {});
+        $dispatcher->dispatch(new Event(), 'foo');
+        $this->assertSame($dispatcher, $tdispatcher);
+        $this->assertCount(2, $dispatcher->getCalledListeners());
+    }
+
+    public function testItReturnsNoOrphanedEventsWhenCreated()
+    {
+        $tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $events = $tdispatcher->getOrphanedEvents();
+        $this->assertEmpty($events);
+    }
+
+    public function testItReturnsOrphanedEventsAfterDispatch()
+    {
+        $tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $tdispatcher->dispatch(new Event(), 'foo');
+        $events = $tdispatcher->getOrphanedEvents();
+        $this->assertCount(1, $events);
+        $this->assertEquals(['foo'], $events);
+    }
+
+    public function testItDoesNotReturnHandledEvents()
+    {
+        $tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $tdispatcher->addListener('foo', function () {});
+        $tdispatcher->dispatch(new Event(), 'foo');
+        $events = $tdispatcher->getOrphanedEvents();
+        $this->assertEmpty($events);
+    }
+
+    public function testLogger()
+    {
+        $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
+        $tdispatcher->addListener('foo', $listener1 = function () {});
+        $tdispatcher->addListener('foo', $listener2 = function () {});
+
+        $logger->expects($this->at(0))->method('debug')->with('Notified event "{event}" to listener "{listener}".', ['event' => 'foo', 'listener' => 'closure']);
+        $logger->expects($this->at(1))->method('debug')->with('Notified event "{event}" to listener "{listener}".', ['event' => 'foo', 'listener' => 'closure']);
+
+        $tdispatcher->dispatch(new Event(), 'foo');
+    }
+
+    public function testLoggerWithStoppedEvent()
+    {
+        $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
+        $tdispatcher->addListener('foo', $listener1 = function (Event $event) { $event->stopPropagation(); });
+        $tdispatcher->addListener('foo', $listener2 = function () {});
+
+        $logger->expects($this->at(0))->method('debug')->with('Notified event "{event}" to listener "{listener}".', ['event' => 'foo', 'listener' => 'closure']);
+        $logger->expects($this->at(1))->method('debug')->with('Listener "{listener}" stopped propagation of the event "{event}".', ['event' => 'foo', 'listener' => 'closure']);
+        $logger->expects($this->at(2))->method('debug')->with('Listener "{listener}" was not called for event "{event}".', ['event' => 'foo', 'listener' => 'closure']);
+
+        $tdispatcher->dispatch(new Event(), 'foo');
+    }
+
+    public function testDispatchCallListeners()
+    {
+        $called = [];
+
+        $dispatcher = new EventDispatcher();
+        $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+        $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo1'; }, 10);
+        $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo2'; }, 20);
+
+        $tdispatcher->dispatch(new Event(), 'foo');
+
+        $this->assertSame(['foo2', 'foo1'], $called);
+    }
+
+    public function testDispatchNested()
+    {
+        $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $loop = 1;
+        $dispatchedEvents = 0;
+        $dispatcher->addListener('foo', $listener1 = function () use ($dispatcher, &$loop) {
+            ++$loop;
+            if (2 == $loop) {
+                $dispatcher->dispatch(new Event(), 'foo');
+            }
+        });
+        $dispatcher->addListener('foo', function () use (&$dispatchedEvents) {
+            ++$dispatchedEvents;
+        });
+
+        $dispatcher->dispatch(new Event(), 'foo');
+
+        $this->assertSame(2, $dispatchedEvents);
+    }
+
+    public function testDispatchReusedEventNested()
+    {
+        $nestedCall = false;
+        $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $dispatcher->addListener('foo', function (Event $e) use ($dispatcher) {
+            $dispatcher->dispatch(new Event(), 'bar', $e);
+        });
+        $dispatcher->addListener('bar', function (Event $e) use (&$nestedCall) {
+            $nestedCall = true;
+        });
+
+        $this->assertFalse($nestedCall);
+        $dispatcher->dispatch(new Event(), 'foo');
+        $this->assertTrue($nestedCall);
+    }
+
+    public function testListenerCanRemoveItselfWhenExecuted()
+    {
+        $eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $listener1 = function ($event, $eventName, EventDispatcherInterface $dispatcher) use (&$listener1) {
+            $dispatcher->removeListener('foo', $listener1);
+        };
+        $eventDispatcher->addListener('foo', $listener1);
+        $eventDispatcher->addListener('foo', function () {});
+        $eventDispatcher->dispatch(new Event(), 'foo');
+
+        $this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed');
+    }
+
+    public function testClearOrphanedEvents()
+    {
+        $tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+        $tdispatcher->dispatch(new Event(), 'foo');
+        $events = $tdispatcher->getOrphanedEvents();
+        $this->assertCount(1, $events);
+        $tdispatcher->reset();
+        $events = $tdispatcher->getOrphanedEvents();
+        $this->assertCount(0, $events);
+    }
+}
+
+class EventSubscriber implements EventSubscriberInterface
+{
+    public static function getSubscribedEvents()
+    {
+        return ['foo' => 'call'];
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/Debug/WrappedListenerTest.php b/vendor/symfony/event-dispatcher/Tests/Debug/WrappedListenerTest.php
new file mode 100644
index 0000000..75e9012
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/Debug/WrappedListenerTest.php
@@ -0,0 +1,60 @@
+<?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\EventDispatcher\Tests\Debug;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Debug\WrappedListener;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\Stopwatch\Stopwatch;
+
+class WrappedListenerTest extends TestCase
+{
+    /**
+     * @dataProvider provideListenersToDescribe
+     */
+    public function testListenerDescription($listener, $expected)
+    {
+        $wrappedListener = new WrappedListener($listener, null, $this->getMockBuilder(Stopwatch::class)->getMock(), $this->getMockBuilder(EventDispatcherInterface::class)->getMock());
+
+        $this->assertStringMatchesFormat($expected, $wrappedListener->getPretty());
+    }
+
+    public function provideListenersToDescribe()
+    {
+        return [
+            [new FooListener(), 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::__invoke'],
+            [[new FooListener(), 'listen'], 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listen'],
+            [['Symfony\Component\EventDispatcher\Tests\Debug\FooListener', 'listenStatic'], 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listenStatic'],
+            [['Symfony\Component\EventDispatcher\Tests\Debug\FooListener', 'invalidMethod'], 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::invalidMethod'],
+            ['var_dump', 'var_dump'],
+            [function () {}, 'closure'],
+            [\Closure::fromCallable([new FooListener(), 'listen']), 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listen'],
+            [\Closure::fromCallable(['Symfony\Component\EventDispatcher\Tests\Debug\FooListener', 'listenStatic']), 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listenStatic'],
+            [\Closure::fromCallable(function () {}), 'closure'],
+        ];
+    }
+}
+
+class FooListener
+{
+    public function listen()
+    {
+    }
+
+    public function __invoke()
+    {
+    }
+
+    public static function listenStatic()
+    {
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php b/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
new file mode 100644
index 0000000..780f708
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
@@ -0,0 +1,258 @@
+<?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\EventDispatcher\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class RegisterListenersPassTest extends TestCase
+{
+    /**
+     * Tests that event subscribers not implementing EventSubscriberInterface
+     * trigger an exception.
+     */
+    public function testEventSubscriberWithoutInterface()
+    {
+        $this->expectException('InvalidArgumentException');
+        $builder = new ContainerBuilder();
+        $builder->register('event_dispatcher');
+        $builder->register('my_event_subscriber', 'stdClass')
+            ->addTag('kernel.event_subscriber');
+
+        $registerListenersPass = new RegisterListenersPass();
+        $registerListenersPass->process($builder);
+    }
+
+    public function testValidEventSubscriber()
+    {
+        $builder = new ContainerBuilder();
+        $eventDispatcherDefinition = $builder->register('event_dispatcher');
+        $builder->register('my_event_subscriber', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService')
+            ->addTag('kernel.event_subscriber');
+
+        $registerListenersPass = new RegisterListenersPass();
+        $registerListenersPass->process($builder);
+
+        $expectedCalls = [
+            [
+                'addListener',
+                [
+                    'event',
+                    [new ServiceClosureArgument(new Reference('my_event_subscriber')), 'onEvent'],
+                    0,
+                ],
+            ],
+        ];
+        $this->assertEquals($expectedCalls, $eventDispatcherDefinition->getMethodCalls());
+    }
+
+    public function testAliasedEventSubscriber(): void
+    {
+        $builder = new ContainerBuilder();
+        $builder->setParameter('event_dispatcher.event_aliases', [AliasedEvent::class => 'aliased_event']);
+        $builder->register('event_dispatcher');
+        $builder->register('my_event_subscriber', AliasedSubscriber::class)
+            ->addTag('kernel.event_subscriber');
+
+        $registerListenersPass = new RegisterListenersPass();
+        $registerListenersPass->process($builder);
+
+        $expectedCalls = [
+            [
+                'addListener',
+                [
+                    'aliased_event',
+                    [new ServiceClosureArgument(new Reference('my_event_subscriber')), 'onAliasedEvent'],
+                    0,
+                ],
+            ],
+        ];
+        $this->assertEquals($expectedCalls, $builder->getDefinition('event_dispatcher')->getMethodCalls());
+    }
+
+    public function testAbstractEventListener()
+    {
+        $this->expectException('InvalidArgumentException');
+        $this->expectExceptionMessage('The service "foo" tagged "kernel.event_listener" must not be abstract.');
+        $container = new ContainerBuilder();
+        $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_listener', []);
+        $container->register('event_dispatcher', 'stdClass');
+
+        $registerListenersPass = new RegisterListenersPass();
+        $registerListenersPass->process($container);
+    }
+
+    public function testAbstractEventSubscriber()
+    {
+        $this->expectException('InvalidArgumentException');
+        $this->expectExceptionMessage('The service "foo" tagged "kernel.event_subscriber" must not be abstract.');
+        $container = new ContainerBuilder();
+        $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_subscriber', []);
+        $container->register('event_dispatcher', 'stdClass');
+
+        $registerListenersPass = new RegisterListenersPass();
+        $registerListenersPass->process($container);
+    }
+
+    public function testEventSubscriberResolvableClassName()
+    {
+        $container = new ContainerBuilder();
+
+        $container->setParameter('subscriber.class', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService');
+        $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', []);
+        $container->register('event_dispatcher', 'stdClass');
+
+        $registerListenersPass = new RegisterListenersPass();
+        $registerListenersPass->process($container);
+
+        $definition = $container->getDefinition('event_dispatcher');
+        $expectedCalls = [
+            [
+                'addListener',
+                [
+                    'event',
+                    [new ServiceClosureArgument(new Reference('foo')), 'onEvent'],
+                    0,
+                ],
+            ],
+        ];
+        $this->assertEquals($expectedCalls, $definition->getMethodCalls());
+    }
+
+    public function testHotPathEvents()
+    {
+        $container = new ContainerBuilder();
+
+        $container->register('foo', SubscriberService::class)->addTag('kernel.event_subscriber', []);
+        $container->register('event_dispatcher', 'stdClass');
+
+        (new RegisterListenersPass())->setHotPathEvents(['event'])->process($container);
+
+        $this->assertTrue($container->getDefinition('foo')->hasTag('container.hot_path'));
+    }
+
+    public function testEventSubscriberUnresolvableClassName()
+    {
+        $this->expectException('InvalidArgumentException');
+        $this->expectExceptionMessage('You have requested a non-existent parameter "subscriber.class"');
+        $container = new ContainerBuilder();
+        $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', []);
+        $container->register('event_dispatcher', 'stdClass');
+
+        $registerListenersPass = new RegisterListenersPass();
+        $registerListenersPass->process($container);
+    }
+
+    public function testInvokableEventListener()
+    {
+        $container = new ContainerBuilder();
+        $container->register('foo', \stdClass::class)->addTag('kernel.event_listener', ['event' => 'foo.bar']);
+        $container->register('bar', InvokableListenerService::class)->addTag('kernel.event_listener', ['event' => 'foo.bar']);
+        $container->register('baz', InvokableListenerService::class)->addTag('kernel.event_listener', ['event' => 'event']);
+        $container->register('event_dispatcher', \stdClass::class);
+
+        $registerListenersPass = new RegisterListenersPass();
+        $registerListenersPass->process($container);
+
+        $definition = $container->getDefinition('event_dispatcher');
+        $expectedCalls = [
+            [
+                'addListener',
+                [
+                    'foo.bar',
+                    [new ServiceClosureArgument(new Reference('foo')), 'onFooBar'],
+                    0,
+                ],
+            ],
+            [
+                'addListener',
+                [
+                    'foo.bar',
+                    [new ServiceClosureArgument(new Reference('bar')), '__invoke'],
+                    0,
+                ],
+            ],
+            [
+                'addListener',
+                [
+                    'event',
+                    [new ServiceClosureArgument(new Reference('baz')), 'onEvent'],
+                    0,
+                ],
+            ],
+        ];
+        $this->assertEquals($expectedCalls, $definition->getMethodCalls());
+    }
+
+    public function testAliasedEventListener(): void
+    {
+        $container = new ContainerBuilder();
+        $container->setParameter('event_dispatcher.event_aliases', [AliasedEvent::class => 'aliased_event']);
+        $container->register('foo', InvokableListenerService::class)->addTag('kernel.event_listener', ['event' => AliasedEvent::class, 'method' => 'onEvent']);
+        $container->register('event_dispatcher');
+
+        $registerListenersPass = new RegisterListenersPass();
+        $registerListenersPass->process($container);
+
+        $definition = $container->getDefinition('event_dispatcher');
+        $expectedCalls = [
+            [
+                'addListener',
+                [
+                    'aliased_event',
+                    [new ServiceClosureArgument(new Reference('foo')), 'onEvent'],
+                    0,
+                ],
+            ],
+        ];
+        $this->assertEquals($expectedCalls, $definition->getMethodCalls());
+    }
+}
+
+class SubscriberService implements EventSubscriberInterface
+{
+    public static function getSubscribedEvents()
+    {
+        return [
+            'event' => 'onEvent',
+        ];
+    }
+}
+
+class InvokableListenerService
+{
+    public function __invoke()
+    {
+    }
+
+    public function onEvent()
+    {
+    }
+}
+
+final class AliasedSubscriber implements EventSubscriberInterface
+{
+    public static function getSubscribedEvents(): array
+    {
+        return [
+            AliasedEvent::class => 'onAliasedEvent',
+        ];
+    }
+}
+
+final class AliasedEvent
+{
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php
new file mode 100644
index 0000000..83d10a8
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php
@@ -0,0 +1,511 @@
+<?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\EventDispatcher\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
+
+class EventDispatcherTest extends TestCase
+{
+    /* Some pseudo events */
+    const preFoo = 'pre.foo';
+    const postFoo = 'post.foo';
+    const preBar = 'pre.bar';
+    const postBar = 'post.bar';
+
+    /**
+     * @var EventDispatcher
+     */
+    private $dispatcher;
+
+    private $listener;
+
+    protected function setUp(): void
+    {
+        $this->dispatcher = $this->createEventDispatcher();
+        $this->listener = new TestEventListener();
+    }
+
+    protected function tearDown(): void
+    {
+        $this->dispatcher = null;
+        $this->listener = null;
+    }
+
+    protected function createEventDispatcher()
+    {
+        return new EventDispatcher();
+    }
+
+    public function testInitialState()
+    {
+        $this->assertEquals([], $this->dispatcher->getListeners());
+        $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
+    }
+
+    public function testAddListener()
+    {
+        $this->dispatcher->addListener('pre.foo', [$this->listener, 'preFoo']);
+        $this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo']);
+        $this->assertTrue($this->dispatcher->hasListeners());
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+        $this->assertCount(1, $this->dispatcher->getListeners(self::preFoo));
+        $this->assertCount(1, $this->dispatcher->getListeners(self::postFoo));
+        $this->assertCount(2, $this->dispatcher->getListeners());
+    }
+
+    public function testGetListenersSortsByPriority()
+    {
+        $listener1 = new TestEventListener();
+        $listener2 = new TestEventListener();
+        $listener3 = new TestEventListener();
+        $listener1->name = '1';
+        $listener2->name = '2';
+        $listener3->name = '3';
+
+        $this->dispatcher->addListener('pre.foo', [$listener1, 'preFoo'], -10);
+        $this->dispatcher->addListener('pre.foo', [$listener2, 'preFoo'], 10);
+        $this->dispatcher->addListener('pre.foo', [$listener3, 'preFoo']);
+
+        $expected = [
+            [$listener2, 'preFoo'],
+            [$listener3, 'preFoo'],
+            [$listener1, 'preFoo'],
+        ];
+
+        $this->assertSame($expected, $this->dispatcher->getListeners('pre.foo'));
+    }
+
+    public function testGetAllListenersSortsByPriority()
+    {
+        $listener1 = new TestEventListener();
+        $listener2 = new TestEventListener();
+        $listener3 = new TestEventListener();
+        $listener4 = new TestEventListener();
+        $listener5 = new TestEventListener();
+        $listener6 = new TestEventListener();
+
+        $this->dispatcher->addListener('pre.foo', $listener1, -10);
+        $this->dispatcher->addListener('pre.foo', $listener2);
+        $this->dispatcher->addListener('pre.foo', $listener3, 10);
+        $this->dispatcher->addListener('post.foo', $listener4, -10);
+        $this->dispatcher->addListener('post.foo', $listener5);
+        $this->dispatcher->addListener('post.foo', $listener6, 10);
+
+        $expected = [
+            'pre.foo' => [$listener3, $listener2, $listener1],
+            'post.foo' => [$listener6, $listener5, $listener4],
+        ];
+
+        $this->assertSame($expected, $this->dispatcher->getListeners());
+    }
+
+    public function testGetListenerPriority()
+    {
+        $listener1 = new TestEventListener();
+        $listener2 = new TestEventListener();
+
+        $this->dispatcher->addListener('pre.foo', $listener1, -10);
+        $this->dispatcher->addListener('pre.foo', $listener2);
+
+        $this->assertSame(-10, $this->dispatcher->getListenerPriority('pre.foo', $listener1));
+        $this->assertSame(0, $this->dispatcher->getListenerPriority('pre.foo', $listener2));
+        $this->assertNull($this->dispatcher->getListenerPriority('pre.bar', $listener2));
+        $this->assertNull($this->dispatcher->getListenerPriority('pre.foo', function () {}));
+    }
+
+    public function testDispatch()
+    {
+        $this->dispatcher->addListener('pre.foo', [$this->listener, 'preFoo']);
+        $this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo']);
+        $this->dispatcher->dispatch(new Event(), self::preFoo);
+        $this->assertTrue($this->listener->preFooInvoked);
+        $this->assertFalse($this->listener->postFooInvoked);
+        $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(new Event(), 'noevent'));
+        $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(new Event(), self::preFoo));
+        $event = new Event();
+        $return = $this->dispatcher->dispatch($event, self::preFoo);
+        $this->assertSame($event, $return);
+    }
+
+    public function testDispatchContractsEvent()
+    {
+        $this->dispatcher->addListener('pre.foo', [$this->listener, 'preFoo']);
+        $this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo']);
+        $this->dispatcher->dispatch(new ContractsEvent(), self::preFoo);
+        $this->assertTrue($this->listener->preFooInvoked);
+        $this->assertFalse($this->listener->postFooInvoked);
+        $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(new Event(), 'noevent'));
+        $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(new Event(), self::preFoo));
+        $event = new Event();
+        $return = $this->dispatcher->dispatch($event, self::preFoo);
+        $this->assertSame($event, $return);
+    }
+
+    public function testDispatchForClosure()
+    {
+        $invoked = 0;
+        $listener = function () use (&$invoked) {
+            ++$invoked;
+        };
+        $this->dispatcher->addListener('pre.foo', $listener);
+        $this->dispatcher->addListener('post.foo', $listener);
+        $this->dispatcher->dispatch(new Event(), self::preFoo);
+        $this->assertEquals(1, $invoked);
+    }
+
+    public function testStopEventPropagation()
+    {
+        $otherListener = new TestEventListener();
+
+        // postFoo() stops the propagation, so only one listener should
+        // be executed
+        // Manually set priority to enforce $this->listener to be called first
+        $this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo'], 10);
+        $this->dispatcher->addListener('post.foo', [$otherListener, 'postFoo']);
+        $this->dispatcher->dispatch(new Event(), self::postFoo);
+        $this->assertTrue($this->listener->postFooInvoked);
+        $this->assertFalse($otherListener->postFooInvoked);
+    }
+
+    public function testDispatchByPriority()
+    {
+        $invoked = [];
+        $listener1 = function () use (&$invoked) {
+            $invoked[] = '1';
+        };
+        $listener2 = function () use (&$invoked) {
+            $invoked[] = '2';
+        };
+        $listener3 = function () use (&$invoked) {
+            $invoked[] = '3';
+        };
+        $this->dispatcher->addListener('pre.foo', $listener1, -10);
+        $this->dispatcher->addListener('pre.foo', $listener2);
+        $this->dispatcher->addListener('pre.foo', $listener3, 10);
+        $this->dispatcher->dispatch(new Event(), self::preFoo);
+        $this->assertEquals(['3', '2', '1'], $invoked);
+    }
+
+    public function testRemoveListener()
+    {
+        $this->dispatcher->addListener('pre.bar', $this->listener);
+        $this->assertTrue($this->dispatcher->hasListeners(self::preBar));
+        $this->dispatcher->removeListener('pre.bar', $this->listener);
+        $this->assertFalse($this->dispatcher->hasListeners(self::preBar));
+        $this->dispatcher->removeListener('notExists', $this->listener);
+    }
+
+    public function testAddSubscriber()
+    {
+        $eventSubscriber = new TestEventSubscriber();
+        $this->dispatcher->addSubscriber($eventSubscriber);
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+    }
+
+    public function testAddSubscriberWithPriorities()
+    {
+        $eventSubscriber = new TestEventSubscriber();
+        $this->dispatcher->addSubscriber($eventSubscriber);
+
+        $eventSubscriber = new TestEventSubscriberWithPriorities();
+        $this->dispatcher->addSubscriber($eventSubscriber);
+
+        $listeners = $this->dispatcher->getListeners('pre.foo');
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertCount(2, $listeners);
+        $this->assertInstanceOf('Symfony\Component\EventDispatcher\Tests\TestEventSubscriberWithPriorities', $listeners[0][0]);
+    }
+
+    public function testAddSubscriberWithMultipleListeners()
+    {
+        $eventSubscriber = new TestEventSubscriberWithMultipleListeners();
+        $this->dispatcher->addSubscriber($eventSubscriber);
+
+        $listeners = $this->dispatcher->getListeners('pre.foo');
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertCount(2, $listeners);
+        $this->assertEquals('preFoo2', $listeners[0][1]);
+    }
+
+    public function testRemoveSubscriber()
+    {
+        $eventSubscriber = new TestEventSubscriber();
+        $this->dispatcher->addSubscriber($eventSubscriber);
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+        $this->dispatcher->removeSubscriber($eventSubscriber);
+        $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
+    }
+
+    public function testRemoveSubscriberWithPriorities()
+    {
+        $eventSubscriber = new TestEventSubscriberWithPriorities();
+        $this->dispatcher->addSubscriber($eventSubscriber);
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->dispatcher->removeSubscriber($eventSubscriber);
+        $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+    }
+
+    public function testRemoveSubscriberWithMultipleListeners()
+    {
+        $eventSubscriber = new TestEventSubscriberWithMultipleListeners();
+        $this->dispatcher->addSubscriber($eventSubscriber);
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertCount(2, $this->dispatcher->getListeners(self::preFoo));
+        $this->dispatcher->removeSubscriber($eventSubscriber);
+        $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+    }
+
+    public function testEventReceivesTheDispatcherInstanceAsArgument()
+    {
+        $listener = new TestWithDispatcher();
+        $this->dispatcher->addListener('test', [$listener, 'foo']);
+        $this->assertNull($listener->name);
+        $this->assertNull($listener->dispatcher);
+        $this->dispatcher->dispatch(new Event(), 'test');
+        $this->assertEquals('test', $listener->name);
+        $this->assertSame($this->dispatcher, $listener->dispatcher);
+    }
+
+    /**
+     * @see https://bugs.php.net/62976
+     *
+     * This bug affects:
+     *  - The PHP 5.3 branch for versions < 5.3.18
+     *  - The PHP 5.4 branch for versions < 5.4.8
+     *  - The PHP 5.5 branch is not affected
+     */
+    public function testWorkaroundForPhpBug62976()
+    {
+        $dispatcher = $this->createEventDispatcher();
+        $dispatcher->addListener('bug.62976', new CallableClass());
+        $dispatcher->removeListener('bug.62976', function () {});
+        $this->assertTrue($dispatcher->hasListeners('bug.62976'));
+    }
+
+    public function testHasListenersWhenAddedCallbackListenerIsRemoved()
+    {
+        $listener = function () {};
+        $this->dispatcher->addListener('foo', $listener);
+        $this->dispatcher->removeListener('foo', $listener);
+        $this->assertFalse($this->dispatcher->hasListeners());
+    }
+
+    public function testGetListenersWhenAddedCallbackListenerIsRemoved()
+    {
+        $listener = function () {};
+        $this->dispatcher->addListener('foo', $listener);
+        $this->dispatcher->removeListener('foo', $listener);
+        $this->assertSame([], $this->dispatcher->getListeners());
+    }
+
+    public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled()
+    {
+        $this->assertFalse($this->dispatcher->hasListeners('foo'));
+        $this->assertFalse($this->dispatcher->hasListeners());
+    }
+
+    public function testHasListenersIsLazy()
+    {
+        $called = 0;
+        $listener = [function () use (&$called) { ++$called; }, 'onFoo'];
+        $this->dispatcher->addListener('foo', $listener);
+        $this->assertTrue($this->dispatcher->hasListeners());
+        $this->assertTrue($this->dispatcher->hasListeners('foo'));
+        $this->assertSame(0, $called);
+    }
+
+    public function testDispatchLazyListener()
+    {
+        $called = 0;
+        $factory = function () use (&$called) {
+            ++$called;
+
+            return new TestWithDispatcher();
+        };
+        $this->dispatcher->addListener('foo', [$factory, 'foo']);
+        $this->assertSame(0, $called);
+        $this->dispatcher->dispatch(new Event(), 'foo');
+        $this->dispatcher->dispatch(new Event(), 'foo');
+        $this->assertSame(1, $called);
+    }
+
+    public function testRemoveFindsLazyListeners()
+    {
+        $test = new TestWithDispatcher();
+        $factory = function () use ($test) { return $test; };
+
+        $this->dispatcher->addListener('foo', [$factory, 'foo']);
+        $this->assertTrue($this->dispatcher->hasListeners('foo'));
+        $this->dispatcher->removeListener('foo', [$test, 'foo']);
+        $this->assertFalse($this->dispatcher->hasListeners('foo'));
+
+        $this->dispatcher->addListener('foo', [$test, 'foo']);
+        $this->assertTrue($this->dispatcher->hasListeners('foo'));
+        $this->dispatcher->removeListener('foo', [$factory, 'foo']);
+        $this->assertFalse($this->dispatcher->hasListeners('foo'));
+    }
+
+    public function testPriorityFindsLazyListeners()
+    {
+        $test = new TestWithDispatcher();
+        $factory = function () use ($test) { return $test; };
+
+        $this->dispatcher->addListener('foo', [$factory, 'foo'], 3);
+        $this->assertSame(3, $this->dispatcher->getListenerPriority('foo', [$test, 'foo']));
+        $this->dispatcher->removeListener('foo', [$factory, 'foo']);
+
+        $this->dispatcher->addListener('foo', [$test, 'foo'], 5);
+        $this->assertSame(5, $this->dispatcher->getListenerPriority('foo', [$factory, 'foo']));
+    }
+
+    public function testGetLazyListeners()
+    {
+        $test = new TestWithDispatcher();
+        $factory = function () use ($test) { return $test; };
+
+        $this->dispatcher->addListener('foo', [$factory, 'foo'], 3);
+        $this->assertSame([[$test, 'foo']], $this->dispatcher->getListeners('foo'));
+
+        $this->dispatcher->removeListener('foo', [$test, 'foo']);
+        $this->dispatcher->addListener('bar', [$factory, 'foo'], 3);
+        $this->assertSame(['bar' => [[$test, 'foo']]], $this->dispatcher->getListeners());
+    }
+
+    public function testMutatingWhilePropagationIsStopped()
+    {
+        $testLoaded = false;
+        $test = new TestEventListener();
+        $this->dispatcher->addListener('foo', [$test, 'postFoo']);
+        $this->dispatcher->addListener('foo', [function () use ($test, &$testLoaded) {
+            $testLoaded = true;
+
+            return $test;
+        }, 'preFoo']);
+
+        $this->dispatcher->dispatch(new Event(), 'foo');
+
+        $this->assertTrue($test->postFooInvoked);
+        $this->assertFalse($test->preFooInvoked);
+
+        $this->assertsame(0, $this->dispatcher->getListenerPriority('foo', [$test, 'preFoo']));
+
+        $test->preFoo(new Event());
+        $this->dispatcher->dispatch(new Event(), 'foo');
+
+        $this->assertTrue($testLoaded);
+    }
+
+    /**
+     * @group legacy
+     * @expectedDeprecation Calling the "Symfony\Component\EventDispatcher\EventDispatcherInterface::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.
+     */
+    public function testLegacySignatureWithoutEvent()
+    {
+        $this->dispatcher->dispatch('foo');
+    }
+
+    /**
+     * @group legacy
+     * @expectedDeprecation Calling the "Symfony\Component\EventDispatcher\EventDispatcherInterface::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.
+     */
+    public function testLegacySignatureWithEvent()
+    {
+        $this->dispatcher->dispatch('foo', new Event());
+    }
+
+    public function testLegacySignatureWithNewEventObject()
+    {
+        $this->expectException('TypeError');
+        $this->expectExceptionMessage('Argument 1 passed to "Symfony\Component\EventDispatcher\EventDispatcherInterface::dispatch()" must be an object, string given.');
+        $this->dispatcher->dispatch('foo', new ContractsEvent());
+    }
+}
+
+class CallableClass
+{
+    public function __invoke()
+    {
+    }
+}
+
+class TestEventListener
+{
+    public $preFooInvoked = false;
+    public $postFooInvoked = false;
+
+    /* Listener methods */
+
+    public function preFoo($e)
+    {
+        $this->preFooInvoked = true;
+    }
+
+    public function postFoo($e)
+    {
+        $this->postFooInvoked = true;
+
+        if (!$this->preFooInvoked) {
+            $e->stopPropagation();
+        }
+    }
+}
+
+class TestWithDispatcher
+{
+    public $name;
+    public $dispatcher;
+
+    public function foo($e, $name, $dispatcher)
+    {
+        $this->name = $name;
+        $this->dispatcher = $dispatcher;
+    }
+}
+
+class TestEventSubscriber implements EventSubscriberInterface
+{
+    public static function getSubscribedEvents()
+    {
+        return ['pre.foo' => 'preFoo', 'post.foo' => 'postFoo'];
+    }
+}
+
+class TestEventSubscriberWithPriorities implements EventSubscriberInterface
+{
+    public static function getSubscribedEvents()
+    {
+        return [
+            'pre.foo' => ['preFoo', 10],
+            'post.foo' => ['postFoo'],
+        ];
+    }
+}
+
+class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface
+{
+    public static function getSubscribedEvents()
+    {
+        return ['pre.foo' => [
+            ['preFoo1'],
+            ['preFoo2', 10],
+        ]];
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/EventTest.php b/vendor/symfony/event-dispatcher/Tests/EventTest.php
new file mode 100644
index 0000000..34ed3ba
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/EventTest.php
@@ -0,0 +1,55 @@
+<?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\EventDispatcher\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * @group legacy
+ */
+class EventTest extends TestCase
+{
+    /**
+     * @var \Symfony\Component\EventDispatcher\Event
+     */
+    protected $event;
+
+    /**
+     * Sets up the fixture, for example, opens a network connection.
+     * This method is called before a test is executed.
+     */
+    protected function setUp(): void
+    {
+        $this->event = new Event();
+    }
+
+    /**
+     * Tears down the fixture, for example, closes a network connection.
+     * This method is called after a test is executed.
+     */
+    protected function tearDown(): void
+    {
+        $this->event = null;
+    }
+
+    public function testIsPropagationStopped()
+    {
+        $this->assertFalse($this->event->isPropagationStopped());
+    }
+
+    public function testStopPropagationAndIsPropagationStopped()
+    {
+        $this->event->stopPropagation();
+        $this->assertTrue($this->event->isPropagationStopped());
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php b/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php
new file mode 100644
index 0000000..484e1a5
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php
@@ -0,0 +1,134 @@
+<?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\EventDispatcher\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\GenericEvent;
+
+/**
+ * Test class for Event.
+ */
+class GenericEventTest extends TestCase
+{
+    /**
+     * @var GenericEvent
+     */
+    private $event;
+
+    private $subject;
+
+    /**
+     * Prepares the environment before running a test.
+     */
+    protected function setUp(): void
+    {
+        $this->subject = new \stdClass();
+        $this->event = new GenericEvent($this->subject, ['name' => 'Event']);
+    }
+
+    /**
+     * Cleans up the environment after running a test.
+     */
+    protected function tearDown(): void
+    {
+        $this->subject = null;
+        $this->event = null;
+    }
+
+    public function testConstruct()
+    {
+        $this->assertEquals($this->event, new GenericEvent($this->subject, ['name' => 'Event']));
+    }
+
+    /**
+     * Tests Event->getArgs().
+     */
+    public function testGetArguments()
+    {
+        // test getting all
+        $this->assertSame(['name' => 'Event'], $this->event->getArguments());
+    }
+
+    public function testSetArguments()
+    {
+        $result = $this->event->setArguments(['foo' => 'bar']);
+        $this->assertSame(['foo' => 'bar'], $this->event->getArguments());
+        $this->assertSame($this->event, $result);
+    }
+
+    public function testSetArgument()
+    {
+        $result = $this->event->setArgument('foo2', 'bar2');
+        $this->assertSame(['name' => 'Event', 'foo2' => 'bar2'], $this->event->getArguments());
+        $this->assertEquals($this->event, $result);
+    }
+
+    public function testGetArgument()
+    {
+        // test getting key
+        $this->assertEquals('Event', $this->event->getArgument('name'));
+    }
+
+    public function testGetArgException()
+    {
+        $this->expectException('\InvalidArgumentException');
+        $this->event->getArgument('nameNotExist');
+    }
+
+    public function testOffsetGet()
+    {
+        // test getting key
+        $this->assertEquals('Event', $this->event['name']);
+
+        // test getting invalid arg
+        $this->expectException('InvalidArgumentException');
+        $this->assertFalse($this->event['nameNotExist']);
+    }
+
+    public function testOffsetSet()
+    {
+        $this->event['foo2'] = 'bar2';
+        $this->assertSame(['name' => 'Event', 'foo2' => 'bar2'], $this->event->getArguments());
+    }
+
+    public function testOffsetUnset()
+    {
+        unset($this->event['name']);
+        $this->assertSame([], $this->event->getArguments());
+    }
+
+    public function testOffsetIsset()
+    {
+        $this->assertArrayHasKey('name', $this->event);
+        $this->assertArrayNotHasKey('nameNotExist', $this->event);
+    }
+
+    public function testHasArgument()
+    {
+        $this->assertTrue($this->event->hasArgument('name'));
+        $this->assertFalse($this->event->hasArgument('nameNotExist'));
+    }
+
+    public function testGetSubject()
+    {
+        $this->assertSame($this->subject, $this->event->getSubject());
+    }
+
+    public function testHasIterator()
+    {
+        $data = [];
+        foreach ($this->event as $key => $value) {
+            $data[$key] = $value;
+        }
+        $this->assertEquals(['name' => 'Event'], $data);
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php
new file mode 100644
index 0000000..edce6f1
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php
@@ -0,0 +1,100 @@
+<?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\EventDispatcher\Tests;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class ImmutableEventDispatcherTest extends TestCase
+{
+    /**
+     * @var MockObject
+     */
+    private $innerDispatcher;
+
+    /**
+     * @var ImmutableEventDispatcher
+     */
+    private $dispatcher;
+
+    protected function setUp(): void
+    {
+        $this->innerDispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
+        $this->dispatcher = new ImmutableEventDispatcher($this->innerDispatcher);
+    }
+
+    public function testDispatchDelegates()
+    {
+        $event = new Event();
+        $resultEvent = new Event();
+
+        $this->innerDispatcher->expects($this->once())
+            ->method('dispatch')
+            ->with($event, 'event')
+            ->willReturn($resultEvent);
+
+        $this->assertSame($resultEvent, $this->dispatcher->dispatch('event', $event));
+    }
+
+    public function testGetListenersDelegates()
+    {
+        $this->innerDispatcher->expects($this->once())
+            ->method('getListeners')
+            ->with('event')
+            ->willReturn(['result']);
+
+        $this->assertSame(['result'], $this->dispatcher->getListeners('event'));
+    }
+
+    public function testHasListenersDelegates()
+    {
+        $this->innerDispatcher->expects($this->once())
+            ->method('hasListeners')
+            ->with('event')
+            ->willReturn(true);
+
+        $this->assertTrue($this->dispatcher->hasListeners('event'));
+    }
+
+    public function testAddListenerDisallowed()
+    {
+        $this->expectException('\BadMethodCallException');
+        $this->dispatcher->addListener('event', function () { return 'foo'; });
+    }
+
+    public function testAddSubscriberDisallowed()
+    {
+        $this->expectException('\BadMethodCallException');
+        $subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock();
+
+        $this->dispatcher->addSubscriber($subscriber);
+    }
+
+    public function testRemoveListenerDisallowed()
+    {
+        $this->expectException('\BadMethodCallException');
+        $this->dispatcher->removeListener('event', function () { return 'foo'; });
+    }
+
+    public function testRemoveSubscriberDisallowed()
+    {
+        $this->expectException('\BadMethodCallException');
+        $subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock();
+
+        $this->dispatcher->removeSubscriber($subscriber);
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/LegacyEventDispatcherProxyTest.php b/vendor/symfony/event-dispatcher/Tests/LegacyEventDispatcherProxyTest.php
new file mode 100644
index 0000000..09e5e92
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/LegacyEventDispatcherProxyTest.php
@@ -0,0 +1,63 @@
+<?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\EventDispatcher\Tests;
+
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
+use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
+
+/**
+ * @group legacy
+ */
+class LegacyEventDispatcherProxyTest extends EventDispatcherTest
+{
+    /**
+     * @group legacy
+     * @expectedDeprecation The signature of the "Symfony\Component\EventDispatcher\Tests\TestLegacyEventDispatcher::dispatch()" method should be updated to "dispatch($event, string $eventName = null)", not doing so is deprecated since Symfony 4.3.
+     * @expectedDeprecation Calling the "Symfony\Contracts\EventDispatcher\EventDispatcherInterface::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.
+     */
+    public function testLegacySignatureWithoutEvent()
+    {
+        $this->createEventDispatcher()->dispatch('foo');
+    }
+
+    /**
+     * @group legacy
+     * @expectedDeprecation The signature of the "Symfony\Component\EventDispatcher\Tests\TestLegacyEventDispatcher::dispatch()" method should be updated to "dispatch($event, string $eventName = null)", not doing so is deprecated since Symfony 4.3.
+     * @expectedDeprecation Calling the "Symfony\Contracts\EventDispatcher\EventDispatcherInterface::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.
+     */
+    public function testLegacySignatureWithEvent()
+    {
+        $this->createEventDispatcher()->dispatch('foo', new Event());
+    }
+
+    public function testLegacySignatureWithNewEventObject()
+    {
+        $this->expectException('TypeError');
+        $this->expectExceptionMessage('Argument 1 passed to "Symfony\Contracts\EventDispatcher\EventDispatcherInterface::dispatch()" must be an object, string given.');
+        $this->createEventDispatcher()->dispatch('foo', new ContractsEvent());
+    }
+
+    protected function createEventDispatcher()
+    {
+        return LegacyEventDispatcherProxy::decorate(new TestLegacyEventDispatcher());
+    }
+}
+
+class TestLegacyEventDispatcher extends EventDispatcher
+{
+    public function dispatch($eventName, Event $event = null)
+    {
+        return parent::dispatch($event, $eventName);
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/composer.json b/vendor/symfony/event-dispatcher/composer.json
new file mode 100644
index 0000000..8449c47
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/composer.json
@@ -0,0 +1,54 @@
+{
+    "name": "symfony/event-dispatcher",
+    "type": "library",
+    "description": "Symfony EventDispatcher 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.1.3",
+        "symfony/event-dispatcher-contracts": "^1.1"
+    },
+    "require-dev": {
+        "symfony/dependency-injection": "~3.4|~4.0",
+        "symfony/expression-language": "~3.4|~4.0",
+        "symfony/config": "~3.4|~4.0",
+        "symfony/http-foundation": "^3.4|^4.0",
+        "symfony/service-contracts": "^1.1",
+        "symfony/stopwatch": "~3.4|~4.0",
+        "psr/log": "~1.0"
+    },
+    "conflict": {
+        "symfony/dependency-injection": "<3.4"
+    },
+    "provide": {
+        "psr/event-dispatcher-implementation": "1.0",
+        "symfony/event-dispatcher-implementation": "1.1"
+    },
+    "suggest": {
+        "symfony/dependency-injection": "",
+        "symfony/http-kernel": ""
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" },
+        "exclude-from-classmap": [
+            "/Tests/"
+        ]
+    },
+    "minimum-stability": "dev",
+    "extra": {
+        "branch-alias": {
+            "dev-master": "4.3-dev"
+        }
+    }
+}
diff --git a/vendor/symfony/event-dispatcher/phpunit.xml.dist b/vendor/symfony/event-dispatcher/phpunit.xml.dist
new file mode 100644
index 0000000..f2eb169
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/phpunit.xml.dist
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
+         backupGlobals="false"
+         colors="true"
+         bootstrap="vendor/autoload.php"
+         failOnRisky="true"
+         failOnWarning="true"
+>
+    <php>
+        <ini name="error_reporting" value="-1" />
+    </php>
+
+    <testsuites>
+        <testsuite name="Symfony EventDispatcher Component Test Suite">
+            <directory>./Tests/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory>./</directory>
+            <exclude>
+                <directory>./Resources</directory>
+                <directory>./Tests</directory>
+                <directory>./vendor</directory>
+            </exclude>
+        </whitelist>
+    </filter>
+</phpunit>
diff --git a/vendor/symfony/http-foundation/.gitignore b/vendor/symfony/http-foundation/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/vendor/symfony/http-foundation/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php b/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php
new file mode 100644
index 0000000..c49f53a
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php
@@ -0,0 +1,21 @@
+<?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\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an UPLOAD_ERR_CANT_WRITE error occurred with UploadedFile.
+ *
+ * @author Florent Mata <florentmata@gmail.com>
+ */
+class CannotWriteFileException extends FileException
+{
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php b/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php
new file mode 100644
index 0000000..ed83499
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php
@@ -0,0 +1,21 @@
+<?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\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an UPLOAD_ERR_EXTENSION error occurred with UploadedFile.
+ *
+ * @author Florent Mata <florentmata@gmail.com>
+ */
+class ExtensionFileException extends FileException
+{
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php b/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php
new file mode 100644
index 0000000..8741be0
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php
@@ -0,0 +1,21 @@
+<?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\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an UPLOAD_ERR_FORM_SIZE error occurred with UploadedFile.
+ *
+ * @author Florent Mata <florentmata@gmail.com>
+ */
+class FormSizeFileException extends FileException
+{
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php b/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php
new file mode 100644
index 0000000..c8fde61
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php
@@ -0,0 +1,21 @@
+<?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\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an UPLOAD_ERR_INI_SIZE error occurred with UploadedFile.
+ *
+ * @author Florent Mata <florentmata@gmail.com>
+ */
+class IniSizeFileException extends FileException
+{
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/NoFileException.php b/vendor/symfony/http-foundation/File/Exception/NoFileException.php
new file mode 100644
index 0000000..4b48cc7
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/NoFileException.php
@@ -0,0 +1,21 @@
+<?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\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an UPLOAD_ERR_NO_FILE error occurred with UploadedFile.
+ *
+ * @author Florent Mata <florentmata@gmail.com>
+ */
+class NoFileException extends FileException
+{
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php b/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php
new file mode 100644
index 0000000..bdead2d
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php
@@ -0,0 +1,21 @@
+<?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\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an UPLOAD_ERR_NO_TMP_DIR error occurred with UploadedFile.
+ *
+ * @author Florent Mata <florentmata@gmail.com>
+ */
+class NoTmpDirFileException extends FileException
+{
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/PartialFileException.php b/vendor/symfony/http-foundation/File/Exception/PartialFileException.php
new file mode 100644
index 0000000..4641efb
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/PartialFileException.php
@@ -0,0 +1,21 @@
+<?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\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an UPLOAD_ERR_PARTIAL error occurred with UploadedFile.
+ *
+ * @author Florent Mata <florentmata@gmail.com>
+ */
+class PartialFileException extends FileException
+{
+}
diff --git a/vendor/symfony/http-foundation/HeaderUtils.php b/vendor/symfony/http-foundation/HeaderUtils.php
new file mode 100644
index 0000000..5866e3b
--- /dev/null
+++ b/vendor/symfony/http-foundation/HeaderUtils.php
@@ -0,0 +1,224 @@
+<?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\HttpFoundation;
+
+/**
+ * HTTP header utility functions.
+ *
+ * @author Christian Schmidt <github@chsc.dk>
+ */
+class HeaderUtils
+{
+    public const DISPOSITION_ATTACHMENT = 'attachment';
+    public const DISPOSITION_INLINE = 'inline';
+
+    /**
+     * This class should not be instantiated.
+     */
+    private function __construct()
+    {
+    }
+
+    /**
+     * Splits an HTTP header by one or more separators.
+     *
+     * Example:
+     *
+     *     HeaderUtils::split("da, en-gb;q=0.8", ",;")
+     *     // => ['da'], ['en-gb', 'q=0.8']]
+     *
+     * @param string $separators List of characters to split on, ordered by
+     *                           precedence, e.g. ",", ";=", or ",;="
+     *
+     * @return array Nested array with as many levels as there are characters in
+     *               $separators
+     */
+    public static function split(string $header, string $separators): array
+    {
+        $quotedSeparators = preg_quote($separators, '/');
+
+        preg_match_all('
+            /
+                (?!\s)
+                    (?:
+                        # quoted-string
+                        "(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$)
+                    |
+                        # token
+                        [^"'.$quotedSeparators.']+
+                    )+
+                (?<!\s)
+            |
+                # separator
+                \s*
+                (?<separator>['.$quotedSeparators.'])
+                \s*
+            /x', trim($header), $matches, PREG_SET_ORDER);
+
+        return self::groupParts($matches, $separators);
+    }
+
+    /**
+     * Combines an array of arrays into one associative array.
+     *
+     * Each of the nested arrays should have one or two elements. The first
+     * value will be used as the keys in the associative array, and the second
+     * will be used as the values, or true if the nested array only contains one
+     * element. Array keys are lowercased.
+     *
+     * Example:
+     *
+     *     HeaderUtils::combine([["foo", "abc"], ["bar"]])
+     *     // => ["foo" => "abc", "bar" => true]
+     */
+    public static function combine(array $parts): array
+    {
+        $assoc = [];
+        foreach ($parts as $part) {
+            $name = strtolower($part[0]);
+            $value = $part[1] ?? true;
+            $assoc[$name] = $value;
+        }
+
+        return $assoc;
+    }
+
+    /**
+     * Joins an associative array into a string for use in an HTTP header.
+     *
+     * The key and value of each entry are joined with "=", and all entries
+     * are joined with the specified separator and an additional space (for
+     * readability). Values are quoted if necessary.
+     *
+     * Example:
+     *
+     *     HeaderUtils::toString(["foo" => "abc", "bar" => true, "baz" => "a b c"], ",")
+     *     // => 'foo=abc, bar, baz="a b c"'
+     */
+    public static function toString(array $assoc, string $separator): string
+    {
+        $parts = [];
+        foreach ($assoc as $name => $value) {
+            if (true === $value) {
+                $parts[] = $name;
+            } else {
+                $parts[] = $name.'='.self::quote($value);
+            }
+        }
+
+        return implode($separator.' ', $parts);
+    }
+
+    /**
+     * Encodes a string as a quoted string, if necessary.
+     *
+     * If a string contains characters not allowed by the "token" construct in
+     * the HTTP specification, it is backslash-escaped and enclosed in quotes
+     * to match the "quoted-string" construct.
+     */
+    public static function quote(string $s): string
+    {
+        if (preg_match('/^[a-z0-9!#$%&\'*.^_`|~-]+$/i', $s)) {
+            return $s;
+        }
+
+        return '"'.addcslashes($s, '"\\"').'"';
+    }
+
+    /**
+     * Decodes a quoted string.
+     *
+     * If passed an unquoted string that matches the "token" construct (as
+     * defined in the HTTP specification), it is passed through verbatimly.
+     */
+    public static function unquote(string $s): string
+    {
+        return preg_replace('/\\\\(.)|"/', '$1', $s);
+    }
+
+    /**
+     * Generates a HTTP Content-Disposition field-value.
+     *
+     * @param string $disposition      One of "inline" or "attachment"
+     * @param string $filename         A unicode string
+     * @param string $filenameFallback A string containing only ASCII characters that
+     *                                 is semantically equivalent to $filename. If the filename is already ASCII,
+     *                                 it can be omitted, or just copied from $filename
+     *
+     * @return string A string suitable for use as a Content-Disposition field-value
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @see RFC 6266
+     */
+    public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string
+    {
+        if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) {
+            throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
+        }
+
+        if ('' === $filenameFallback) {
+            $filenameFallback = $filename;
+        }
+
+        // filenameFallback is not ASCII.
+        if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) {
+            throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.');
+        }
+
+        // percent characters aren't safe in fallback.
+        if (false !== strpos($filenameFallback, '%')) {
+            throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');
+        }
+
+        // path separators aren't allowed in either.
+        if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) {
+            throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');
+        }
+
+        $params = ['filename' => $filenameFallback];
+        if ($filename !== $filenameFallback) {
+            $params['filename*'] = "utf-8''".rawurlencode($filename);
+        }
+
+        return $disposition.'; '.self::toString($params, ';');
+    }
+
+    private static function groupParts(array $matches, string $separators): array
+    {
+        $separator = $separators[0];
+        $partSeparators = substr($separators, 1);
+
+        $i = 0;
+        $partMatches = [];
+        foreach ($matches as $match) {
+            if (isset($match['separator']) && $match['separator'] === $separator) {
+                ++$i;
+            } else {
+                $partMatches[$i][] = $match;
+            }
+        }
+
+        $parts = [];
+        if ($partSeparators) {
+            foreach ($partMatches as $matches) {
+                $parts[] = self::groupParts($matches, $partSeparators);
+            }
+        } else {
+            foreach ($partMatches as $matches) {
+                $parts[] = self::unquote($matches[0][0]);
+            }
+        }
+
+        return $parts;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/SessionUtils.php b/vendor/symfony/http-foundation/Session/SessionUtils.php
new file mode 100644
index 0000000..b5bce4a
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/SessionUtils.php
@@ -0,0 +1,59 @@
+<?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\HttpFoundation\Session;
+
+/**
+ * Session utility functions.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ * @author Rémon van de Kamp <rpkamp@gmail.com>
+ *
+ * @internal
+ */
+final class SessionUtils
+{
+    /**
+     * Finds the session header amongst the headers that are to be sent, removes it, and returns
+     * it so the caller can process it further.
+     */
+    public static function popSessionCookie(string $sessionName, string $sessionId): ?string
+    {
+        $sessionCookie = null;
+        $sessionCookiePrefix = sprintf(' %s=', urlencode($sessionName));
+        $sessionCookieWithId = sprintf('%s%s;', $sessionCookiePrefix, urlencode($sessionId));
+        $otherCookies = [];
+        foreach (headers_list() as $h) {
+            if (0 !== stripos($h, 'Set-Cookie:')) {
+                continue;
+            }
+            if (11 === strpos($h, $sessionCookiePrefix, 11)) {
+                $sessionCookie = $h;
+
+                if (11 !== strpos($h, $sessionCookieWithId, 11)) {
+                    $otherCookies[] = $h;
+                }
+            } else {
+                $otherCookies[] = $h;
+            }
+        }
+        if (null === $sessionCookie) {
+            return null;
+        }
+
+        header_remove('Set-Cookie');
+        foreach ($otherCookies as $h) {
+            header($h, false);
+        }
+
+        return $sessionCookie;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php
new file mode 100644
index 0000000..253d8cb
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php
@@ -0,0 +1,124 @@
+<?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\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Migrating session handler for migrating from one handler to another. It reads
+ * from the current handler and writes both the current and new ones.
+ *
+ * It ignores errors from the new handler.
+ *
+ * @author Ross Motley <ross.motley@amara.com>
+ * @author Oliver Radwell <oliver.radwell@amara.com>
+ */
+class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
+{
+    private $currentHandler;
+    private $writeOnlyHandler;
+
+    public function __construct(\SessionHandlerInterface $currentHandler, \SessionHandlerInterface $writeOnlyHandler)
+    {
+        if (!$currentHandler instanceof \SessionUpdateTimestampHandlerInterface) {
+            $currentHandler = new StrictSessionHandler($currentHandler);
+        }
+        if (!$writeOnlyHandler instanceof \SessionUpdateTimestampHandlerInterface) {
+            $writeOnlyHandler = new StrictSessionHandler($writeOnlyHandler);
+        }
+
+        $this->currentHandler = $currentHandler;
+        $this->writeOnlyHandler = $writeOnlyHandler;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function close()
+    {
+        $result = $this->currentHandler->close();
+        $this->writeOnlyHandler->close();
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function destroy($sessionId)
+    {
+        $result = $this->currentHandler->destroy($sessionId);
+        $this->writeOnlyHandler->destroy($sessionId);
+
+        return $result;
+    }
+
+    /**
+     * @return bool
+     */
+    public function gc($maxlifetime)
+    {
+        $result = $this->currentHandler->gc($maxlifetime);
+        $this->writeOnlyHandler->gc($maxlifetime);
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function open($savePath, $sessionName)
+    {
+        $result = $this->currentHandler->open($savePath, $sessionName);
+        $this->writeOnlyHandler->open($savePath, $sessionName);
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function read($sessionId)
+    {
+        // No reading from new handler until switch-over
+        return $this->currentHandler->read($sessionId);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function write($sessionId, $sessionData)
+    {
+        $result = $this->currentHandler->write($sessionId, $sessionData);
+        $this->writeOnlyHandler->write($sessionId, $sessionData);
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function validateId($sessionId)
+    {
+        // No reading from new handler until switch-over
+        return $this->currentHandler->validateId($sessionId);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function updateTimestamp($sessionId, $sessionData)
+    {
+        $result = $this->currentHandler->updateTimestamp($sessionId, $sessionData);
+        $this->writeOnlyHandler->updateTimestamp($sessionId, $sessionData);
+
+        return $result;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php
new file mode 100644
index 0000000..46ca679
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php
@@ -0,0 +1,114 @@
+<?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\HttpFoundation\Session\Storage\Handler;
+
+use Predis\Response\ErrorInterface;
+use Symfony\Component\Cache\Traits\RedisClusterProxy;
+use Symfony\Component\Cache\Traits\RedisProxy;
+
+/**
+ * Redis based session storage handler based on the Redis class
+ * provided by the PHP redis extension.
+ *
+ * @author Dalibor Karlović <dalibor@flexolabs.io>
+ */
+class RedisSessionHandler extends AbstractSessionHandler
+{
+    private $redis;
+
+    /**
+     * @var string Key prefix for shared environments
+     */
+    private $prefix;
+
+    /**
+     * List of available options:
+     *  * prefix: The prefix to use for the keys in order to avoid collision on the Redis server.
+     *
+     * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy $redis
+     * @param array                                                               $options An associative array of options
+     *
+     * @throws \InvalidArgumentException When unsupported client or options are passed
+     */
+    public function __construct($redis, array $options = [])
+    {
+        if (
+            !$redis instanceof \Redis &&
+            !$redis instanceof \RedisArray &&
+            !$redis instanceof \RedisCluster &&
+            !$redis instanceof \Predis\ClientInterface &&
+            !$redis instanceof RedisProxy &&
+            !$redis instanceof RedisClusterProxy
+        ) {
+            throw new \InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, %s given', __METHOD__, \is_object($redis) ? \get_class($redis) : \gettype($redis)));
+        }
+
+        if ($diff = array_diff(array_keys($options), ['prefix'])) {
+            throw new \InvalidArgumentException(sprintf('The following options are not supported "%s"', implode(', ', $diff)));
+        }
+
+        $this->redis = $redis;
+        $this->prefix = $options['prefix'] ?? 'sf_s';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doRead($sessionId): string
+    {
+        return $this->redis->get($this->prefix.$sessionId) ?: '';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doWrite($sessionId, $data): bool
+    {
+        $result = $this->redis->setEx($this->prefix.$sessionId, (int) ini_get('session.gc_maxlifetime'), $data);
+
+        return $result && !$result instanceof ErrorInterface;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDestroy($sessionId): bool
+    {
+        $this->redis->del($this->prefix.$sessionId);
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function close(): bool
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function gc($maxlifetime): bool
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function updateTimestamp($sessionId, $data)
+    {
+        return (bool) $this->redis->expire($this->prefix.$sessionId, (int) ini_get('session.gc_maxlifetime'));
+    }
+}
diff --git a/vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php b/vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php
new file mode 100644
index 0000000..2d10562
--- /dev/null
+++ b/vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php
@@ -0,0 +1,54 @@
+<?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\HttpFoundation\Test\Constraint;
+
+use PHPUnit\Framework\Constraint\Constraint;
+
+final class RequestAttributeValueSame extends Constraint
+{
+    private $name;
+    private $value;
+
+    public function __construct(string $name, string $value)
+    {
+        $this->name = $name;
+        $this->value = $value;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function toString(): string
+    {
+        return sprintf('has attribute "%s" with value "%s"', $this->name, $this->value);
+    }
+
+    /**
+     * @param Request $request
+     *
+     * {@inheritdoc}
+     */
+    protected function matches($request): bool
+    {
+        return $this->value === $request->attributes->get($this->name);
+    }
+
+    /**
+     * @param Request $request
+     *
+     * {@inheritdoc}
+     */
+    protected function failureDescription($request): string
+    {
+        return 'the Request '.$this->toString();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php
new file mode 100644
index 0000000..554e1a1
--- /dev/null
+++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.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\HttpFoundation\Test\Constraint;
+
+use PHPUnit\Framework\Constraint\Constraint;
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\Response;
+
+final class ResponseCookieValueSame extends Constraint
+{
+    private $name;
+    private $value;
+    private $path;
+    private $domain;
+
+    public function __construct(string $name, string $value, string $path = '/', string $domain = null)
+    {
+        $this->name = $name;
+        $this->value = $value;
+        $this->path = $path;
+        $this->domain = $domain;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function toString(): string
+    {
+        $str = sprintf('has cookie "%s"', $this->name);
+        if ('/' !== $this->path) {
+            $str .= sprintf(' with path "%s"', $this->path);
+        }
+        if ($this->domain) {
+            $str .= sprintf(' for domain "%s"', $this->domain);
+        }
+        $str .= sprintf(' with value "%s"', $this->value);
+
+        return $str;
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function matches($response): bool
+    {
+        $cookie = $this->getCookie($response);
+        if (!$cookie) {
+            return false;
+        }
+
+        return $this->value === $cookie->getValue();
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function failureDescription($response): string
+    {
+        return 'the Response '.$this->toString();
+    }
+
+    protected function getCookie(Response $response): ?Cookie
+    {
+        $cookies = $response->headers->getCookies();
+
+        $filteredCookies = array_filter($cookies, function (Cookie $cookie) {
+            return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain;
+        });
+
+        return reset($filteredCookies) ?: null;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php
new file mode 100644
index 0000000..bd792b0
--- /dev/null
+++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php
@@ -0,0 +1,77 @@
+<?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\HttpFoundation\Test\Constraint;
+
+use PHPUnit\Framework\Constraint\Constraint;
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\Response;
+
+final class ResponseHasCookie extends Constraint
+{
+    private $name;
+    private $path;
+    private $domain;
+
+    public function __construct(string $name, string $path = '/', string $domain = null)
+    {
+        $this->name = $name;
+        $this->path = $path;
+        $this->domain = $domain;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function toString(): string
+    {
+        $str = sprintf('has cookie "%s"', $this->name);
+        if ('/' !== $this->path) {
+            $str .= sprintf(' with path "%s"', $this->path);
+        }
+        if ($this->domain) {
+            $str .= sprintf(' for domain "%s"', $this->domain);
+        }
+
+        return $str;
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function matches($response): bool
+    {
+        return null !== $this->getCookie($response);
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function failureDescription($response): string
+    {
+        return 'the Response '.$this->toString();
+    }
+
+    protected function getCookie(Response $response): ?Cookie
+    {
+        $cookies = $response->headers->getCookies();
+
+        $filteredCookies = array_filter($cookies, function (Cookie $cookie) {
+            return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain;
+        });
+
+        return reset($filteredCookies) ?: null;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php
new file mode 100644
index 0000000..68ad827
--- /dev/null
+++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.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\HttpFoundation\Test\Constraint;
+
+use PHPUnit\Framework\Constraint\Constraint;
+use Symfony\Component\HttpFoundation\Response;
+
+final class ResponseHasHeader extends Constraint
+{
+    private $headerName;
+
+    public function __construct(string $headerName)
+    {
+        $this->headerName = $headerName;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function toString(): string
+    {
+        return sprintf('has header "%s"', $this->headerName);
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function matches($response): bool
+    {
+        return $response->headers->has($this->headerName);
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function failureDescription($response): string
+    {
+        return 'the Response '.$this->toString();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php
new file mode 100644
index 0000000..acdea71
--- /dev/null
+++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php
@@ -0,0 +1,55 @@
+<?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\HttpFoundation\Test\Constraint;
+
+use PHPUnit\Framework\Constraint\Constraint;
+use Symfony\Component\HttpFoundation\Response;
+
+final class ResponseHeaderSame extends Constraint
+{
+    private $headerName;
+    private $expectedValue;
+
+    public function __construct(string $headerName, string $expectedValue)
+    {
+        $this->headerName = $headerName;
+        $this->expectedValue = $expectedValue;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function toString(): string
+    {
+        return sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue);
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function matches($response): bool
+    {
+        return $this->expectedValue === $response->headers->get($this->headerName, null, true);
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function failureDescription($response): string
+    {
+        return 'the Response '.$this->toString();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php
new file mode 100644
index 0000000..8c4b883
--- /dev/null
+++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.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\HttpFoundation\Test\Constraint;
+
+use PHPUnit\Framework\Constraint\Constraint;
+use Symfony\Component\HttpFoundation\Response;
+
+final class ResponseIsRedirected extends Constraint
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function toString(): string
+    {
+        return 'is redirected';
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function matches($response): bool
+    {
+        return $response->isRedirect();
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function failureDescription($response): string
+    {
+        return 'the Response '.$this->toString();
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function additionalFailureDescription($response): string
+    {
+        return (string) $response;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php
new file mode 100644
index 0000000..9c66558
--- /dev/null
+++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.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\HttpFoundation\Test\Constraint;
+
+use PHPUnit\Framework\Constraint\Constraint;
+use Symfony\Component\HttpFoundation\Response;
+
+final class ResponseIsSuccessful extends Constraint
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function toString(): string
+    {
+        return 'is successful';
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function matches($response): bool
+    {
+        return $response->isSuccessful();
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function failureDescription($response): string
+    {
+        return 'the Response '.$this->toString();
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function additionalFailureDescription($response): string
+    {
+        return (string) $response;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php
new file mode 100644
index 0000000..72bb000
--- /dev/null
+++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php
@@ -0,0 +1,63 @@
+<?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\HttpFoundation\Test\Constraint;
+
+use PHPUnit\Framework\Constraint\Constraint;
+use Symfony\Component\HttpFoundation\Response;
+
+final class ResponseStatusCodeSame extends Constraint
+{
+    private $statusCode;
+
+    public function __construct(int $statusCode)
+    {
+        $this->statusCode = $statusCode;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function toString(): string
+    {
+        return 'status code is '.$this->statusCode;
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function matches($response): bool
+    {
+        return $this->statusCode === $response->getStatusCode();
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function failureDescription($response): string
+    {
+        return 'the Response '.$this->toString();
+    }
+
+    /**
+     * @param Response $response
+     *
+     * {@inheritdoc}
+     */
+    protected function additionalFailureDescription($response): string
+    {
+        return (string) $response;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/-test b/vendor/symfony/http-foundation/Tests/File/Fixtures/-test
new file mode 100644
index 0000000..b636f4b
Binary files /dev/null and b/vendor/symfony/http-foundation/Tests/File/Fixtures/-test differ
diff --git a/vendor/symfony/http-foundation/Tests/HeaderUtilsTest.php b/vendor/symfony/http-foundation/Tests/HeaderUtilsTest.php
new file mode 100644
index 0000000..d2b19ca
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/HeaderUtilsTest.php
@@ -0,0 +1,132 @@
+<?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\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\HeaderUtils;
+
+class HeaderUtilsTest extends TestCase
+{
+    public function testSplit()
+    {
+        $this->assertSame(['foo=123', 'bar'], HeaderUtils::split('foo=123,bar', ','));
+        $this->assertSame(['foo=123', 'bar'], HeaderUtils::split('foo=123, bar', ','));
+        $this->assertSame([['foo=123', 'bar']], HeaderUtils::split('foo=123; bar', ',;'));
+        $this->assertSame([['foo=123'], ['bar']], HeaderUtils::split('foo=123, bar', ',;'));
+        $this->assertSame(['foo', '123, bar'], HeaderUtils::split('foo=123, bar', '='));
+        $this->assertSame(['foo', '123, bar'], HeaderUtils::split(' foo = 123, bar ', '='));
+        $this->assertSame([['foo', '123'], ['bar']], HeaderUtils::split('foo=123, bar', ',='));
+        $this->assertSame([[['foo', '123']], [['bar'], ['foo', '456']]], HeaderUtils::split('foo=123, bar; foo=456', ',;='));
+        $this->assertSame([[['foo', 'a,b;c=d']]], HeaderUtils::split('foo="a,b;c=d"', ',;='));
+
+        $this->assertSame(['foo', 'bar'], HeaderUtils::split('foo,,,, bar', ','));
+        $this->assertSame(['foo', 'bar'], HeaderUtils::split(',foo, bar,', ','));
+        $this->assertSame(['foo', 'bar'], HeaderUtils::split(' , foo, bar, ', ','));
+        $this->assertSame(['foo bar'], HeaderUtils::split('foo "bar"', ','));
+        $this->assertSame(['foo bar'], HeaderUtils::split('"foo" bar', ','));
+        $this->assertSame(['foo bar'], HeaderUtils::split('"foo" "bar"', ','));
+
+        // These are not a valid header values. We test that they parse anyway,
+        // and that both the valid and invalid parts are returned.
+        $this->assertSame([], HeaderUtils::split('', ','));
+        $this->assertSame([], HeaderUtils::split(',,,', ','));
+        $this->assertSame(['foo', 'bar', 'baz'], HeaderUtils::split('foo, "bar", "baz', ','));
+        $this->assertSame(['foo', 'bar, baz'], HeaderUtils::split('foo, "bar, baz', ','));
+        $this->assertSame(['foo', 'bar, baz\\'], HeaderUtils::split('foo, "bar, baz\\', ','));
+        $this->assertSame(['foo', 'bar, baz\\'], HeaderUtils::split('foo, "bar, baz\\\\', ','));
+    }
+
+    public function testCombine()
+    {
+        $this->assertSame(['foo' => '123'], HeaderUtils::combine([['foo', '123']]));
+        $this->assertSame(['foo' => true], HeaderUtils::combine([['foo']]));
+        $this->assertSame(['foo' => true], HeaderUtils::combine([['Foo']]));
+        $this->assertSame(['foo' => '123', 'bar' => true], HeaderUtils::combine([['foo', '123'], ['bar']]));
+    }
+
+    public function testToString()
+    {
+        $this->assertSame('foo', HeaderUtils::toString(['foo' => true], ','));
+        $this->assertSame('foo; bar', HeaderUtils::toString(['foo' => true, 'bar' => true], ';'));
+        $this->assertSame('foo=123', HeaderUtils::toString(['foo' => '123'], ','));
+        $this->assertSame('foo="1 2 3"', HeaderUtils::toString(['foo' => '1 2 3'], ','));
+        $this->assertSame('foo="1 2 3", bar', HeaderUtils::toString(['foo' => '1 2 3', 'bar' => true], ','));
+    }
+
+    public function testQuote()
+    {
+        $this->assertSame('foo', HeaderUtils::quote('foo'));
+        $this->assertSame('az09!#$%&\'*.^_`|~-', HeaderUtils::quote('az09!#$%&\'*.^_`|~-'));
+        $this->assertSame('"foo bar"', HeaderUtils::quote('foo bar'));
+        $this->assertSame('"foo [bar]"', HeaderUtils::quote('foo [bar]'));
+        $this->assertSame('"foo \"bar\""', HeaderUtils::quote('foo "bar"'));
+        $this->assertSame('"foo \\\\ bar"', HeaderUtils::quote('foo \\ bar'));
+    }
+
+    public function testUnquote()
+    {
+        $this->assertEquals('foo', HeaderUtils::unquote('foo'));
+        $this->assertEquals('az09!#$%&\'*.^_`|~-', HeaderUtils::unquote('az09!#$%&\'*.^_`|~-'));
+        $this->assertEquals('foo bar', HeaderUtils::unquote('"foo bar"'));
+        $this->assertEquals('foo [bar]', HeaderUtils::unquote('"foo [bar]"'));
+        $this->assertEquals('foo "bar"', HeaderUtils::unquote('"foo \"bar\""'));
+        $this->assertEquals('foo "bar"', HeaderUtils::unquote('"foo \"\b\a\r\""'));
+        $this->assertEquals('foo \\ bar', HeaderUtils::unquote('"foo \\\\ bar"'));
+    }
+
+    public function testMakeDispositionInvalidDisposition()
+    {
+        $this->expectException('InvalidArgumentException');
+        HeaderUtils::makeDisposition('invalid', 'foo.html');
+    }
+
+    /**
+     * @dataProvider provideMakeDisposition
+     */
+    public function testMakeDisposition($disposition, $filename, $filenameFallback, $expected)
+    {
+        $this->assertEquals($expected, HeaderUtils::makeDisposition($disposition, $filename, $filenameFallback));
+    }
+
+    public function provideMakeDisposition()
+    {
+        return [
+            ['attachment', 'foo.html', 'foo.html', 'attachment; filename=foo.html'],
+            ['attachment', 'foo.html', '', 'attachment; filename=foo.html'],
+            ['attachment', 'foo bar.html', '', 'attachment; filename="foo bar.html"'],
+            ['attachment', 'foo "bar".html', '', 'attachment; filename="foo \\"bar\\".html"'],
+            ['attachment', 'foo%20bar.html', 'foo bar.html', 'attachment; filename="foo bar.html"; filename*=utf-8\'\'foo%2520bar.html'],
+            ['attachment', 'föö.html', 'foo.html', 'attachment; filename=foo.html; filename*=utf-8\'\'f%C3%B6%C3%B6.html'],
+        ];
+    }
+
+    /**
+     * @dataProvider provideMakeDispositionFail
+     */
+    public function testMakeDispositionFail($disposition, $filename)
+    {
+        $this->expectException('InvalidArgumentException');
+        HeaderUtils::makeDisposition($disposition, $filename);
+    }
+
+    public function provideMakeDispositionFail()
+    {
+        return [
+            ['attachment', 'foo%20bar.html'],
+            ['attachment', 'foo/bar.html'],
+            ['attachment', '/foo.html'],
+            ['attachment', 'foo\bar.html'],
+            ['attachment', '\foo.html'],
+            ['attachment', 'föö.html'],
+        ];
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php
new file mode 100644
index 0000000..6a15a06
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php
@@ -0,0 +1,145 @@
+<?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\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler;
+
+/**
+ * @requires extension redis
+ * @group time-sensitive
+ */
+abstract class AbstractRedisSessionHandlerTestCase extends TestCase
+{
+    protected const PREFIX = 'prefix_';
+
+    /**
+     * @var RedisSessionHandler
+     */
+    protected $storage;
+
+    /**
+     * @var \Redis|\RedisArray|\RedisCluster|\Predis\Client
+     */
+    protected $redisClient;
+
+    /**
+     * @return \Redis|\RedisArray|\RedisCluster|\Predis\Client
+     */
+    abstract protected function createRedisClient(string $host);
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+
+        if (!\extension_loaded('redis')) {
+            self::markTestSkipped('Extension redis required.');
+        }
+
+        $host = getenv('REDIS_HOST') ?: 'localhost';
+
+        $this->redisClient = $this->createRedisClient($host);
+        $this->storage = new RedisSessionHandler(
+            $this->redisClient,
+            ['prefix' => self::PREFIX]
+        );
+    }
+
+    protected function tearDown(): void
+    {
+        $this->redisClient = null;
+        $this->storage = null;
+
+        parent::tearDown();
+    }
+
+    public function testOpenSession()
+    {
+        $this->assertTrue($this->storage->open('', ''));
+    }
+
+    public function testCloseSession()
+    {
+        $this->assertTrue($this->storage->close());
+    }
+
+    public function testReadSession()
+    {
+        $this->redisClient->set(self::PREFIX.'id1', null);
+        $this->redisClient->set(self::PREFIX.'id2', 'abc123');
+
+        $this->assertEquals('', $this->storage->read('id1'));
+        $this->assertEquals('abc123', $this->storage->read('id2'));
+    }
+
+    public function testWriteSession()
+    {
+        $this->assertTrue($this->storage->write('id', 'data'));
+
+        $this->assertTrue((bool) $this->redisClient->exists(self::PREFIX.'id'));
+        $this->assertEquals('data', $this->redisClient->get(self::PREFIX.'id'));
+    }
+
+    public function testUseSessionGcMaxLifetimeAsTimeToLive()
+    {
+        $this->storage->write('id', 'data');
+        $ttl = $this->redisClient->ttl(self::PREFIX.'id');
+
+        $this->assertLessThanOrEqual(ini_get('session.gc_maxlifetime'), $ttl);
+        $this->assertGreaterThanOrEqual(0, $ttl);
+    }
+
+    public function testDestroySession()
+    {
+        $this->redisClient->set(self::PREFIX.'id', 'foo');
+
+        $this->assertTrue((bool) $this->redisClient->exists(self::PREFIX.'id'));
+        $this->assertTrue($this->storage->destroy('id'));
+        $this->assertFalse((bool) $this->redisClient->exists(self::PREFIX.'id'));
+    }
+
+    public function testGcSession()
+    {
+        $this->assertTrue($this->storage->gc(123));
+    }
+
+    public function testUpdateTimestamp()
+    {
+        $lowTtl = 10;
+
+        $this->redisClient->setex(self::PREFIX.'id', $lowTtl, 'foo');
+        $this->storage->updateTimestamp('id', []);
+
+        $this->assertGreaterThan($lowTtl, $this->redisClient->ttl(self::PREFIX.'id'));
+    }
+
+    /**
+     * @dataProvider getOptionFixtures
+     */
+    public function testSupportedParam(array $options, bool $supported)
+    {
+        try {
+            new RedisSessionHandler($this->redisClient, $options);
+            $this->assertTrue($supported);
+        } catch (\InvalidArgumentException $e) {
+            $this->assertFalse($supported);
+        }
+    }
+
+    public function getOptionFixtures(): array
+    {
+        return [
+            [['prefix' => 'session'], true],
+            [['prefix' => 'sfs', 'foo' => 'bar'], false],
+        ];
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.expected b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.expected
new file mode 100644
index 0000000..d20fb88
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.expected
@@ -0,0 +1,16 @@
+open
+validateId
+read
+doRead: 
+read
+
+write
+doWrite: foo|s:3:"bar";
+close
+Array
+(
+    [0] => Content-Type: text/plain; charset=utf-8
+    [1] => Cache-Control: max-age=0, private, must-revalidate
+    [2] => Set-Cookie: sid=random_session_id; path=/; secure; HttpOnly; SameSite=lax
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.php
new file mode 100644
index 0000000..fc2c418
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.php
@@ -0,0 +1,13 @@
+<?php
+
+require __DIR__.'/common.inc';
+
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+
+$storage = new NativeSessionStorage(['cookie_samesite' => 'lax']);
+$storage->setSaveHandler(new TestSessionHandler());
+$storage->start();
+
+$_SESSION = ['foo' => 'bar'];
+
+ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); });
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.expected b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.expected
new file mode 100644
index 0000000..8b5fc08
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.expected
@@ -0,0 +1,23 @@
+open
+validateId
+read
+doRead: 
+read
+destroy
+close
+open
+validateId
+read
+doRead: 
+read
+
+write
+doWrite: foo|s:3:"bar";
+close
+Array
+(
+    [0] => Content-Type: text/plain; charset=utf-8
+    [1] => Cache-Control: max-age=0, private, must-revalidate
+    [2] => Set-Cookie: sid=random_session_id; path=/; secure; HttpOnly; SameSite=lax
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.php
new file mode 100644
index 0000000..a28b6fe
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.php
@@ -0,0 +1,15 @@
+<?php
+
+require __DIR__.'/common.inc';
+
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+
+$storage = new NativeSessionStorage(['cookie_samesite' => 'lax']);
+$storage->setSaveHandler(new TestSessionHandler());
+$storage->start();
+
+$_SESSION = ['foo' => 'bar'];
+
+$storage->regenerate(true);
+
+ob_start(function ($buffer) { return preg_replace('~_sf2_meta.*$~m', '', str_replace(session_id(), 'random_session_id', $buffer)); });
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MigratingSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MigratingSessionHandlerTest.php
new file mode 100644
index 0000000..01615e6
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MigratingSessionHandlerTest.php
@@ -0,0 +1,186 @@
+<?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\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\MigratingSessionHandler;
+
+class MigratingSessionHandlerTest extends TestCase
+{
+    private $dualHandler;
+    private $currentHandler;
+    private $writeOnlyHandler;
+
+    protected function setUp(): void
+    {
+        $this->currentHandler = $this->createMock(\SessionHandlerInterface::class);
+        $this->writeOnlyHandler = $this->createMock(\SessionHandlerInterface::class);
+
+        $this->dualHandler = new MigratingSessionHandler($this->currentHandler, $this->writeOnlyHandler);
+    }
+
+    public function testInstanceOf()
+    {
+        $this->assertInstanceOf(\SessionHandlerInterface::class, $this->dualHandler);
+        $this->assertInstanceOf(\SessionUpdateTimestampHandlerInterface::class, $this->dualHandler);
+    }
+
+    public function testClose()
+    {
+        $this->currentHandler->expects($this->once())
+            ->method('close')
+            ->willReturn(true);
+
+        $this->writeOnlyHandler->expects($this->once())
+            ->method('close')
+            ->willReturn(false);
+
+        $result = $this->dualHandler->close();
+
+        $this->assertTrue($result);
+    }
+
+    public function testDestroy()
+    {
+        $sessionId = 'xyz';
+
+        $this->currentHandler->expects($this->once())
+            ->method('destroy')
+            ->with($sessionId)
+            ->willReturn(true);
+
+        $this->writeOnlyHandler->expects($this->once())
+            ->method('destroy')
+            ->with($sessionId)
+            ->willReturn(false);
+
+        $result = $this->dualHandler->destroy($sessionId);
+
+        $this->assertTrue($result);
+    }
+
+    public function testGc()
+    {
+        $maxlifetime = 357;
+
+        $this->currentHandler->expects($this->once())
+            ->method('gc')
+            ->with($maxlifetime)
+            ->willReturn(true);
+
+        $this->writeOnlyHandler->expects($this->once())
+            ->method('gc')
+            ->with($maxlifetime)
+            ->willReturn(false);
+
+        $result = $this->dualHandler->gc($maxlifetime);
+        $this->assertTrue($result);
+    }
+
+    public function testOpen()
+    {
+        $savePath = '/path/to/save/location';
+        $sessionName = 'xyz';
+
+        $this->currentHandler->expects($this->once())
+            ->method('open')
+            ->with($savePath, $sessionName)
+            ->willReturn(true);
+
+        $this->writeOnlyHandler->expects($this->once())
+            ->method('open')
+            ->with($savePath, $sessionName)
+            ->willReturn(false);
+
+        $result = $this->dualHandler->open($savePath, $sessionName);
+
+        $this->assertTrue($result);
+    }
+
+    public function testRead()
+    {
+        $sessionId = 'xyz';
+        $readValue = 'something';
+
+        $this->currentHandler->expects($this->once())
+            ->method('read')
+            ->with($sessionId)
+            ->willReturn($readValue);
+
+        $this->writeOnlyHandler->expects($this->never())
+            ->method('read')
+            ->with($this->any());
+
+        $result = $this->dualHandler->read($sessionId);
+
+        $this->assertSame($readValue, $result);
+    }
+
+    public function testWrite()
+    {
+        $sessionId = 'xyz';
+        $data = 'my-serialized-data';
+
+        $this->currentHandler->expects($this->once())
+            ->method('write')
+            ->with($sessionId, $data)
+            ->willReturn(true);
+
+        $this->writeOnlyHandler->expects($this->once())
+            ->method('write')
+            ->with($sessionId, $data)
+            ->willReturn(false);
+
+        $result = $this->dualHandler->write($sessionId, $data);
+
+        $this->assertTrue($result);
+    }
+
+    public function testValidateId()
+    {
+        $sessionId = 'xyz';
+        $readValue = 'something';
+
+        $this->currentHandler->expects($this->once())
+            ->method('read')
+            ->with($sessionId)
+            ->willReturn($readValue);
+
+        $this->writeOnlyHandler->expects($this->never())
+            ->method('read')
+            ->with($this->any());
+
+        $result = $this->dualHandler->validateId($sessionId);
+
+        $this->assertTrue($result);
+    }
+
+    public function testUpdateTimestamp()
+    {
+        $sessionId = 'xyz';
+        $data = 'my-serialized-data';
+
+        $this->currentHandler->expects($this->once())
+            ->method('write')
+            ->with($sessionId, $data)
+            ->willReturn(true);
+
+        $this->writeOnlyHandler->expects($this->once())
+            ->method('write')
+            ->with($sessionId, $data)
+            ->willReturn(false);
+
+        $result = $this->dualHandler->updateTimestamp($sessionId, $data);
+
+        $this->assertTrue($result);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php
new file mode 100644
index 0000000..622b42d
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php
@@ -0,0 +1,22 @@
+<?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\HttpFoundation\Tests\Session\Storage\Handler;
+
+use Predis\Client;
+
+class PredisClusterSessionHandlerTest extends AbstractRedisSessionHandlerTestCase
+{
+    protected function createRedisClient(string $host): Client
+    {
+        return new Client([['host' => $host]]);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PredisSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PredisSessionHandlerTest.php
new file mode 100644
index 0000000..5ecab11
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PredisSessionHandlerTest.php
@@ -0,0 +1,22 @@
+<?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\HttpFoundation\Tests\Session\Storage\Handler;
+
+use Predis\Client;
+
+class PredisSessionHandlerTest extends AbstractRedisSessionHandlerTestCase
+{
+    protected function createRedisClient(string $host): Client
+    {
+        return new Client(['host' => $host]);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisArraySessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisArraySessionHandlerTest.php
new file mode 100644
index 0000000..b03a372
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisArraySessionHandlerTest.php
@@ -0,0 +1,20 @@
+<?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\HttpFoundation\Tests\Session\Storage\Handler;
+
+class RedisArraySessionHandlerTest extends AbstractRedisSessionHandlerTestCase
+{
+    protected function createRedisClient(string $host): \RedisArray
+    {
+        return new \RedisArray([$host]);
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php
new file mode 100644
index 0000000..c1ba70d
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php
@@ -0,0 +1,31 @@
+<?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\HttpFoundation\Tests\Session\Storage\Handler;
+
+class RedisClusterSessionHandlerTest extends AbstractRedisSessionHandlerTestCase
+{
+    public static function setUpBeforeClass(): void
+    {
+        if (!class_exists('RedisCluster')) {
+            self::markTestSkipped('The RedisCluster class is required.');
+        }
+
+        if (!$hosts = getenv('REDIS_CLUSTER_HOSTS')) {
+            self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.');
+        }
+    }
+
+    protected function createRedisClient(string $host): \RedisCluster
+    {
+        return new \RedisCluster(null, explode(' ', getenv('REDIS_CLUSTER_HOSTS')));
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php
new file mode 100644
index 0000000..afdb6c5
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php
@@ -0,0 +1,23 @@
+<?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\HttpFoundation\Tests\Session\Storage\Handler;
+
+class RedisSessionHandlerTest extends AbstractRedisSessionHandlerTestCase
+{
+    protected function createRedisClient(string $host): \Redis
+    {
+        $client = new \Redis();
+        $client->connect($host);
+
+        return $client;
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Test/Constraint/RequestAttributeValueSameTest.php b/vendor/symfony/http-foundation/Tests/Test/Constraint/RequestAttributeValueSameTest.php
new file mode 100644
index 0000000..eca8aed
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Test/Constraint/RequestAttributeValueSameTest.php
@@ -0,0 +1,41 @@
+<?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\HttpFoundation\Tests\Test\Constraint;
+
+use PHPUnit\Framework\ExpectationFailedException;
+use PHPUnit\Framework\TestCase;
+use PHPUnit\Framework\TestFailure;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Test\Constraint\RequestAttributeValueSame;
+
+class RequestAttributeValueSameTest extends TestCase
+{
+    public function testConstraint(): void
+    {
+        $request = new Request();
+        $request->attributes->set('foo', 'bar');
+        $constraint = new RequestAttributeValueSame('foo', 'bar');
+        $this->assertTrue($constraint->evaluate($request, '', true));
+        $constraint = new RequestAttributeValueSame('bar', 'foo');
+        $this->assertFalse($constraint->evaluate($request, '', true));
+
+        try {
+            $constraint->evaluate($request);
+        } catch (ExpectationFailedException $e) {
+            $this->assertEquals("Failed asserting that the Request has attribute \"bar\" with value \"foo\".\n", TestFailure::exceptionToString($e));
+
+            return;
+        }
+
+        $this->fail();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseCookieValueSameTest.php b/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseCookieValueSameTest.php
new file mode 100644
index 0000000..778879c
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseCookieValueSameTest.php
@@ -0,0 +1,44 @@
+<?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\HttpFoundation\Tests\Test\Constraint;
+
+use PHPUnit\Framework\ExpectationFailedException;
+use PHPUnit\Framework\TestCase;
+use PHPUnit\Framework\TestFailure;
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Test\Constraint\ResponseCookieValueSame;
+
+class ResponseCookieValueSameTest extends TestCase
+{
+    public function testConstraint(): void
+    {
+        $response = new Response();
+        $response->headers->setCookie(Cookie::create('foo', 'bar', 0, '/path'));
+        $constraint = new ResponseCookieValueSame('foo', 'bar', '/path');
+        $this->assertTrue($constraint->evaluate($response, '', true));
+        $constraint = new ResponseCookieValueSame('foo', 'bar', '/path');
+        $this->assertTrue($constraint->evaluate($response, '', true));
+        $constraint = new ResponseCookieValueSame('foo', 'babar', '/path');
+        $this->assertFalse($constraint->evaluate($response, '', true));
+
+        try {
+            $constraint->evaluate($response);
+        } catch (ExpectationFailedException $e) {
+            $this->assertEquals("Failed asserting that the Response has cookie \"foo\" with path \"/path\" with value \"babar\".\n", TestFailure::exceptionToString($e));
+
+            return;
+        }
+
+        $this->fail();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseHasCookieTest.php b/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseHasCookieTest.php
new file mode 100644
index 0000000..05ca95f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseHasCookieTest.php
@@ -0,0 +1,42 @@
+<?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\HttpFoundation\Tests\Test\Constraint;
+
+use PHPUnit\Framework\ExpectationFailedException;
+use PHPUnit\Framework\TestCase;
+use PHPUnit\Framework\TestFailure;
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Test\Constraint\ResponseHasCookie;
+
+class ResponseHasCookieTest extends TestCase
+{
+    public function testConstraint(): void
+    {
+        $response = new Response();
+        $response->headers->setCookie(Cookie::create('foo', 'bar'));
+        $constraint = new ResponseHasCookie('foo');
+        $this->assertTrue($constraint->evaluate($response, '', true));
+        $constraint = new ResponseHasCookie('bar');
+        $this->assertFalse($constraint->evaluate($response, '', true));
+
+        try {
+            $constraint->evaluate($response);
+        } catch (ExpectationFailedException $e) {
+            $this->assertEquals("Failed asserting that the Response has cookie \"bar\".\n", TestFailure::exceptionToString($e));
+
+            return;
+        }
+
+        $this->fail();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseHasHeaderTest.php b/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseHasHeaderTest.php
new file mode 100644
index 0000000..7b811a6
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseHasHeaderTest.php
@@ -0,0 +1,39 @@
+<?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\HttpFoundation\Tests\Test\Constraint;
+
+use PHPUnit\Framework\ExpectationFailedException;
+use PHPUnit\Framework\TestCase;
+use PHPUnit\Framework\TestFailure;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Test\Constraint\ResponseHasHeader;
+
+class ResponseHasHeaderTest extends TestCase
+{
+    public function testConstraint(): void
+    {
+        $constraint = new ResponseHasHeader('Date');
+        $this->assertTrue($constraint->evaluate(new Response(), '', true));
+        $constraint = new ResponseHasHeader('X-Date');
+        $this->assertFalse($constraint->evaluate(new Response(), '', true));
+
+        try {
+            $constraint->evaluate(new Response());
+        } catch (ExpectationFailedException $e) {
+            $this->assertEquals("Failed asserting that the Response has header \"X-Date\".\n", TestFailure::exceptionToString($e));
+
+            return;
+        }
+
+        $this->fail();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseHeaderSameTest.php b/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseHeaderSameTest.php
new file mode 100644
index 0000000..d527f35
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseHeaderSameTest.php
@@ -0,0 +1,39 @@
+<?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\HttpFoundation\Tests\Test\Constraint;
+
+use PHPUnit\Framework\ExpectationFailedException;
+use PHPUnit\Framework\TestCase;
+use PHPUnit\Framework\TestFailure;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Test\Constraint\ResponseHeaderSame;
+
+class ResponseHeaderSameTest extends TestCase
+{
+    public function testConstraint(): void
+    {
+        $constraint = new ResponseHeaderSame('Cache-Control', 'no-cache, private');
+        $this->assertTrue($constraint->evaluate(new Response(), '', true));
+        $constraint = new ResponseHeaderSame('Cache-Control', 'public');
+        $this->assertFalse($constraint->evaluate(new Response(), '', true));
+
+        try {
+            $constraint->evaluate(new Response());
+        } catch (ExpectationFailedException $e) {
+            $this->assertEquals("Failed asserting that the Response has header \"Cache-Control\" with value \"public\".\n", TestFailure::exceptionToString($e));
+
+            return;
+        }
+
+        $this->fail();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseIsRedirectedTest.php b/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseIsRedirectedTest.php
new file mode 100644
index 0000000..a8314c2
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseIsRedirectedTest.php
@@ -0,0 +1,39 @@
+<?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\HttpFoundation\Tests\Test\Constraint;
+
+use PHPUnit\Framework\ExpectationFailedException;
+use PHPUnit\Framework\TestCase;
+use PHPUnit\Framework\TestFailure;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Test\Constraint\ResponseIsRedirected;
+
+class ResponseIsRedirectedTest extends TestCase
+{
+    public function testConstraint(): void
+    {
+        $constraint = new ResponseIsRedirected();
+
+        $this->assertTrue($constraint->evaluate(new Response('', 301), '', true));
+        $this->assertFalse($constraint->evaluate(new Response(), '', true));
+
+        try {
+            $constraint->evaluate(new Response());
+        } catch (ExpectationFailedException $e) {
+            $this->assertStringContainsString("Failed asserting that the Response is redirected.\nHTTP/1.0 200 OK", TestFailure::exceptionToString($e));
+
+            return;
+        }
+
+        $this->fail();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseIsSuccessfulTest.php b/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseIsSuccessfulTest.php
new file mode 100644
index 0000000..b59daf8
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseIsSuccessfulTest.php
@@ -0,0 +1,39 @@
+<?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\HttpFoundation\Tests\Test\Constraint;
+
+use PHPUnit\Framework\ExpectationFailedException;
+use PHPUnit\Framework\TestCase;
+use PHPUnit\Framework\TestFailure;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Test\Constraint\ResponseIsSuccessful;
+
+class ResponseIsSuccessfulTest extends TestCase
+{
+    public function testConstraint(): void
+    {
+        $constraint = new ResponseIsSuccessful();
+
+        $this->assertTrue($constraint->evaluate(new Response(), '', true));
+        $this->assertFalse($constraint->evaluate(new Response('', 404), '', true));
+
+        try {
+            $constraint->evaluate(new Response('', 404));
+        } catch (ExpectationFailedException $e) {
+            $this->assertStringContainsString("Failed asserting that the Response is successful.\nHTTP/1.0 404 Not Found", TestFailure::exceptionToString($e));
+
+            return;
+        }
+
+        $this->fail();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseStatusCodeSameTest.php b/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseStatusCodeSameTest.php
new file mode 100644
index 0000000..53200fd
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Test/Constraint/ResponseStatusCodeSameTest.php
@@ -0,0 +1,41 @@
+<?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\HttpFoundation\Tests\Test\Constraint;
+
+use PHPUnit\Framework\ExpectationFailedException;
+use PHPUnit\Framework\TestCase;
+use PHPUnit\Framework\TestFailure;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Test\Constraint\ResponseStatusCodeSame;
+
+class ResponseStatusCodeSameTest extends TestCase
+{
+    public function testConstraint(): void
+    {
+        $constraint = new ResponseStatusCodeSame(200);
+        $this->assertTrue($constraint->evaluate(new Response(), '', true));
+        $this->assertFalse($constraint->evaluate(new Response('', 404), '', true));
+        $constraint = new ResponseStatusCodeSame(404);
+        $this->assertTrue($constraint->evaluate(new Response('', 404), '', true));
+
+        $constraint = new ResponseStatusCodeSame(200);
+        try {
+            $constraint->evaluate(new Response('', 404));
+        } catch (ExpectationFailedException $e) {
+            $this->assertStringContainsString("Failed asserting that the Response status code is 200.\nHTTP/1.0 404 Not Found", TestFailure::exceptionToString($e));
+
+            return;
+        }
+
+        $this->fail();
+    }
+}
diff --git a/vendor/symfony/http-foundation/Tests/UrlHelperTest.php b/vendor/symfony/http-foundation/Tests/UrlHelperTest.php
new file mode 100644
index 0000000..9a750bd
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/UrlHelperTest.php
@@ -0,0 +1,143 @@
+<?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\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpFoundation\UrlHelper;
+use Symfony\Component\Routing\RequestContext;
+
+class UrlHelperTest extends TestCase
+{
+    /**
+     * @dataProvider getGenerateAbsoluteUrlData()
+     */
+    public function testGenerateAbsoluteUrl($expected, $path, $pathinfo)
+    {
+        $stack = new RequestStack();
+        $stack->push(Request::create($pathinfo));
+        $helper = new UrlHelper($stack);
+
+        $this->assertEquals($expected, $helper->getAbsoluteUrl($path));
+    }
+
+    public function getGenerateAbsoluteUrlData()
+    {
+        return [
+            ['http://localhost/foo.png', '/foo.png', '/foo/bar.html'],
+            ['http://localhost/foo/foo.png', 'foo.png', '/foo/bar.html'],
+            ['http://localhost/foo/foo.png', 'foo.png', '/foo/bar'],
+            ['http://localhost/foo/bar/foo.png', 'foo.png', '/foo/bar/'],
+
+            ['http://example.com/baz', 'http://example.com/baz', '/'],
+            ['https://example.com/baz', 'https://example.com/baz', '/'],
+            ['//example.com/baz', '//example.com/baz', '/'],
+
+            ['http://localhost/foo/bar?baz', '?baz', '/foo/bar'],
+            ['http://localhost/foo/bar?baz=1', '?baz=1', '/foo/bar?foo=1'],
+            ['http://localhost/foo/baz?baz=1', 'baz?baz=1', '/foo/bar?foo=1'],
+
+            ['http://localhost/foo/bar#baz', '#baz', '/foo/bar'],
+            ['http://localhost/foo/bar?0#baz', '#baz', '/foo/bar?0'],
+            ['http://localhost/foo/bar?baz=1#baz', '?baz=1#baz', '/foo/bar?foo=1'],
+            ['http://localhost/foo/baz?baz=1#baz', 'baz?baz=1#baz', '/foo/bar?foo=1'],
+        ];
+    }
+
+    /**
+     * @dataProvider getGenerateAbsoluteUrlRequestContextData
+     */
+    public function testGenerateAbsoluteUrlWithRequestContext($path, $baseUrl, $host, $scheme, $httpPort, $httpsPort, $expected)
+    {
+        if (!class_exists('Symfony\Component\Routing\RequestContext')) {
+            $this->markTestSkipped('The Routing component is needed to run tests that depend on its request context.');
+        }
+
+        $requestContext = new RequestContext($baseUrl, 'GET', $host, $scheme, $httpPort, $httpsPort, $path);
+        $helper = new UrlHelper(new RequestStack(), $requestContext);
+
+        $this->assertEquals($expected, $helper->getAbsoluteUrl($path));
+    }
+
+    /**
+     * @dataProvider getGenerateAbsoluteUrlRequestContextData
+     */
+    public function testGenerateAbsoluteUrlWithoutRequestAndRequestContext($path)
+    {
+        if (!class_exists('Symfony\Component\Routing\RequestContext')) {
+            $this->markTestSkipped('The Routing component is needed to run tests that depend on its request context.');
+        }
+
+        $helper = new UrlHelper(new RequestStack());
+
+        $this->assertEquals($path, $helper->getAbsoluteUrl($path));
+    }
+
+    public function getGenerateAbsoluteUrlRequestContextData()
+    {
+        return [
+            ['/foo.png', '/foo', 'localhost', 'http', 80, 443, 'http://localhost/foo.png'],
+            ['foo.png', '/foo', 'localhost', 'http', 80, 443, 'http://localhost/foo/foo.png'],
+            ['foo.png', '/foo/bar/', 'localhost', 'http', 80, 443, 'http://localhost/foo/bar/foo.png'],
+            ['/foo.png', '/foo', 'localhost', 'https', 80, 443, 'https://localhost/foo.png'],
+            ['foo.png', '/foo', 'localhost', 'https', 80, 443, 'https://localhost/foo/foo.png'],
+            ['foo.png', '/foo/bar/', 'localhost', 'https', 80, 443, 'https://localhost/foo/bar/foo.png'],
+            ['/foo.png', '/foo', 'localhost', 'http', 443, 80, 'http://localhost:443/foo.png'],
+            ['/foo.png', '/foo', 'localhost', 'https', 443, 80, 'https://localhost:80/foo.png'],
+        ];
+    }
+
+    public function testGenerateAbsoluteUrlWithScriptFileName()
+    {
+        $request = Request::create('http://localhost/app/web/app_dev.php');
+        $request->server->set('SCRIPT_FILENAME', '/var/www/app/web/app_dev.php');
+
+        $stack = new RequestStack();
+        $stack->push($request);
+        $helper = new UrlHelper($stack);
+
+        $this->assertEquals(
+            'http://localhost/app/web/bundles/framework/css/structure.css',
+            $helper->getAbsoluteUrl('/app/web/bundles/framework/css/structure.css')
+        );
+    }
+
+    /**
+     * @dataProvider getGenerateRelativePathData()
+     */
+    public function testGenerateRelativePath($expected, $path, $pathinfo)
+    {
+        if (!method_exists('Symfony\Component\HttpFoundation\Request', 'getRelativeUriForPath')) {
+            $this->markTestSkipped('Your version of Symfony HttpFoundation is too old.');
+        }
+
+        $stack = new RequestStack();
+        $stack->push(Request::create($pathinfo));
+        $urlHelper = new UrlHelper($stack);
+
+        $this->assertEquals($expected, $urlHelper->getRelativePath($path));
+    }
+
+    public function getGenerateRelativePathData()
+    {
+        return [
+            ['../foo.png', '/foo.png', '/foo/bar.html'],
+            ['../baz/foo.png', '/baz/foo.png', '/foo/bar.html'],
+            ['baz/foo.png', 'baz/foo.png', '/foo/bar.html'],
+
+            ['http://example.com/baz', 'http://example.com/baz', '/'],
+            ['https://example.com/baz', 'https://example.com/baz', '/'],
+            ['//example.com/baz', '//example.com/baz', '/'],
+        ];
+    }
+}
diff --git a/vendor/symfony/http-foundation/UrlHelper.php b/vendor/symfony/http-foundation/UrlHelper.php
new file mode 100644
index 0000000..f114c0a
--- /dev/null
+++ b/vendor/symfony/http-foundation/UrlHelper.php
@@ -0,0 +1,102 @@
+<?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\HttpFoundation;
+
+use Symfony\Component\Routing\RequestContext;
+
+/**
+ * A helper service for manipulating URLs within and outside the request scope.
+ *
+ * @author Valentin Udaltsov <udaltsov.valentin@gmail.com>
+ */
+final class UrlHelper
+{
+    private $requestStack;
+    private $requestContext;
+
+    public function __construct(RequestStack $requestStack, RequestContext $requestContext = null)
+    {
+        $this->requestStack = $requestStack;
+        $this->requestContext = $requestContext;
+    }
+
+    public function getAbsoluteUrl(string $path): string
+    {
+        if (false !== strpos($path, '://') || '//' === substr($path, 0, 2)) {
+            return $path;
+        }
+
+        if (null === $request = $this->requestStack->getMasterRequest()) {
+            return $this->getAbsoluteUrlFromContext($path);
+        }
+
+        if ('#' === $path[0]) {
+            $path = $request->getRequestUri().$path;
+        } elseif ('?' === $path[0]) {
+            $path = $request->getPathInfo().$path;
+        }
+
+        if (!$path || '/' !== $path[0]) {
+            $prefix = $request->getPathInfo();
+            $last = \strlen($prefix) - 1;
+            if ($last !== $pos = strrpos($prefix, '/')) {
+                $prefix = substr($prefix, 0, $pos).'/';
+            }
+
+            return $request->getUriForPath($prefix.$path);
+        }
+
+        return $request->getSchemeAndHttpHost().$path;
+    }
+
+    public function getRelativePath(string $path): string
+    {
+        if (false !== strpos($path, '://') || '//' === substr($path, 0, 2)) {
+            return $path;
+        }
+
+        if (null === $request = $this->requestStack->getMasterRequest()) {
+            return $path;
+        }
+
+        return $request->getRelativeUriForPath($path);
+    }
+
+    private function getAbsoluteUrlFromContext(string $path): string
+    {
+        if (null === $this->requestContext || '' === $host = $this->requestContext->getHost()) {
+            return $path;
+        }
+
+        $scheme = $this->requestContext->getScheme();
+        $port = '';
+
+        if ('http' === $scheme && 80 !== $this->requestContext->getHttpPort()) {
+            $port = ':'.$this->requestContext->getHttpPort();
+        } elseif ('https' === $scheme && 443 !== $this->requestContext->getHttpsPort()) {
+            $port = ':'.$this->requestContext->getHttpsPort();
+        }
+
+        if ('#' === $path[0]) {
+            $queryString = $this->requestContext->getQueryString();
+            $path = $this->requestContext->getPathInfo().($queryString ? '?'.$queryString : '').$path;
+        } elseif ('?' === $path[0]) {
+            $path = $this->requestContext->getPathInfo().$path;
+        }
+
+        if ('/' !== $path[0]) {
+            $path = rtrim($this->requestContext->getBaseUrl(), '/').'/'.$path;
+        }
+
+        return $scheme.'://'.$host.$port.$path;
+    }
+}
diff --git a/vendor/symfony/mime/.gitignore b/vendor/symfony/mime/.gitignore
new file mode 100644
index 0000000..5414c2c
--- /dev/null
+++ b/vendor/symfony/mime/.gitignore
@@ -0,0 +1,3 @@
+composer.lock
+phpunit.xml
+vendor/
diff --git a/vendor/symfony/mime/Address.php b/vendor/symfony/mime/Address.php
new file mode 100644
index 0000000..be1ca76
--- /dev/null
+++ b/vendor/symfony/mime/Address.php
@@ -0,0 +1,98 @@
+<?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\Mime;
+
+use Egulias\EmailValidator\EmailValidator;
+use Egulias\EmailValidator\Validation\RFCValidation;
+use Symfony\Component\Mime\Encoder\IdnAddressEncoder;
+use Symfony\Component\Mime\Exception\InvalidArgumentException;
+use Symfony\Component\Mime\Exception\LogicException;
+use Symfony\Component\Mime\Exception\RfcComplianceException;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+class Address
+{
+    private static $validator;
+    private static $encoder;
+
+    private $address;
+
+    public function __construct(string $address)
+    {
+        if (!class_exists(EmailValidator::class)) {
+            throw new LogicException(sprintf('The "%s" class cannot be used as it needs "%s"; try running "composer require egulias/email-validator".', __CLASS__, EmailValidator::class));
+        }
+
+        if (null === self::$validator) {
+            self::$validator = new EmailValidator();
+        }
+
+        $this->address = trim($address);
+
+        if (!self::$validator->isValid($this->address, new RFCValidation())) {
+            throw new RfcComplianceException(sprintf('Email "%s" does not comply with addr-spec of RFC 2822.', $address));
+        }
+    }
+
+    public function getAddress(): string
+    {
+        return $this->address;
+    }
+
+    public function getEncodedAddress(): string
+    {
+        if (null === self::$encoder) {
+            self::$encoder = new IdnAddressEncoder();
+        }
+
+        return self::$encoder->encodeString($this->address);
+    }
+
+    public function toString(): string
+    {
+        return $this->getEncodedAddress();
+    }
+
+    /**
+     * @param Address|string $address
+     */
+    public static function create($address): self
+    {
+        if ($address instanceof self) {
+            return $address;
+        }
+        if (\is_string($address)) {
+            return new self($address);
+        }
+
+        throw new InvalidArgumentException(sprintf('An address can be an instance of Address or a string ("%s") given).', \is_object($address) ? \get_class($address) : \gettype($address)));
+    }
+
+    /**
+     * @param (Address|string)[] $addresses
+     *
+     * @return Address[]
+     */
+    public static function createArray(array $addresses): array
+    {
+        $addrs = [];
+        foreach ($addresses as $address) {
+            $addrs[] = self::create($address);
+        }
+
+        return $addrs;
+    }
+}
diff --git a/vendor/symfony/mime/BodyRendererInterface.php b/vendor/symfony/mime/BodyRendererInterface.php
new file mode 100644
index 0000000..8fcc401
--- /dev/null
+++ b/vendor/symfony/mime/BodyRendererInterface.php
@@ -0,0 +1,22 @@
+<?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\Mime;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+interface BodyRendererInterface
+{
+    public function render(Message $message): void;
+}
diff --git a/vendor/symfony/mime/CHANGELOG.md b/vendor/symfony/mime/CHANGELOG.md
new file mode 100644
index 0000000..796cfdd
--- /dev/null
+++ b/vendor/symfony/mime/CHANGELOG.md
@@ -0,0 +1,12 @@
+CHANGELOG
+=========
+
+4.3.3
+-----
+
+ * [BC BREAK] Renamed method `Headers::getAll()` to `Headers::all()`.
+
+4.3.0
+-----
+
+ * Introduced the component as experimental
diff --git a/vendor/symfony/mime/CharacterStream.php b/vendor/symfony/mime/CharacterStream.php
new file mode 100644
index 0000000..045b477
--- /dev/null
+++ b/vendor/symfony/mime/CharacterStream.php
@@ -0,0 +1,223 @@
+<?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\Mime;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Xavier De Cock <xdecock@gmail.com>
+ *
+ * @internal
+ *
+ * @experimental in 4.3
+ */
+final class CharacterStream
+{
+    /** Pre-computed for optimization */
+    private const UTF8_LENGTH_MAP = [
+        "\x00" => 1, "\x01" => 1, "\x02" => 1, "\x03" => 1, "\x04" => 1, "\x05" => 1, "\x06" => 1, "\x07" => 1,
+        "\x08" => 1, "\x09" => 1, "\x0a" => 1, "\x0b" => 1, "\x0c" => 1, "\x0d" => 1, "\x0e" => 1, "\x0f" => 1,
+        "\x10" => 1, "\x11" => 1, "\x12" => 1, "\x13" => 1, "\x14" => 1, "\x15" => 1, "\x16" => 1, "\x17" => 1,
+        "\x18" => 1, "\x19" => 1, "\x1a" => 1, "\x1b" => 1, "\x1c" => 1, "\x1d" => 1, "\x1e" => 1, "\x1f" => 1,
+        "\x20" => 1, "\x21" => 1, "\x22" => 1, "\x23" => 1, "\x24" => 1, "\x25" => 1, "\x26" => 1, "\x27" => 1,
+        "\x28" => 1, "\x29" => 1, "\x2a" => 1, "\x2b" => 1, "\x2c" => 1, "\x2d" => 1, "\x2e" => 1, "\x2f" => 1,
+        "\x30" => 1, "\x31" => 1, "\x32" => 1, "\x33" => 1, "\x34" => 1, "\x35" => 1, "\x36" => 1, "\x37" => 1,
+        "\x38" => 1, "\x39" => 1, "\x3a" => 1, "\x3b" => 1, "\x3c" => 1, "\x3d" => 1, "\x3e" => 1, "\x3f" => 1,
+        "\x40" => 1, "\x41" => 1, "\x42" => 1, "\x43" => 1, "\x44" => 1, "\x45" => 1, "\x46" => 1, "\x47" => 1,
+        "\x48" => 1, "\x49" => 1, "\x4a" => 1, "\x4b" => 1, "\x4c" => 1, "\x4d" => 1, "\x4e" => 1, "\x4f" => 1,
+        "\x50" => 1, "\x51" => 1, "\x52" => 1, "\x53" => 1, "\x54" => 1, "\x55" => 1, "\x56" => 1, "\x57" => 1,
+        "\x58" => 1, "\x59" => 1, "\x5a" => 1, "\x5b" => 1, "\x5c" => 1, "\x5d" => 1, "\x5e" => 1, "\x5f" => 1,
+        "\x60" => 1, "\x61" => 1, "\x62" => 1, "\x63" => 1, "\x64" => 1, "\x65" => 1, "\x66" => 1, "\x67" => 1,
+        "\x68" => 1, "\x69" => 1, "\x6a" => 1, "\x6b" => 1, "\x6c" => 1, "\x6d" => 1, "\x6e" => 1, "\x6f" => 1,
+        "\x70" => 1, "\x71" => 1, "\x72" => 1, "\x73" => 1, "\x74" => 1, "\x75" => 1, "\x76" => 1, "\x77" => 1,
+        "\x78" => 1, "\x79" => 1, "\x7a" => 1, "\x7b" => 1, "\x7c" => 1, "\x7d" => 1, "\x7e" => 1, "\x7f" => 1,
+        "\x80" => 0, "\x81" => 0, "\x82" => 0, "\x83" => 0, "\x84" => 0, "\x85" => 0, "\x86" => 0, "\x87" => 0,
+        "\x88" => 0, "\x89" => 0, "\x8a" => 0, "\x8b" => 0, "\x8c" => 0, "\x8d" => 0, "\x8e" => 0, "\x8f" => 0,
+        "\x90" => 0, "\x91" => 0, "\x92" => 0, "\x93" => 0, "\x94" => 0, "\x95" => 0, "\x96" => 0, "\x97" => 0,
+        "\x98" => 0, "\x99" => 0, "\x9a" => 0, "\x9b" => 0, "\x9c" => 0, "\x9d" => 0, "\x9e" => 0, "\x9f" => 0,
+        "\xa0" => 0, "\xa1" => 0, "\xa2" => 0, "\xa3" => 0, "\xa4" => 0, "\xa5" => 0, "\xa6" => 0, "\xa7" => 0,
+        "\xa8" => 0, "\xa9" => 0, "\xaa" => 0, "\xab" => 0, "\xac" => 0, "\xad" => 0, "\xae" => 0, "\xaf" => 0,
+        "\xb0" => 0, "\xb1" => 0, "\xb2" => 0, "\xb3" => 0, "\xb4" => 0, "\xb5" => 0, "\xb6" => 0, "\xb7" => 0,
+        "\xb8" => 0, "\xb9" => 0, "\xba" => 0, "\xbb" => 0, "\xbc" => 0, "\xbd" => 0, "\xbe" => 0, "\xbf" => 0,
+        "\xc0" => 2, "\xc1" => 2, "\xc2" => 2, "\xc3" => 2, "\xc4" => 2, "\xc5" => 2, "\xc6" => 2, "\xc7" => 2,
+        "\xc8" => 2, "\xc9" => 2, "\xca" => 2, "\xcb" => 2, "\xcc" => 2, "\xcd" => 2, "\xce" => 2, "\xcf" => 2,
+        "\xd0" => 2, "\xd1" => 2, "\xd2" => 2, "\xd3" => 2, "\xd4" => 2, "\xd5" => 2, "\xd6" => 2, "\xd7" => 2,
+        "\xd8" => 2, "\xd9" => 2, "\xda" => 2, "\xdb" => 2, "\xdc" => 2, "\xdd" => 2, "\xde" => 2, "\xdf" => 2,
+        "\xe0" => 3, "\xe1" => 3, "\xe2" => 3, "\xe3" => 3, "\xe4" => 3, "\xe5" => 3, "\xe6" => 3, "\xe7" => 3,
+        "\xe8" => 3, "\xe9" => 3, "\xea" => 3, "\xeb" => 3, "\xec" => 3, "\xed" => 3, "\xee" => 3, "\xef" => 3,
+        "\xf0" => 4, "\xf1" => 4, "\xf2" => 4, "\xf3" => 4, "\xf4" => 4, "\xf5" => 4, "\xf6" => 4, "\xf7" => 4,
+        "\xf8" => 5, "\xf9" => 5, "\xfa" => 5, "\xfb" => 5, "\xfc" => 6, "\xfd" => 6, "\xfe" => 0, "\xff" => 0,
+    ];
+
+    private $data = '';
+    private $dataSize = 0;
+    private $map = [];
+    private $charCount = 0;
+    private $currentPos = 0;
+    private $fixedWidth = 0;
+
+    /**
+     * @param resource|string $input
+     */
+    public function __construct($input, ?string $charset = 'utf-8')
+    {
+        $charset = strtolower(trim($charset)) ?: 'utf-8';
+        if ('utf-8' === $charset || 'utf8' === $charset) {
+            $this->fixedWidth = 0;
+            $this->map = ['p' => [], 'i' => []];
+        } else {
+            switch ($charset) {
+                // 16 bits
+                case 'ucs2':
+                case 'ucs-2':
+                case 'utf16':
+                case 'utf-16':
+                    $this->fixedWidth = 2;
+                    break;
+
+                // 32 bits
+                case 'ucs4':
+                case 'ucs-4':
+                case 'utf32':
+                case 'utf-32':
+                    $this->fixedWidth = 4;
+                break;
+
+                // 7-8 bit charsets: (us-)?ascii, (iso|iec)-?8859-?[0-9]+, windows-?125[0-9], cp-?[0-9]+, ansi, macintosh,
+                //                   koi-?7, koi-?8-?.+, mik, (cork|t1), v?iscii
+                // and fallback
+                default:
+                    $this->fixedWidth = 1;
+            }
+        }
+        if (\is_resource($input)) {
+            $blocks = 512;
+            if (stream_get_meta_data($input)['seekable'] ?? false) {
+                rewind($input);
+            }
+            while (false !== $read = fread($input, $blocks)) {
+                $this->write($read);
+            }
+        } else {
+            $this->write($input);
+        }
+    }
+
+    public function read(int $length): ?string
+    {
+        if ($this->currentPos >= $this->charCount) {
+            return null;
+        }
+        $length = ($this->currentPos + $length > $this->charCount) ? $this->charCount - $this->currentPos : $length;
+        if ($this->fixedWidth > 0) {
+            $len = $length * $this->fixedWidth;
+            $ret = substr($this->data, $this->currentPos * $this->fixedWidth, $len);
+            $this->currentPos += $length;
+        } else {
+            $end = $this->currentPos + $length;
+            $end = $end > $this->charCount ? $this->charCount : $end;
+            $ret = '';
+            $start = 0;
+            if ($this->currentPos > 0) {
+                $start = $this->map['p'][$this->currentPos - 1];
+            }
+            $to = $start;
+            for (; $this->currentPos < $end; ++$this->currentPos) {
+                if (isset($this->map['i'][$this->currentPos])) {
+                    $ret .= substr($this->data, $start, $to - $start).'?';
+                    $start = $this->map['p'][$this->currentPos];
+                } else {
+                    $to = $this->map['p'][$this->currentPos];
+                }
+            }
+            $ret .= substr($this->data, $start, $to - $start);
+        }
+
+        return $ret;
+    }
+
+    public function readBytes(int $length): ?array
+    {
+        if (null !== $read = $this->read($length)) {
+            return array_map('ord', str_split($read, 1));
+        }
+
+        return null;
+    }
+
+    public function setPointer(int $charOffset): void
+    {
+        if ($this->charCount < $charOffset) {
+            $charOffset = $this->charCount;
+        }
+        $this->currentPos = $charOffset;
+    }
+
+    public function write(string $chars): void
+    {
+        $ignored = '';
+        $this->data .= $chars;
+        if ($this->fixedWidth > 0) {
+            $strlen = \strlen($chars);
+            $ignoredL = $strlen % $this->fixedWidth;
+            $ignored = $ignoredL ? substr($chars, -$ignoredL) : '';
+            $this->charCount += ($strlen - $ignoredL) / $this->fixedWidth;
+        } else {
+            $this->charCount += $this->getUtf8CharPositions($chars, $this->dataSize, $ignored);
+        }
+        $this->dataSize = \strlen($this->data) - \strlen($ignored);
+    }
+
+    private function getUtf8CharPositions(string $string, int $startOffset, &$ignoredChars): int
+    {
+        $strlen = \strlen($string);
+        $charPos = \count($this->map['p']);
+        $foundChars = 0;
+        $invalid = false;
+        for ($i = 0; $i < $strlen; ++$i) {
+            $char = $string[$i];
+            $size = self::UTF8_LENGTH_MAP[$char];
+            if (0 == $size) {
+                /* char is invalid, we must wait for a resync */
+                $invalid = true;
+                continue;
+            }
+
+            if ($invalid) {
+                /* We mark the chars as invalid and start a new char */
+                $this->map['p'][$charPos + $foundChars] = $startOffset + $i;
+                $this->map['i'][$charPos + $foundChars] = true;
+                ++$foundChars;
+                $invalid = false;
+            }
+            if (($i + $size) > $strlen) {
+                $ignoredChars = substr($string, $i);
+                break;
+            }
+            for ($j = 1; $j < $size; ++$j) {
+                $char = $string[$i + $j];
+                if ($char > "\x7F" && $char < "\xC0") {
+                    // Valid - continue parsing
+                } else {
+                    /* char is invalid, we must wait for a resync */
+                    $invalid = true;
+                    continue 2;
+                }
+            }
+            /* Ok we got a complete char here */
+            $this->map['p'][$charPos + $foundChars] = $startOffset + $i + $size;
+            $i += $j - 1;
+            ++$foundChars;
+        }
+
+        return $foundChars;
+    }
+}
diff --git a/vendor/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php b/vendor/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php
new file mode 100644
index 0000000..78450b9
--- /dev/null
+++ b/vendor/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php
@@ -0,0 +1,48 @@
+<?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\Mime\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Registers custom mime types guessers.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+class AddMimeTypeGuesserPass implements CompilerPassInterface
+{
+    private $mimeTypesService;
+    private $mimeTypeGuesserTag;
+
+    public function __construct(string $mimeTypesService = 'mime_types', string $mimeTypeGuesserTag = 'mime.mime_type_guesser')
+    {
+        $this->mimeTypesService = $mimeTypesService;
+        $this->mimeTypeGuesserTag = $mimeTypeGuesserTag;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function process(ContainerBuilder $container)
+    {
+        if ($container->has($this->mimeTypesService)) {
+            $definition = $container->findDefinition($this->mimeTypesService);
+            foreach ($container->findTaggedServiceIds($this->mimeTypeGuesserTag, true) as $id => $attributes) {
+                $definition->addMethodCall('registerGuesser', [new Reference($id)]);
+            }
+        }
+    }
+}
diff --git a/vendor/symfony/mime/Email.php b/vendor/symfony/mime/Email.php
new file mode 100644
index 0000000..1bcdc8a
--- /dev/null
+++ b/vendor/symfony/mime/Email.php
@@ -0,0 +1,593 @@
+<?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\Mime;
+
+use Symfony\Component\Mime\Exception\LogicException;
+use Symfony\Component\Mime\Part\AbstractPart;
+use Symfony\Component\Mime\Part\DataPart;
+use Symfony\Component\Mime\Part\Multipart\AlternativePart;
+use Symfony\Component\Mime\Part\Multipart\MixedPart;
+use Symfony\Component\Mime\Part\Multipart\RelatedPart;
+use Symfony\Component\Mime\Part\TextPart;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+class Email extends Message
+{
+    const PRIORITY_HIGHEST = 1;
+    const PRIORITY_HIGH = 2;
+    const PRIORITY_NORMAL = 3;
+    const PRIORITY_LOW = 4;
+    const PRIORITY_LOWEST = 5;
+
+    private const PRIORITY_MAP = [
+        self::PRIORITY_HIGHEST => 'Highest',
+        self::PRIORITY_HIGH => 'High',
+        self::PRIORITY_NORMAL => 'Normal',
+        self::PRIORITY_LOW => 'Low',
+        self::PRIORITY_LOWEST => 'Lowest',
+    ];
+
+    private $text;
+    private $textCharset;
+    private $html;
+    private $htmlCharset;
+    private $attachments = [];
+
+    /**
+     * @return $this
+     */
+    public function subject(string $subject)
+    {
+        return $this->setHeaderBody('Text', 'Subject', $subject);
+    }
+
+    public function getSubject(): ?string
+    {
+        return $this->getHeaders()->getHeaderBody('Subject');
+    }
+
+    /**
+     * @return $this
+     */
+    public function date(\DateTimeInterface $dateTime)
+    {
+        return $this->setHeaderBody('Date', 'Date', $dateTime);
+    }
+
+    public function getDate(): ?\DateTimeImmutable
+    {
+        return $this->getHeaders()->getHeaderBody('Date');
+    }
+
+    /**
+     * @param Address|string $address
+     *
+     * @return $this
+     */
+    public function returnPath($address)
+    {
+        return $this->setHeaderBody('Path', 'Return-Path', Address::create($address));
+    }
+
+    public function getReturnPath(): ?Address
+    {
+        return $this->getHeaders()->getHeaderBody('Return-Path');
+    }
+
+    /**
+     * @param Address|string $address
+     *
+     * @return $this
+     */
+    public function sender($address)
+    {
+        return $this->setHeaderBody('Mailbox', 'Sender', Address::create($address));
+    }
+
+    public function getSender(): ?Address
+    {
+        return $this->getHeaders()->getHeaderBody('Sender');
+    }
+
+    /**
+     * @param Address|NamedAddress|string ...$addresses
+     *
+     * @return $this
+     */
+    public function addFrom(...$addresses)
+    {
+        return $this->addListAddressHeaderBody('From', $addresses);
+    }
+
+    /**
+     * @param Address|NamedAddress|string ...$addresses
+     *
+     * @return $this
+     */
+    public function from(...$addresses)
+    {
+        return $this->setListAddressHeaderBody('From', $addresses);
+    }
+
+    /**
+     * @return (Address|NamedAddress)[]
+     */
+    public function getFrom(): array
+    {
+        return $this->getHeaders()->getHeaderBody('From') ?: [];
+    }
+
+    /**
+     * @param Address|string ...$addresses
+     *
+     * @return $this
+     */
+    public function addReplyTo(...$addresses)
+    {
+        return $this->addListAddressHeaderBody('Reply-To', $addresses);
+    }
+
+    /**
+     * @param Address|string ...$addresses
+     *
+     * @return $this
+     */
+    public function replyTo(...$addresses)
+    {
+        return $this->setListAddressHeaderBody('Reply-To', $addresses);
+    }
+
+    /**
+     * @return Address[]
+     */
+    public function getReplyTo(): array
+    {
+        return $this->getHeaders()->getHeaderBody('Reply-To') ?: [];
+    }
+
+    /**
+     * @param Address|NamedAddress|string ...$addresses
+     *
+     * @return $this
+     */
+    public function addTo(...$addresses)
+    {
+        return $this->addListAddressHeaderBody('To', $addresses);
+    }
+
+    /**
+     * @param Address|NamedAddress|string ...$addresses
+     *
+     * @return $this
+     */
+    public function to(...$addresses)
+    {
+        return $this->setListAddressHeaderBody('To', $addresses);
+    }
+
+    /**
+     * @return (Address|NamedAddress)[]
+     */
+    public function getTo(): array
+    {
+        return $this->getHeaders()->getHeaderBody('To') ?: [];
+    }
+
+    /**
+     * @param Address|NamedAddress|string ...$addresses
+     *
+     * @return $this
+     */
+    public function addCc(...$addresses)
+    {
+        return $this->addListAddressHeaderBody('Cc', $addresses);
+    }
+
+    /**
+     * @param Address|string ...$addresses
+     *
+     * @return $this
+     */
+    public function cc(...$addresses)
+    {
+        return $this->setListAddressHeaderBody('Cc', $addresses);
+    }
+
+    /**
+     * @return (Address|NamedAddress)[]
+     */
+    public function getCc(): array
+    {
+        return $this->getHeaders()->getHeaderBody('Cc') ?: [];
+    }
+
+    /**
+     * @param Address|NamedAddress|string ...$addresses
+     *
+     * @return $this
+     */
+    public function addBcc(...$addresses)
+    {
+        return $this->addListAddressHeaderBody('Bcc', $addresses);
+    }
+
+    /**
+     * @param Address|string ...$addresses
+     *
+     * @return $this
+     */
+    public function bcc(...$addresses)
+    {
+        return $this->setListAddressHeaderBody('Bcc', $addresses);
+    }
+
+    /**
+     * @return (Address|NamedAddress)[]
+     */
+    public function getBcc(): array
+    {
+        return $this->getHeaders()->getHeaderBody('Bcc') ?: [];
+    }
+
+    /**
+     * Sets the priority of this message.
+     *
+     * The value is an integer where 1 is the highest priority and 5 is the lowest.
+     *
+     * @return $this
+     */
+    public function priority(int $priority)
+    {
+        if ($priority > 5) {
+            $priority = 5;
+        } elseif ($priority < 1) {
+            $priority = 1;
+        }
+
+        return $this->setHeaderBody('Text', 'X-Priority', sprintf('%d (%s)', $priority, self::PRIORITY_MAP[$priority]));
+    }
+
+    /**
+     * Get the priority of this message.
+     *
+     * The returned value is an integer where 1 is the highest priority and 5
+     * is the lowest.
+     */
+    public function getPriority(): int
+    {
+        list($priority) = sscanf($this->getHeaders()->getHeaderBody('X-Priority'), '%[1-5]');
+
+        return $priority ?? 3;
+    }
+
+    /**
+     * @param resource|string $body
+     *
+     * @return $this
+     */
+    public function text($body, string $charset = 'utf-8')
+    {
+        $this->text = $body;
+        $this->textCharset = $charset;
+
+        return $this;
+    }
+
+    /**
+     * @return resource|string|null
+     */
+    public function getTextBody()
+    {
+        return $this->text;
+    }
+
+    public function getTextCharset(): ?string
+    {
+        return $this->textCharset;
+    }
+
+    /**
+     * @param resource|string|null $body
+     *
+     * @return $this
+     */
+    public function html($body, string $charset = 'utf-8')
+    {
+        $this->html = $body;
+        $this->htmlCharset = $charset;
+
+        return $this;
+    }
+
+    /**
+     * @return resource|string|null
+     */
+    public function getHtmlBody()
+    {
+        return $this->html;
+    }
+
+    public function getHtmlCharset(): ?string
+    {
+        return $this->htmlCharset;
+    }
+
+    /**
+     * @param resource|string $body
+     *
+     * @return $this
+     */
+    public function attach($body, string $name = null, string $contentType = null)
+    {
+        $this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
+
+        return $this;
+    }
+
+    /**
+     * @return $this
+     */
+    public function attachFromPath(string $path, string $name = null, string $contentType = null)
+    {
+        $this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
+
+        return $this;
+    }
+
+    /**
+     * @param resource|string $body
+     *
+     * @return $this
+     */
+    public function embed($body, string $name = null, string $contentType = null)
+    {
+        $this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
+
+        return $this;
+    }
+
+    /**
+     * @return $this
+     */
+    public function embedFromPath(string $path, string $name = null, string $contentType = null)
+    {
+        $this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
+
+        return $this;
+    }
+
+    /**
+     * @return $this
+     */
+    public function attachPart(DataPart $part)
+    {
+        $this->attachments[] = ['part' => $part];
+
+        return $this;
+    }
+
+    /**
+     * @return DataPart[]
+     */
+    public function getAttachments(): array
+    {
+        $parts = [];
+        foreach ($this->attachments as $attachment) {
+            $parts[] = $this->createDataPart($attachment);
+        }
+
+        return $parts;
+    }
+
+    public function getBody(): AbstractPart
+    {
+        if (null !== $body = parent::getBody()) {
+            return $body;
+        }
+
+        return $this->generateBody();
+    }
+
+    /**
+     * Generates an AbstractPart based on the raw body of a message.
+     *
+     * The most "complex" part generated by this method is when there is text and HTML bodies
+     * with related images for the HTML part and some attachments:
+     *
+     * multipart/mixed
+     *         |
+     *         |------------> multipart/related
+     *         |                      |
+     *         |                      |------------> multipart/alternative
+     *         |                      |                      |
+     *         |                      |                       ------------> text/plain (with content)
+     *         |                      |                      |
+     *         |                      |                       ------------> text/html (with content)
+     *         |                      |
+     *         |                       ------------> image/png (with content)
+     *         |
+     *          ------------> application/pdf (with content)
+     */
+    private function generateBody(): AbstractPart
+    {
+        [$htmlPart, $attachmentParts, $inlineParts] = $this->prepareParts();
+        if (null === $this->text && null === $this->html && !$attachmentParts) {
+            throw new LogicException('A message must have a text or an HTML part or attachments.');
+        }
+
+        $part = null === $this->text ? null : new TextPart($this->text, $this->textCharset);
+        if (null !== $htmlPart) {
+            if (null !== $part) {
+                $part = new AlternativePart($part, $htmlPart);
+            } else {
+                $part = $htmlPart;
+            }
+        }
+
+        if ($inlineParts) {
+            $part = new RelatedPart($part, ...$inlineParts);
+        }
+
+        if ($attachmentParts) {
+            if ($part) {
+                $part = new MixedPart($part, ...$attachmentParts);
+            } else {
+                $part = new MixedPart(...$attachmentParts);
+            }
+        }
+
+        return $part;
+    }
+
+    private function prepareParts(): ?array
+    {
+        $names = [];
+        $htmlPart = null;
+        $html = $this->html;
+        if (null !== $this->html) {
+            if (\is_resource($html)) {
+                if (stream_get_meta_data($html)['seekable'] ?? false) {
+                    rewind($html);
+                }
+
+                $html = stream_get_contents($html);
+            }
+            $htmlPart = new TextPart($html, $this->htmlCharset, 'html');
+            preg_match_all('(<img\s+[^>]*src\s*=\s*(?:([\'"])cid:([^"]+)\\1|cid:([^>\s]+)))i', $html, $names);
+            $names = array_filter(array_unique(array_merge($names[2], $names[3])));
+        }
+
+        $attachmentParts = $inlineParts = [];
+        foreach ($this->attachments as $attachment) {
+            foreach ($names as $name) {
+                if (isset($attachment['part'])) {
+                    continue;
+                }
+                if ($name !== $attachment['name']) {
+                    continue;
+                }
+                if (isset($inlineParts[$name])) {
+                    continue 2;
+                }
+                $attachment['inline'] = true;
+                $inlineParts[$name] = $part = $this->createDataPart($attachment);
+                $html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html);
+                continue 2;
+            }
+            $attachmentParts[] = $this->createDataPart($attachment);
+        }
+        if (null !== $htmlPart) {
+            $htmlPart = new TextPart($html, $this->htmlCharset, 'html');
+        }
+
+        return [$htmlPart, $attachmentParts, array_values($inlineParts)];
+    }
+
+    private function createDataPart(array $attachment): DataPart
+    {
+        if (isset($attachment['part'])) {
+            return $attachment['part'];
+        }
+
+        if (isset($attachment['body'])) {
+            $part = new DataPart($attachment['body'], $attachment['name'] ?? null, $attachment['content-type'] ?? null);
+        } else {
+            $part = DataPart::fromPath($attachment['path'] ?? '', $attachment['name'] ?? null, $attachment['content-type'] ?? null);
+        }
+        if ($attachment['inline']) {
+            $part->asInline();
+        }
+
+        return $part;
+    }
+
+    /**
+     * @return $this
+     */
+    private function setHeaderBody(string $type, string $name, $body)
+    {
+        $this->getHeaders()->setHeaderBody($type, $name, $body);
+
+        return $this;
+    }
+
+    private function addListAddressHeaderBody($name, array $addresses)
+    {
+        if (!$to = $this->getHeaders()->get($name)) {
+            return $this->setListAddressHeaderBody($name, $addresses);
+        }
+        $to->addAddresses(Address::createArray($addresses));
+
+        return $this;
+    }
+
+    private function setListAddressHeaderBody($name, array $addresses)
+    {
+        $addresses = Address::createArray($addresses);
+        $headers = $this->getHeaders();
+        if ($to = $headers->get($name)) {
+            $to->setAddresses($addresses);
+        } else {
+            $headers->addMailboxListHeader($name, $addresses);
+        }
+
+        return $this;
+    }
+
+    /**
+     * @internal
+     */
+    public function __serialize(): array
+    {
+        if (\is_resource($this->text)) {
+            if (stream_get_meta_data($this->text)['seekable'] ?? false) {
+                rewind($this->text);
+            }
+
+            $this->text = stream_get_contents($this->text);
+        }
+
+        if (\is_resource($this->html)) {
+            if (stream_get_meta_data($this->html)['seekable'] ?? false) {
+                rewind($this->html);
+            }
+
+            $this->html = stream_get_contents($this->html);
+        }
+
+        foreach ($this->attachments as $i => $attachment) {
+            if (isset($attachment['body']) && \is_resource($attachment['body'])) {
+                if (stream_get_meta_data($attachment['body'])['seekable'] ?? false) {
+                    rewind($attachment['body']);
+                }
+
+                $this->attachments[$i]['body'] = stream_get_contents($attachment['body']);
+            }
+        }
+
+        return [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, parent::__serialize()];
+    }
+
+    /**
+     * @internal
+     */
+    public function __unserialize(array $data): void
+    {
+        [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, $parentData] = $data;
+
+        parent::__unserialize($parentData);
+    }
+}
diff --git a/vendor/symfony/mime/Encoder/AddressEncoderInterface.php b/vendor/symfony/mime/Encoder/AddressEncoderInterface.php
new file mode 100644
index 0000000..5d6ea3c
--- /dev/null
+++ b/vendor/symfony/mime/Encoder/AddressEncoderInterface.php
@@ -0,0 +1,30 @@
+<?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\Mime\Encoder;
+
+use Symfony\Component\Mime\Exception\AddressEncoderException;
+
+/**
+ * @author Christian Schmidt
+ *
+ * @experimental in 4.3
+ */
+interface AddressEncoderInterface
+{
+    /**
+     * Encodes an email address.
+     *
+     * @throws AddressEncoderException if the email cannot be represented in
+     *                                 the encoding implemented by this class
+     */
+    public function encodeString(string $address): string;
+}
diff --git a/vendor/symfony/mime/Encoder/Base64ContentEncoder.php b/vendor/symfony/mime/Encoder/Base64ContentEncoder.php
new file mode 100644
index 0000000..e9c352e
--- /dev/null
+++ b/vendor/symfony/mime/Encoder/Base64ContentEncoder.php
@@ -0,0 +1,50 @@
+<?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\Mime\Encoder;
+
+use Symfony\Component\Mime\Exception\RuntimeException;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+final class Base64ContentEncoder extends Base64Encoder implements ContentEncoderInterface
+{
+    public function encodeByteStream($stream, int $maxLineLength = 0): iterable
+    {
+        if (!\is_resource($stream)) {
+            throw new \TypeError(sprintf('Method "%s" takes a stream as a first argument.', __METHOD__));
+        }
+
+        $filter = stream_filter_append($stream, 'convert.base64-encode', \STREAM_FILTER_READ, [
+            'line-length' => 0 >= $maxLineLength || 76 < $maxLineLength ? 76 : $maxLineLength,
+            'line-break-chars' => "\r\n",
+        ]);
+        if (!\is_resource($filter)) {
+            throw new RuntimeException('Unable to set the base64 content encoder to the filter.');
+        }
+
+        if (stream_get_meta_data($stream)['seekable'] ?? false) {
+            rewind($stream);
+        }
+        while (!feof($stream)) {
+            yield fread($stream, 8192);
+        }
+        stream_filter_remove($filter);
+    }
+
+    public function getName(): string
+    {
+        return 'base64';
+    }
+}
diff --git a/vendor/symfony/mime/Encoder/Base64Encoder.php b/vendor/symfony/mime/Encoder/Base64Encoder.php
new file mode 100644
index 0000000..25dae67
--- /dev/null
+++ b/vendor/symfony/mime/Encoder/Base64Encoder.php
@@ -0,0 +1,43 @@
+<?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\Mime\Encoder;
+
+/**
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+class Base64Encoder implements EncoderInterface
+{
+    /**
+     * Takes an unencoded string and produces a Base64 encoded string from it.
+     *
+     * Base64 encoded strings have a maximum line length of 76 characters.
+     * If the first line needs to be shorter, indicate the difference with
+     * $firstLineOffset.
+     */
+    public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string
+    {
+        if (0 >= $maxLineLength || 76 < $maxLineLength) {
+            $maxLineLength = 76;
+        }
+
+        $encodedString = base64_encode($string);
+        $firstLine = '';
+        if (0 !== $firstLineOffset) {
+            $firstLine = substr($encodedString, 0, $maxLineLength - $firstLineOffset)."\r\n";
+            $encodedString = substr($encodedString, $maxLineLength - $firstLineOffset);
+        }
+
+        return $firstLine.trim(chunk_split($encodedString, $maxLineLength, "\r\n"));
+    }
+}
diff --git a/vendor/symfony/mime/Encoder/Base64MimeHeaderEncoder.php b/vendor/symfony/mime/Encoder/Base64MimeHeaderEncoder.php
new file mode 100644
index 0000000..8baee5b
--- /dev/null
+++ b/vendor/symfony/mime/Encoder/Base64MimeHeaderEncoder.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\Mime\Encoder;
+
+/**
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+final class Base64MimeHeaderEncoder extends Base64Encoder implements MimeHeaderEncoderInterface
+{
+    public function getName(): string
+    {
+        return 'B';
+    }
+
+    /**
+     * Takes an unencoded string and produces a Base64 encoded string from it.
+     *
+     * If the charset is iso-2022-jp, it uses mb_encode_mimeheader instead of
+     * default encodeString, otherwise pass to the parent method.
+     */
+    public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string
+    {
+        if ('iso-2022-jp' === strtolower($charset)) {
+            $old = mb_internal_encoding();
+            mb_internal_encoding('utf-8');
+            $newstring = mb_encode_mimeheader($string, 'iso-2022-jp', $this->getName(), "\r\n");
+            mb_internal_encoding($old);
+
+            return $newstring;
+        }
+
+        return parent::encodeString($string, $charset, $firstLineOffset, $maxLineLength);
+    }
+}
diff --git a/vendor/symfony/mime/Encoder/ContentEncoderInterface.php b/vendor/symfony/mime/Encoder/ContentEncoderInterface.php
new file mode 100644
index 0000000..b44e1a4
--- /dev/null
+++ b/vendor/symfony/mime/Encoder/ContentEncoderInterface.php
@@ -0,0 +1,32 @@
+<?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\Mime\Encoder;
+
+/**
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+interface ContentEncoderInterface extends EncoderInterface
+{
+    /**
+     * Encodes the stream to a Generator.
+     *
+     * @param resource $stream
+     */
+    public function encodeByteStream($stream, int $maxLineLength = 0): iterable;
+
+    /**
+     * Gets the MIME name of this content encoding scheme.
+     */
+    public function getName(): string;
+}
diff --git a/vendor/symfony/mime/Encoder/EightBitContentEncoder.php b/vendor/symfony/mime/Encoder/EightBitContentEncoder.php
new file mode 100644
index 0000000..94b838c
--- /dev/null
+++ b/vendor/symfony/mime/Encoder/EightBitContentEncoder.php
@@ -0,0 +1,37 @@
+<?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\Mime\Encoder;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+final class EightBitContentEncoder implements ContentEncoderInterface
+{
+    public function encodeByteStream($stream, int $maxLineLength = 0): iterable
+    {
+        while (!feof($stream)) {
+            yield fread($stream, 16372);
+        }
+    }
+
+    public function getName(): string
+    {
+        return '8bit';
+    }
+
+    public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string
+    {
+        return $string;
+    }
+}
diff --git a/vendor/symfony/mime/Encoder/EncoderInterface.php b/vendor/symfony/mime/Encoder/EncoderInterface.php
new file mode 100644
index 0000000..3c2ef19
--- /dev/null
+++ b/vendor/symfony/mime/Encoder/EncoderInterface.php
@@ -0,0 +1,28 @@
+<?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\Mime\Encoder;
+
+/**
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+interface EncoderInterface
+{
+    /**
+     * Encode a given string to produce an encoded string.
+     *
+     * @param int $firstLineOffset if first line needs to be shorter
+     * @param int $maxLineLength   - 0 indicates the default length for this encoding
+     */
+    public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string;
+}
diff --git a/vendor/symfony/mime/Encoder/IdnAddressEncoder.php b/vendor/symfony/mime/Encoder/IdnAddressEncoder.php
new file mode 100644
index 0000000..8936047
--- /dev/null
+++ b/vendor/symfony/mime/Encoder/IdnAddressEncoder.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\Mime\Encoder;
+
+use Symfony\Component\Mime\Exception\AddressEncoderException;
+
+/**
+ * An IDN email address encoder.
+ *
+ * Encodes the domain part of an address using IDN. This is compatible will all
+ * SMTP servers.
+ *
+ * This encoder does not support email addresses with non-ASCII characters in
+ * local-part (the substring before @). To send to such addresses, use
+ * Utf8AddressEncoder together with SmtpUtf8Handler. Your outbound SMTP server must support
+ * the SMTPUTF8 extension.
+ *
+ * @author Christian Schmidt
+ *
+ * @experimental in 4.3
+ */
+final class IdnAddressEncoder implements AddressEncoderInterface
+{
+    /**
+     * Encodes the domain part of an address using IDN.
+     *
+     * @throws AddressEncoderException If local-part contains non-ASCII characters
+     */
+    public function encodeString(string $address): string
+    {
+        $i = strrpos($address, '@');
+        if (false !== $i) {
+            $local = substr($address, 0, $i);
+            $domain = substr($address, $i + 1);
+
+            if (preg_match('/[^\x00-\x7F]/', $local)) {
+                throw new AddressEncoderException(sprintf('Non-ASCII characters not supported in local-part os "%s".', $address));
+            }
+
+            if (preg_match('/[^\x00-\x7F]/', $domain)) {
+                $address = sprintf('%s@%s', $local, idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46));
+            }
+        }
+
+        return $address;
+    }
+}
diff --git a/vendor/symfony/mime/Encoder/MimeHeaderEncoderInterface.php b/vendor/symfony/mime/Encoder/MimeHeaderEncoderInterface.php
new file mode 100644
index 0000000..f575665
--- /dev/null
+++ b/vendor/symfony/mime/Encoder/MimeHeaderEncoderInterface.php
@@ -0,0 +1,25 @@
+<?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\Mime\Encoder;
+
+/**
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+interface MimeHeaderEncoderInterface
+{
+    /**
+     * Get the MIME name of this content encoding scheme.
+     */
+    public function getName(): string;
+}
diff --git a/vendor/symfony/mime/Encoder/QpContentEncoder.php b/vendor/symfony/mime/Encoder/QpContentEncoder.php
new file mode 100644
index 0000000..ef2de46
--- /dev/null
+++ b/vendor/symfony/mime/Encoder/QpContentEncoder.php
@@ -0,0 +1,66 @@
+<?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\Mime\Encoder;
+
+/**
+ * @author Lars Strojny
+ *
+ * @experimental in 4.3
+ */
+final class QpContentEncoder implements ContentEncoderInterface
+{
+    public function encodeByteStream($stream, int $maxLineLength = 0): iterable
+    {
+        if (!\is_resource($stream)) {
+            throw new \TypeError(sprintf('Method "%s" takes a stream as a first argument.', __METHOD__));
+        }
+
+        // we don't use PHP stream filters here as the content should be small enough
+        if (stream_get_meta_data($stream)['seekable'] ?? false) {
+            rewind($stream);
+        }
+
+        yield $this->encodeString(stream_get_contents($stream), 'utf-8', 0, $maxLineLength);
+    }
+
+    public function getName(): string
+    {
+        return 'quoted-printable';
+    }
+
+    public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string
+    {
+        return $this->standardize(quoted_printable_encode($string));
+    }
+
+    /**
+     * Make sure CRLF is correct and HT/SPACE are in valid places.
+     */
+    private function standardize(string $string): string
+    {
+        // transform CR or LF to CRLF
+        $string = preg_replace('~=0D(?!=0A)|(?<!=0D)=0A~', '=0D=0A', $string);
+        // transform =0D=0A to CRLF
+        $string = str_replace(["\t=0D=0A", ' =0D=0A', '=0D=0A'], ["=09\r\n", "=20\r\n", "\r\n"], $string);
+
+        switch (\ord(substr($string, -1))) {
+            case 0x09:
+                $string = substr_replace($string, '=09', -1);
+                break;
+            case 0x20:
+                $string = substr_replace($string, '=20', -1);
+                break;
+        }
+
+        return $string;
+    }
+}
diff --git a/vendor/symfony/mime/Encoder/QpEncoder.php b/vendor/symfony/mime/Encoder/QpEncoder.php
new file mode 100644
index 0000000..4ffbaed
--- /dev/null
+++ b/vendor/symfony/mime/Encoder/QpEncoder.php
@@ -0,0 +1,197 @@
+<?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\Mime\Encoder;
+
+use Symfony\Component\Mime\CharacterStream;
+
+/**
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+class QpEncoder implements EncoderInterface
+{
+    /**
+     * Pre-computed QP for HUGE optimization.
+     */
+    private static $qpMap = [
+        0 => '=00', 1 => '=01', 2 => '=02', 3 => '=03', 4 => '=04',
+        5 => '=05', 6 => '=06', 7 => '=07', 8 => '=08', 9 => '=09',
+        10 => '=0A', 11 => '=0B', 12 => '=0C', 13 => '=0D', 14 => '=0E',
+        15 => '=0F', 16 => '=10', 17 => '=11', 18 => '=12', 19 => '=13',
+        20 => '=14', 21 => '=15', 22 => '=16', 23 => '=17', 24 => '=18',
+        25 => '=19', 26 => '=1A', 27 => '=1B', 28 => '=1C', 29 => '=1D',
+        30 => '=1E', 31 => '=1F', 32 => '=20', 33 => '=21', 34 => '=22',
+        35 => '=23', 36 => '=24', 37 => '=25', 38 => '=26', 39 => '=27',
+        40 => '=28', 41 => '=29', 42 => '=2A', 43 => '=2B', 44 => '=2C',
+        45 => '=2D', 46 => '=2E', 47 => '=2F', 48 => '=30', 49 => '=31',
+        50 => '=32', 51 => '=33', 52 => '=34', 53 => '=35', 54 => '=36',
+        55 => '=37', 56 => '=38', 57 => '=39', 58 => '=3A', 59 => '=3B',
+        60 => '=3C', 61 => '=3D', 62 => '=3E', 63 => '=3F', 64 => '=40',
+        65 => '=41', 66 => '=42', 67 => '=43', 68 => '=44', 69 => '=45',
+        70 => '=46', 71 => '=47', 72 => '=48', 73 => '=49', 74 => '=4A',
+        75 => '=4B', 76 => '=4C', 77 => '=4D', 78 => '=4E', 79 => '=4F',
+        80 => '=50', 81 => '=51', 82 => '=52', 83 => '=53', 84 => '=54',
+        85 => '=55', 86 => '=56', 87 => '=57', 88 => '=58', 89 => '=59',
+        90 => '=5A', 91 => '=5B', 92 => '=5C', 93 => '=5D', 94 => '=5E',
+        95 => '=5F', 96 => '=60', 97 => '=61', 98 => '=62', 99 => '=63',
+        100 => '=64', 101 => '=65', 102 => '=66', 103 => '=67', 104 => '=68',
+        105 => '=69', 106 => '=6A', 107 => '=6B', 108 => '=6C', 109 => '=6D',
+        110 => '=6E', 111 => '=6F', 112 => '=70', 113 => '=71', 114 => '=72',
+        115 => '=73', 116 => '=74', 117 => '=75', 118 => '=76', 119 => '=77',
+        120 => '=78', 121 => '=79', 122 => '=7A', 123 => '=7B', 124 => '=7C',
+        125 => '=7D', 126 => '=7E', 127 => '=7F', 128 => '=80', 129 => '=81',
+        130 => '=82', 131 => '=83', 132 => '=84', 133 => '=85', 134 => '=86',
+        135 => '=87', 136 => '=88', 137 => '=89', 138 => '=8A', 139 => '=8B',
+        140 => '=8C', 141 => '=8D', 142 => '=8E', 143 => '=8F', 144 => '=90',
+        145 => '=91', 146 => '=92', 147 => '=93', 148 => '=94', 149 => '=95',
+        150 => '=96', 151 => '=97', 152 => '=98', 153 => '=99', 154 => '=9A',
+        155 => '=9B', 156 => '=9C', 157 => '=9D', 158 => '=9E', 159 => '=9F',
+        160 => '=A0', 161 => '=A1', 162 => '=A2', 163 => '=A3', 164 => '=A4',
+        165 => '=A5', 166 => '=A6', 167 => '=A7', 168 => '=A8', 169 => '=A9',
+        170 => '=AA', 171 => '=AB', 172 => '=AC', 173 => '=AD', 174 => '=AE',
+        175 => '=AF', 176 => '=B0', 177 => '=B1', 178 => '=B2', 179 => '=B3',
+        180 => '=B4', 181 => '=B5', 182 => '=B6', 183 => '=B7', 184 => '=B8',
+        185 => '=B9', 186 => '=BA', 187 => '=BB', 188 => '=BC', 189 => '=BD',
+        190 => '=BE', 191 => '=BF', 192 => '=C0', 193 => '=C1', 194 => '=C2',
+        195 => '=C3', 196 => '=C4', 197 => '=C5', 198 => '=C6', 199 => '=C7',
+        200 => '=C8', 201 => '=C9', 202 => '=CA', 203 => '=CB', 204 => '=CC',
+        205 => '=CD', 206 => '=CE', 207 => '=CF', 208 => '=D0', 209 => '=D1',
+        210 => '=D2', 211 => '=D3', 212 => '=D4', 213 => '=D5', 214 => '=D6',
+        215 => '=D7', 216 => '=D8', 217 => '=D9', 218 => '=DA', 219 => '=DB',
+        220 => '=DC', 221 => '=DD', 222 => '=DE', 223 => '=DF', 224 => '=E0',
+        225 => '=E1', 226 => '=E2', 227 => '=E3', 228 => '=E4', 229 => '=E5',
+        230 => '=E6', 231 => '=E7', 232 => '=E8', 233 => '=E9', 234 => '=EA',
+        235 => '=EB', 236 => '=EC', 237 => '=ED', 238 => '=EE', 239 => '=EF',
+        240 => '=F0', 241 => '=F1', 242 => '=F2', 243 => '=F3', 244 => '=F4',
+        245 => '=F5', 246 => '=F6', 247 => '=F7', 248 => '=F8', 249 => '=F9',
+        250 => '=FA', 251 => '=FB', 252 => '=FC', 253 => '=FD', 254 => '=FE',
+        255 => '=FF',
+    ];
+
+    private static $safeMapShare = [];
+
+    /**
+     * A map of non-encoded ascii characters.
+     *
+     * @var string[]
+     *
+     * @internal
+     */
+    protected $safeMap = [];
+
+    public function __construct()
+    {
+        $id = \get_class($this);
+        if (!isset(self::$safeMapShare[$id])) {
+            $this->initSafeMap();
+            self::$safeMapShare[$id] = $this->safeMap;
+        } else {
+            $this->safeMap = self::$safeMapShare[$id];
+        }
+    }
+
+    protected function initSafeMap(): void
+    {
+        foreach (array_merge([0x09, 0x20], range(0x21, 0x3C), range(0x3E, 0x7E)) as $byte) {
+            $this->safeMap[$byte] = \chr($byte);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * Takes an unencoded string and produces a QP encoded string from it.
+     *
+     * QP encoded strings have a maximum line length of 76 characters.
+     * If the first line needs to be shorter, indicate the difference with
+     * $firstLineOffset.
+     */
+    public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string
+    {
+        if ($maxLineLength > 76 || $maxLineLength <= 0) {
+            $maxLineLength = 76;
+        }
+
+        $thisLineLength = $maxLineLength - $firstLineOffset;
+
+        $lines = [];
+        $lNo = 0;
+        $lines[$lNo] = '';
+        $currentLine = &$lines[$lNo++];
+        $size = $lineLen = 0;
+        $charStream = new CharacterStream($string, $charset);
+
+        // Fetching more than 4 chars at one is slower, as is fetching fewer bytes
+        // Conveniently 4 chars is the UTF-8 safe number since UTF-8 has up to 6
+        // bytes per char and (6 * 4 * 3 = 72 chars per line) * =NN is 3 bytes
+        while (null !== $bytes = $charStream->readBytes(4)) {
+            $enc = $this->encodeByteSequence($bytes, $size);
+
+            $i = strpos($enc, '=0D=0A');
+            $newLineLength = $lineLen + (false === $i ? $size : $i);
+
+            if ($currentLine && $newLineLength >= $thisLineLength) {
+                $lines[$lNo] = '';
+                $currentLine = &$lines[$lNo++];
+                $thisLineLength = $maxLineLength;
+                $lineLen = 0;
+            }
+
+            $currentLine .= $enc;
+
+            if (false === $i) {
+                $lineLen += $size;
+            } else {
+                // 6 is the length of '=0D=0A'.
+                $lineLen = $size - strrpos($enc, '=0D=0A') - 6;
+            }
+        }
+
+        return $this->standardize(implode("=\r\n", $lines));
+    }
+
+    /**
+     * Encode the given byte array into a verbatim QP form.
+     */
+    private function encodeByteSequence(array $bytes, int &$size): string
+    {
+        $ret = '';
+        $size = 0;
+        foreach ($bytes as $b) {
+            if (isset($this->safeMap[$b])) {
+                $ret .= $this->safeMap[$b];
+                ++$size;
+            } else {
+                $ret .= self::$qpMap[$b];
+                $size += 3;
+            }
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Make sure CRLF is correct and HT/SPACE are in valid places.
+     */
+    private function standardize(string $string): string
+    {
+        $string = str_replace(["\t=0D=0A", ' =0D=0A', '=0D=0A'], ["=09\r\n", "=20\r\n", "\r\n"], $string);
+        switch ($end = \ord(substr($string, -1))) {
+            case 0x09:
+            case 0x20:
+                $string = substr_replace($string, self::$qpMap[$end], -1);
+        }
+
+        return $string;
+    }
+}
diff --git a/vendor/symfony/mime/Encoder/QpMimeHeaderEncoder.php b/vendor/symfony/mime/Encoder/QpMimeHeaderEncoder.php
new file mode 100644
index 0000000..0413959
--- /dev/null
+++ b/vendor/symfony/mime/Encoder/QpMimeHeaderEncoder.php
@@ -0,0 +1,42 @@
+<?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\Mime\Encoder;
+
+/**
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+final class QpMimeHeaderEncoder extends QpEncoder implements MimeHeaderEncoderInterface
+{
+    protected function initSafeMap(): void
+    {
+        foreach (array_merge(
+            range(0x61, 0x7A), range(0x41, 0x5A),
+            range(0x30, 0x39), [0x20, 0x21, 0x2A, 0x2B, 0x2D, 0x2F]
+        ) as $byte) {
+            $this->safeMap[$byte] = \chr($byte);
+        }
+    }
+
+    public function getName(): string
+    {
+        return 'Q';
+    }
+
+    public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string
+    {
+        return str_replace([' ', '=20', "=\r\n"], ['_', '_', "\r\n"],
+            parent::encodeString($string, $charset, $firstLineOffset, $maxLineLength)
+        );
+    }
+}
diff --git a/vendor/symfony/mime/Encoder/Rfc2231Encoder.php b/vendor/symfony/mime/Encoder/Rfc2231Encoder.php
new file mode 100644
index 0000000..4743a72
--- /dev/null
+++ b/vendor/symfony/mime/Encoder/Rfc2231Encoder.php
@@ -0,0 +1,52 @@
+<?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\Mime\Encoder;
+
+use Symfony\Component\Mime\CharacterStream;
+
+/**
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+final class Rfc2231Encoder implements EncoderInterface
+{
+    /**
+     * Takes an unencoded string and produces a string encoded according to RFC 2231 from it.
+     */
+    public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string
+    {
+        $lines = [];
+        $lineCount = 0;
+        $lines[] = '';
+        $currentLine = &$lines[$lineCount++];
+
+        if (0 >= $maxLineLength) {
+            $maxLineLength = 75;
+        }
+
+        $charStream = new CharacterStream($string, $charset);
+        $thisLineLength = $maxLineLength - $firstLineOffset;
+
+        while (null !== $char = $charStream->read(4)) {
+            $encodedChar = rawurlencode($char);
+            if (0 !== \strlen($currentLine) && \strlen($currentLine.$encodedChar) > $thisLineLength) {
+                $lines[] = '';
+                $currentLine = &$lines[$lineCount++];
+                $thisLineLength = $maxLineLength;
+            }
+            $currentLine .= $encodedChar;
+        }
+
+        return implode("\r\n", $lines);
+    }
+}
diff --git a/vendor/symfony/mime/Exception/AddressEncoderException.php b/vendor/symfony/mime/Exception/AddressEncoderException.php
new file mode 100644
index 0000000..73ef7f3
--- /dev/null
+++ b/vendor/symfony/mime/Exception/AddressEncoderException.php
@@ -0,0 +1,21 @@
+<?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\Mime\Exception;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+class AddressEncoderException extends RfcComplianceException
+{
+}
diff --git a/vendor/symfony/mime/Exception/ExceptionInterface.php b/vendor/symfony/mime/Exception/ExceptionInterface.php
new file mode 100644
index 0000000..7dbcdc7
--- /dev/null
+++ b/vendor/symfony/mime/Exception/ExceptionInterface.php
@@ -0,0 +1,21 @@
+<?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\Mime\Exception;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/vendor/symfony/mime/Exception/InvalidArgumentException.php b/vendor/symfony/mime/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..59d04e2
--- /dev/null
+++ b/vendor/symfony/mime/Exception/InvalidArgumentException.php
@@ -0,0 +1,21 @@
+<?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\Mime\Exception;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/mime/Exception/LogicException.php b/vendor/symfony/mime/Exception/LogicException.php
new file mode 100644
index 0000000..07cb044
--- /dev/null
+++ b/vendor/symfony/mime/Exception/LogicException.php
@@ -0,0 +1,21 @@
+<?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\Mime\Exception;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/mime/Exception/RfcComplianceException.php b/vendor/symfony/mime/Exception/RfcComplianceException.php
new file mode 100644
index 0000000..5dc4cf5
--- /dev/null
+++ b/vendor/symfony/mime/Exception/RfcComplianceException.php
@@ -0,0 +1,21 @@
+<?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\Mime\Exception;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+class RfcComplianceException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/mime/Exception/RuntimeException.php b/vendor/symfony/mime/Exception/RuntimeException.php
new file mode 100644
index 0000000..84b11fb
--- /dev/null
+++ b/vendor/symfony/mime/Exception/RuntimeException.php
@@ -0,0 +1,21 @@
+<?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\Mime\Exception;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/mime/FileBinaryMimeTypeGuesser.php b/vendor/symfony/mime/FileBinaryMimeTypeGuesser.php
new file mode 100644
index 0000000..59e55f7
--- /dev/null
+++ b/vendor/symfony/mime/FileBinaryMimeTypeGuesser.php
@@ -0,0 +1,95 @@
+<?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\Mime;
+
+use Symfony\Component\Mime\Exception\InvalidArgumentException;
+use Symfony\Component\Mime\Exception\LogicException;
+
+/**
+ * Guesses the MIME type with the binary "file" (only available on *nix).
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ *
+ * @experimental in 4.3
+ */
+class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
+{
+    private $cmd;
+
+    /**
+     * The $cmd pattern must contain a "%s" string that will be replaced
+     * with the file name to guess.
+     *
+     * The command output must start with the MIME type of the file.
+     *
+     * @param string $cmd The command to run to get the MIME type of a file
+     */
+    public function __construct(string $cmd = 'file -b --mime -- %s 2>/dev/null')
+    {
+        $this->cmd = $cmd;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isGuesserSupported(): bool
+    {
+        static $supported = null;
+
+        if (null !== $supported) {
+            return $supported;
+        }
+
+        if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('passthru') || !\function_exists('escapeshellarg')) {
+            return $supported = false;
+        }
+
+        ob_start();
+        passthru('command -v file', $exitStatus);
+        $binPath = trim(ob_get_clean());
+
+        return $supported = 0 === $exitStatus && '' !== $binPath;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function guessMimeType(string $path): ?string
+    {
+        if (!is_file($path) || !is_readable($path)) {
+            throw new InvalidArgumentException(sprintf('The "%s" file does not exist or is not readable.', $path));
+        }
+
+        if (!$this->isGuesserSupported()) {
+            throw new LogicException(sprintf('The "%s" guesser is not supported.', __CLASS__));
+        }
+
+        ob_start();
+
+        // need to use --mime instead of -i. see #6641
+        passthru(sprintf($this->cmd, escapeshellarg((0 === strpos($path, '-') ? './' : '').$path)), $return);
+        if ($return > 0) {
+            ob_end_clean();
+
+            return null;
+        }
+
+        $type = trim(ob_get_clean());
+
+        if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\+\.]+)#i', $type, $match)) {
+            // it's not a type, but an error message
+            return null;
+        }
+
+        return $match[1];
+    }
+}
diff --git a/vendor/symfony/mime/FileinfoMimeTypeGuesser.php b/vendor/symfony/mime/FileinfoMimeTypeGuesser.php
new file mode 100644
index 0000000..81c62ee
--- /dev/null
+++ b/vendor/symfony/mime/FileinfoMimeTypeGuesser.php
@@ -0,0 +1,65 @@
+<?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\Mime;
+
+use Symfony\Component\Mime\Exception\InvalidArgumentException;
+use Symfony\Component\Mime\Exception\LogicException;
+
+/**
+ * Guesses the MIME type using the PECL extension FileInfo.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ *
+ * @experimental in 4.3
+ */
+class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
+{
+    private $magicFile;
+
+    /**
+     * @param string $magicFile A magic file to use with the finfo instance
+     *
+     * @see http://www.php.net/manual/en/function.finfo-open.php
+     */
+    public function __construct(string $magicFile = null)
+    {
+        $this->magicFile = $magicFile;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isGuesserSupported(): bool
+    {
+        return \function_exists('finfo_open');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function guessMimeType(string $path): ?string
+    {
+        if (!is_file($path) || !is_readable($path)) {
+            throw new InvalidArgumentException(sprintf('The "%s" file does not exist or is not readable.', $path));
+        }
+
+        if (!$this->isGuesserSupported()) {
+            throw new LogicException(sprintf('The "%s" guesser is not supported.', __CLASS__));
+        }
+
+        if (false === $finfo = new \finfo(FILEINFO_MIME_TYPE, $this->magicFile)) {
+            return null;
+        }
+
+        return $finfo->file($path);
+    }
+}
diff --git a/vendor/symfony/mime/Header/AbstractHeader.php b/vendor/symfony/mime/Header/AbstractHeader.php
new file mode 100644
index 0000000..517ee8d
--- /dev/null
+++ b/vendor/symfony/mime/Header/AbstractHeader.php
@@ -0,0 +1,281 @@
+<?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\Mime\Header;
+
+use Symfony\Component\Mime\Encoder\QpMimeHeaderEncoder;
+
+/**
+ * An abstract base MIME Header.
+ *
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+abstract class AbstractHeader implements HeaderInterface
+{
+    const PHRASE_PATTERN = '(?:(?:(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]+(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?)|(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?"((?:(?:[ \t]*(?:\r\n))?[ \t])?(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21\x23-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])))*(?:(?:[ \t]*(?:\r\n))?[ \t])?"(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?))+?)';
+
+    private static $encoder;
+
+    private $name;
+    private $lineLength = 76;
+    private $lang;
+    private $charset = 'utf-8';
+
+    public function __construct(string $name)
+    {
+        $this->name = $name;
+    }
+
+    public function setCharset(string $charset)
+    {
+        $this->charset = $charset;
+    }
+
+    public function getCharset(): ?string
+    {
+        return $this->charset;
+    }
+
+    /**
+     * Set the language used in this Header.
+     *
+     * For example, for US English, 'en-us'.
+     */
+    public function setLanguage(string $lang)
+    {
+        $this->lang = $lang;
+    }
+
+    public function getLanguage(): ?string
+    {
+        return $this->lang;
+    }
+
+    public function getName(): string
+    {
+        return $this->name;
+    }
+
+    public function setMaxLineLength(int $lineLength)
+    {
+        $this->lineLength = $lineLength;
+    }
+
+    public function getMaxLineLength(): int
+    {
+        return $this->lineLength;
+    }
+
+    public function toString(): string
+    {
+        return $this->tokensToString($this->toTokens());
+    }
+
+    /**
+     * Produces a compliant, formatted RFC 2822 'phrase' based on the string given.
+     *
+     * @param string $string  as displayed
+     * @param bool   $shorten the first line to make remove for header name
+     */
+    protected function createPhrase(HeaderInterface $header, string $string, string $charset, bool $shorten = false): string
+    {
+        // Treat token as exactly what was given
+        $phraseStr = $string;
+
+        // If it's not valid
+        if (!preg_match('/^'.self::PHRASE_PATTERN.'$/D', $phraseStr)) {
+            // .. but it is just ascii text, try escaping some characters
+            // and make it a quoted-string
+            if (preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $phraseStr)) {
+                foreach (['\\', '"'] as $char) {
+                    $phraseStr = str_replace($char, '\\'.$char, $phraseStr);
+                }
+                $phraseStr = '"'.$phraseStr.'"';
+            } else {
+                // ... otherwise it needs encoding
+                // Determine space remaining on line if first line
+                if ($shorten) {
+                    $usedLength = \strlen($header->getName().': ');
+                } else {
+                    $usedLength = 0;
+                }
+                $phraseStr = $this->encodeWords($header, $string, $usedLength);
+            }
+        }
+
+        return $phraseStr;
+    }
+
+    /**
+     * Encode needed word tokens within a string of input.
+     */
+    protected function encodeWords(HeaderInterface $header, string $input, int $usedLength = -1): string
+    {
+        $value = '';
+        $tokens = $this->getEncodableWordTokens($input);
+        foreach ($tokens as $token) {
+            // See RFC 2822, Sect 2.2 (really 2.2 ??)
+            if ($this->tokenNeedsEncoding($token)) {
+                // Don't encode starting WSP
+                $firstChar = substr($token, 0, 1);
+                switch ($firstChar) {
+                    case ' ':
+                    case "\t":
+                        $value .= $firstChar;
+                        $token = substr($token, 1);
+                }
+
+                if (-1 == $usedLength) {
+                    $usedLength = \strlen($header->getName().': ') + \strlen($value);
+                }
+                $value .= $this->getTokenAsEncodedWord($token, $usedLength);
+            } else {
+                $value .= $token;
+            }
+        }
+
+        return $value;
+    }
+
+    protected function tokenNeedsEncoding(string $token): bool
+    {
+        return (bool) preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token);
+    }
+
+    /**
+     * Splits a string into tokens in blocks of words which can be encoded quickly.
+     *
+     * @return string[]
+     */
+    protected function getEncodableWordTokens(string $string): array
+    {
+        $tokens = [];
+        $encodedToken = '';
+        // Split at all whitespace boundaries
+        foreach (preg_split('~(?=[\t ])~', $string) as $token) {
+            if ($this->tokenNeedsEncoding($token)) {
+                $encodedToken .= $token;
+            } else {
+                if (\strlen($encodedToken) > 0) {
+                    $tokens[] = $encodedToken;
+                    $encodedToken = '';
+                }
+                $tokens[] = $token;
+            }
+        }
+        if (\strlen($encodedToken)) {
+            $tokens[] = $encodedToken;
+        }
+
+        return $tokens;
+    }
+
+    /**
+     * Get a token as an encoded word for safe insertion into headers.
+     */
+    protected function getTokenAsEncodedWord(string $token, int $firstLineOffset = 0): string
+    {
+        if (null === self::$encoder) {
+            self::$encoder = new QpMimeHeaderEncoder();
+        }
+
+        // Adjust $firstLineOffset to account for space needed for syntax
+        $charsetDecl = $this->charset;
+        if (null !== $this->lang) {
+            $charsetDecl .= '*'.$this->lang;
+        }
+        $encodingWrapperLength = \strlen('=?'.$charsetDecl.'?'.self::$encoder->getName().'??=');
+
+        if ($firstLineOffset >= 75) {
+            //Does this logic need to be here?
+            $firstLineOffset = 0;
+        }
+
+        $encodedTextLines = explode("\r\n",
+            self::$encoder->encodeString($token, $this->charset, $firstLineOffset, 75 - $encodingWrapperLength)
+        );
+
+        if ('iso-2022-jp' !== strtolower($this->charset)) {
+            // special encoding for iso-2022-jp using mb_encode_mimeheader
+            foreach ($encodedTextLines as $lineNum => $line) {
+                $encodedTextLines[$lineNum] = '=?'.$charsetDecl.'?'.self::$encoder->getName().'?'.$line.'?=';
+            }
+        }
+
+        return implode("\r\n ", $encodedTextLines);
+    }
+
+    /**
+     * Generates tokens from the given string which include CRLF as individual tokens.
+     *
+     * @return string[]
+     */
+    protected function generateTokenLines(string $token): array
+    {
+        return preg_split('~(\r\n)~', $token, -1, PREG_SPLIT_DELIM_CAPTURE);
+    }
+
+    /**
+     * Generate a list of all tokens in the final header.
+     */
+    protected function toTokens(string $string = null): array
+    {
+        if (null === $string) {
+            $string = $this->getBodyAsString();
+        }
+
+        $tokens = [];
+        // Generate atoms; split at all invisible boundaries followed by WSP
+        foreach (preg_split('~(?=[ \t])~', $string) as $token) {
+            $newTokens = $this->generateTokenLines($token);
+            foreach ($newTokens as $newToken) {
+                $tokens[] = $newToken;
+            }
+        }
+
+        return $tokens;
+    }
+
+    /**
+     * Takes an array of tokens which appear in the header and turns them into
+     * an RFC 2822 compliant string, adding FWSP where needed.
+     *
+     * @param string[] $tokens
+     */
+    private function tokensToString(array $tokens): string
+    {
+        $lineCount = 0;
+        $headerLines = [];
+        $headerLines[] = $this->name.': ';
+        $currentLine = &$headerLines[$lineCount++];
+
+        // Build all tokens back into compliant header
+        foreach ($tokens as $i => $token) {
+            // Line longer than specified maximum or token was just a new line
+            if (("\r\n" === $token) ||
+                ($i > 0 && \strlen($currentLine.$token) > $this->lineLength)
+                && 0 < \strlen($currentLine)) {
+                $headerLines[] = '';
+                $currentLine = &$headerLines[$lineCount++];
+            }
+
+            // Append token to the line
+            if ("\r\n" !== $token) {
+                $currentLine .= $token;
+            }
+        }
+
+        // Implode with FWS (RFC 2822, 2.2.3)
+        return implode("\r\n", $headerLines);
+    }
+}
diff --git a/vendor/symfony/mime/Header/DateHeader.php b/vendor/symfony/mime/Header/DateHeader.php
new file mode 100644
index 0000000..1e1a979
--- /dev/null
+++ b/vendor/symfony/mime/Header/DateHeader.php
@@ -0,0 +1,71 @@
+<?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\Mime\Header;
+
+/**
+ * A Date MIME Header.
+ *
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+final class DateHeader extends AbstractHeader
+{
+    private $dateTime;
+
+    public function __construct(string $name, \DateTimeInterface $date)
+    {
+        parent::__construct($name);
+
+        $this->setDateTime($date);
+    }
+
+    /**
+     * @param \DateTimeInterface $body
+     */
+    public function setBody($body)
+    {
+        $this->setDateTime($body);
+    }
+
+    /**
+     * @return \DateTimeImmutable
+     */
+    public function getBody()
+    {
+        return $this->getDateTime();
+    }
+
+    public function getDateTime(): \DateTimeImmutable
+    {
+        return $this->dateTime;
+    }
+
+    /**
+     * Set the date-time of the Date in this Header.
+     *
+     * If a DateTime instance is provided, it is converted to DateTimeImmutable.
+     */
+    public function setDateTime(\DateTimeInterface $dateTime)
+    {
+        if ($dateTime instanceof \DateTime) {
+            $immutable = new \DateTimeImmutable('@'.$dateTime->getTimestamp());
+            $dateTime = $immutable->setTimezone($dateTime->getTimezone());
+        }
+        $this->dateTime = $dateTime;
+    }
+
+    public function getBodyAsString(): string
+    {
+        return $this->dateTime->format(\DateTime::RFC2822);
+    }
+}
diff --git a/vendor/symfony/mime/Header/HeaderInterface.php b/vendor/symfony/mime/Header/HeaderInterface.php
new file mode 100644
index 0000000..b0638bc
--- /dev/null
+++ b/vendor/symfony/mime/Header/HeaderInterface.php
@@ -0,0 +1,67 @@
+<?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\Mime\Header;
+
+/**
+ * A MIME Header.
+ *
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+interface HeaderInterface
+{
+    /**
+     * Sets the body.
+     *
+     * The type depends on the Header concrete class.
+     *
+     * @param mixed $body
+     */
+    public function setBody($body);
+
+    /**
+     * Gets the body.
+     *
+     * The return type depends on the Header concrete class.
+     *
+     * @return mixed
+     */
+    public function getBody();
+
+    public function setCharset(string $charset);
+
+    public function getCharset(): ?string;
+
+    public function setLanguage(string $lang);
+
+    public function getLanguage(): ?string;
+
+    public function getName(): string;
+
+    public function setMaxLineLength(int $lineLength);
+
+    public function getMaxLineLength(): int;
+
+    /**
+     * Gets this Header rendered as a compliant string.
+     */
+    public function toString(): string;
+
+    /**
+     * Gets the header's body, prepared for folding into a final header value.
+     *
+     * This is not necessarily RFC 2822 compliant since folding white space is
+     * not added at this stage (see {@link toString()} for that).
+     */
+    public function getBodyAsString(): string;
+}
diff --git a/vendor/symfony/mime/Header/Headers.php b/vendor/symfony/mime/Header/Headers.php
new file mode 100644
index 0000000..57f02b2
--- /dev/null
+++ b/vendor/symfony/mime/Header/Headers.php
@@ -0,0 +1,285 @@
+<?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\Mime\Header;
+
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Exception\LogicException;
+use Symfony\Component\Mime\NamedAddress;
+
+/**
+ * A collection of headers.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+final class Headers
+{
+    private static $uniqueHeaders = [
+        'date', 'from', 'sender', 'reply-to', 'to', 'cc', 'bcc',
+        'message-id', 'in-reply-to', 'references', 'subject',
+    ];
+
+    private $headers = [];
+    private $lineLength = 76;
+
+    public function __construct(HeaderInterface ...$headers)
+    {
+        foreach ($headers as $header) {
+            $this->add($header);
+        }
+    }
+
+    public function __clone()
+    {
+        foreach ($this->headers as $name => $collection) {
+            foreach ($collection as $i => $header) {
+                $this->headers[$name][$i] = clone $header;
+            }
+        }
+    }
+
+    public function setMaxLineLength(int $lineLength)
+    {
+        $this->lineLength = $lineLength;
+        foreach ($this->all() as $header) {
+            $header->setMaxLineLength($lineLength);
+        }
+    }
+
+    public function getMaxLineLength(): int
+    {
+        return $this->lineLength;
+    }
+
+    /**
+     * @param (NamedAddress|Address|string)[] $addresses
+     *
+     * @return $this
+     */
+    public function addMailboxListHeader(string $name, array $addresses)
+    {
+        return $this->add(new MailboxListHeader($name, Address::createArray($addresses)));
+    }
+
+    /**
+     * @param NamedAddress|Address|string $address
+     *
+     * @return $this
+     */
+    public function addMailboxHeader(string $name, $address)
+    {
+        return $this->add(new MailboxHeader($name, Address::create($address)));
+    }
+
+    /**
+     * @param string|array $ids
+     *
+     * @return $this
+     */
+    public function addIdHeader(string $name, $ids)
+    {
+        return $this->add(new IdentificationHeader($name, $ids));
+    }
+
+    /**
+     * @param Address|string $path
+     *
+     * @return $this
+     */
+    public function addPathHeader(string $name, $path)
+    {
+        return $this->add(new PathHeader($name, $path instanceof Address ? $path : new Address($path)));
+    }
+
+    /**
+     * @return $this
+     */
+    public function addDateHeader(string $name, \DateTimeInterface $dateTime)
+    {
+        return $this->add(new DateHeader($name, $dateTime));
+    }
+
+    /**
+     * @return $this
+     */
+    public function addTextHeader(string $name, string $value)
+    {
+        return $this->add(new UnstructuredHeader($name, $value));
+    }
+
+    /**
+     * @return $this
+     */
+    public function addParameterizedHeader(string $name, string $value, array $params = [])
+    {
+        return $this->add(new ParameterizedHeader($name, $value, $params));
+    }
+
+    public function has(string $name): bool
+    {
+        return isset($this->headers[strtolower($name)]);
+    }
+
+    /**
+     * @return $this
+     */
+    public function add(HeaderInterface $header)
+    {
+        static $map = [
+            'date' => DateHeader::class,
+            'from' => MailboxListHeader::class,
+            'sender' => MailboxHeader::class,
+            'reply-to' => MailboxListHeader::class,
+            'to' => MailboxListHeader::class,
+            'cc' => MailboxListHeader::class,
+            'bcc' => MailboxListHeader::class,
+            'message-id' => IdentificationHeader::class,
+            'in-reply-to' => IdentificationHeader::class,
+            'references' => IdentificationHeader::class,
+            'return-path' => PathHeader::class,
+        ];
+
+        $header->setMaxLineLength($this->lineLength);
+        $name = strtolower($header->getName());
+
+        if (isset($map[$name]) && !$header instanceof $map[$name]) {
+            throw new LogicException(sprintf('The "%s" header must be an instance of "%s" (got "%s").', $header->getName(), $map[$name], \get_class($header)));
+        }
+
+        if (\in_array($name, self::$uniqueHeaders, true) && isset($this->headers[$name]) && \count($this->headers[$name]) > 0) {
+            throw new LogicException(sprintf('Impossible to set header "%s" as it\'s already defined and must be unique.', $header->getName()));
+        }
+
+        $this->headers[$name][] = $header;
+
+        return $this;
+    }
+
+    public function get(string $name): ?HeaderInterface
+    {
+        $name = strtolower($name);
+        if (!isset($this->headers[$name])) {
+            return null;
+        }
+
+        $values = array_values($this->headers[$name]);
+
+        return array_shift($values);
+    }
+
+    public function all(string $name = null): iterable
+    {
+        if (null === $name) {
+            foreach ($this->headers as $name => $collection) {
+                foreach ($collection as $header) {
+                    yield $name => $header;
+                }
+            }
+        } elseif (isset($this->headers[strtolower($name)])) {
+            foreach ($this->headers[strtolower($name)] as $header) {
+                yield $header;
+            }
+        }
+    }
+
+    public function getNames(): array
+    {
+        return array_keys($this->headers);
+    }
+
+    public function remove(string $name): void
+    {
+        unset($this->headers[strtolower($name)]);
+    }
+
+    public static function isUniqueHeader(string $name): bool
+    {
+        return \in_array($name, self::$uniqueHeaders, true);
+    }
+
+    public function toString(): string
+    {
+        $string = '';
+        foreach ($this->toArray() as $str) {
+            $string .= $str."\r\n";
+        }
+
+        return $string;
+    }
+
+    public function toArray(): array
+    {
+        $arr = [];
+        foreach ($this->all() as $header) {
+            if ('' !== $header->getBodyAsString()) {
+                $arr[] = $header->toString();
+            }
+        }
+
+        return $arr;
+    }
+
+    /**
+     * @internal
+     */
+    public function getHeaderBody($name)
+    {
+        return $this->has($name) ? $this->get($name)->getBody() : null;
+    }
+
+    /**
+     * @internal
+     */
+    public function setHeaderBody(string $type, string $name, $body): void
+    {
+        if ($this->has($name)) {
+            $this->get($name)->setBody($body);
+        } else {
+            $this->{'add'.$type.'Header'}($name, $body);
+        }
+    }
+
+    /**
+     * @internal
+     */
+    public function getHeaderParameter(string $name, string $parameter): ?string
+    {
+        if (!$this->has($name)) {
+            return null;
+        }
+
+        $header = $this->get($name);
+        if (!$header instanceof ParameterizedHeader) {
+            throw new LogicException(sprintf('Unable to get parameter "%s" on header "%s" as the header is not of class "%s".', $parameter, $name, ParameterizedHeader::class));
+        }
+
+        return $header->getParameter($parameter);
+    }
+
+    /**
+     * @internal
+     */
+    public function setHeaderParameter(string $name, string $parameter, $value): void
+    {
+        if (!$this->has($name)) {
+            throw new LogicException(sprintf('Unable to set parameter "%s" on header "%s" as the header is not defined.', $parameter, $name));
+        }
+
+        $header = $this->get($name);
+        if (!$header instanceof ParameterizedHeader) {
+            throw new LogicException(sprintf('Unable to set parameter "%s" on header "%s" as the header is not of class "%s".', $parameter, $name, ParameterizedHeader::class));
+        }
+
+        $header->setParameter($parameter, $value);
+    }
+}
diff --git a/vendor/symfony/mime/Header/IdentificationHeader.php b/vendor/symfony/mime/Header/IdentificationHeader.php
new file mode 100644
index 0000000..0695b68
--- /dev/null
+++ b/vendor/symfony/mime/Header/IdentificationHeader.php
@@ -0,0 +1,115 @@
+<?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\Mime\Header;
+
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Exception\RfcComplianceException;
+
+/**
+ * An ID MIME Header for something like Message-ID or Content-ID (one or more addresses).
+ *
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+final class IdentificationHeader extends AbstractHeader
+{
+    private $ids = [];
+    private $idsAsAddresses = [];
+
+    /**
+     * @param string|array $ids
+     */
+    public function __construct(string $name, $ids)
+    {
+        parent::__construct($name);
+
+        $this->setId($ids);
+    }
+
+    /**
+     * @param string|array $body a string ID or an array of IDs
+     *
+     * @throws RfcComplianceException
+     */
+    public function setBody($body)
+    {
+        $this->setId($body);
+    }
+
+    /**
+     * @return array
+     */
+    public function getBody()
+    {
+        return $this->getIds();
+    }
+
+    /**
+     * Set the ID used in the value of this header.
+     *
+     * @param string|array $id
+     *
+     * @throws RfcComplianceException
+     */
+    public function setId($id)
+    {
+        $this->setIds(\is_array($id) ? $id : [$id]);
+    }
+
+    /**
+     * Get the ID used in the value of this Header.
+     *
+     * If multiple IDs are set only the first is returned.
+     */
+    public function getId(): ?string
+    {
+        return $this->ids[0] ?? null;
+    }
+
+    /**
+     * Set a collection of IDs to use in the value of this Header.
+     *
+     * @param string[] $ids
+     *
+     * @throws RfcComplianceException
+     */
+    public function setIds(array $ids)
+    {
+        $this->ids = [];
+        $this->idsAsAddresses = [];
+        foreach ($ids as $id) {
+            $this->idsAsAddresses[] = new Address($id);
+            $this->ids[] = $id;
+        }
+    }
+
+    /**
+     * Get the list of IDs used in this Header.
+     *
+     * @return string[]
+     */
+    public function getIds(): array
+    {
+        return $this->ids;
+    }
+
+    public function getBodyAsString(): string
+    {
+        $addrs = [];
+        foreach ($this->idsAsAddresses as $address) {
+            $addrs[] = '<'.$address->toString().'>';
+        }
+
+        return implode(' ', $addrs);
+    }
+}
diff --git a/vendor/symfony/mime/Header/MailboxHeader.php b/vendor/symfony/mime/Header/MailboxHeader.php
new file mode 100644
index 0000000..c664923
--- /dev/null
+++ b/vendor/symfony/mime/Header/MailboxHeader.php
@@ -0,0 +1,90 @@
+<?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\Mime\Header;
+
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Exception\RfcComplianceException;
+use Symfony\Component\Mime\NamedAddress;
+
+/**
+ * A Mailbox MIME Header for something like Sender (one named address).
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+final class MailboxHeader extends AbstractHeader
+{
+    private $address;
+
+    public function __construct(string $name, Address $address)
+    {
+        parent::__construct($name);
+
+        $this->setAddress($address);
+    }
+
+    /**
+     * @param Address $body
+     *
+     * @throws RfcComplianceException
+     */
+    public function setBody($body)
+    {
+        $this->setAddress($body);
+    }
+
+    /**
+     * @throws RfcComplianceException
+     *
+     * @return Address
+     */
+    public function getBody()
+    {
+        return $this->getAddress();
+    }
+
+    /**
+     * @throws RfcComplianceException
+     */
+    public function setAddress(Address $address)
+    {
+        $this->address = $address;
+    }
+
+    public function getAddress(): Address
+    {
+        return $this->address;
+    }
+
+    public function getBodyAsString(): string
+    {
+        $str = $this->address->getEncodedAddress();
+        if ($this->address instanceof NamedAddress && $name = $this->address->getName()) {
+            $str = $this->createPhrase($this, $name, $this->getCharset(), true).' <'.$str.'>';
+        }
+
+        return $str;
+    }
+
+    /**
+     * Redefine the encoding requirements for an address.
+     *
+     * All "specials" must be encoded as the full header value will not be quoted
+     *
+     * @see RFC 2822 3.2.1
+     */
+    protected function tokenNeedsEncoding(string $token): bool
+    {
+        return preg_match('/[()<>\[\]:;@\,."]/', $token) || parent::tokenNeedsEncoding($token);
+    }
+}
diff --git a/vendor/symfony/mime/Header/MailboxListHeader.php b/vendor/symfony/mime/Header/MailboxListHeader.php
new file mode 100644
index 0000000..51af2da
--- /dev/null
+++ b/vendor/symfony/mime/Header/MailboxListHeader.php
@@ -0,0 +1,139 @@
+<?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\Mime\Header;
+
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Exception\RfcComplianceException;
+use Symfony\Component\Mime\NamedAddress;
+
+/**
+ * A Mailbox list MIME Header for something like From, To, Cc, and Bcc (one or more named addresses).
+ *
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+final class MailboxListHeader extends AbstractHeader
+{
+    private $addresses = [];
+
+    /**
+     * @param (NamedAddress|Address)[] $addresses
+     */
+    public function __construct(string $name, array $addresses)
+    {
+        parent::__construct($name);
+
+        $this->setAddresses($addresses);
+    }
+
+    /**
+     * @param (NamedAddress|Address)[] $body
+     *
+     * @throws RfcComplianceException
+     */
+    public function setBody($body)
+    {
+        $this->setAddresses($body);
+    }
+
+    /**
+     * @throws RfcComplianceException
+     *
+     * @return (NamedAddress|Address)[]
+     */
+    public function getBody()
+    {
+        return $this->getAddresses();
+    }
+
+    /**
+     * Sets a list of addresses to be shown in this Header.
+     *
+     * @param (NamedAddress|Address)[] $addresses
+     *
+     * @throws RfcComplianceException
+     */
+    public function setAddresses(array $addresses)
+    {
+        $this->addresses = [];
+        $this->addAddresses($addresses);
+    }
+
+    /**
+     * Sets a list of addresses to be shown in this Header.
+     *
+     * @param (NamedAddress|Address)[] $addresses
+     *
+     * @throws RfcComplianceException
+     */
+    public function addAddresses(array $addresses)
+    {
+        foreach ($addresses as $address) {
+            $this->addAddress($address);
+        }
+    }
+
+    /**
+     * @throws RfcComplianceException
+     */
+    public function addAddress(Address $address)
+    {
+        $this->addresses[] = $address;
+    }
+
+    /**
+     * @return (NamedAddress|Address)[]
+     */
+    public function getAddresses(): array
+    {
+        return $this->addresses;
+    }
+
+    /**
+     * Gets the full mailbox list of this Header as an array of valid RFC 2822 strings.
+     *
+     * @throws RfcComplianceException
+     *
+     * @return string[]
+     */
+    public function getAddressStrings(): array
+    {
+        $strings = [];
+        foreach ($this->addresses as $address) {
+            $str = $address->getEncodedAddress();
+            if ($address instanceof NamedAddress && $name = $address->getName()) {
+                $str = $this->createPhrase($this, $name, $this->getCharset(), empty($strings)).' <'.$str.'>';
+            }
+            $strings[] = $str;
+        }
+
+        return $strings;
+    }
+
+    public function getBodyAsString(): string
+    {
+        return implode(', ', $this->getAddressStrings());
+    }
+
+    /**
+     * Redefine the encoding requirements for addresses.
+     *
+     * All "specials" must be encoded as the full header value will not be quoted
+     *
+     * @see RFC 2822 3.2.1
+     */
+    protected function tokenNeedsEncoding(string $token): bool
+    {
+        return preg_match('/[()<>\[\]:;@\,."]/', $token) || parent::tokenNeedsEncoding($token);
+    }
+}
diff --git a/vendor/symfony/mime/Header/ParameterizedHeader.php b/vendor/symfony/mime/Header/ParameterizedHeader.php
new file mode 100644
index 0000000..811a969
--- /dev/null
+++ b/vendor/symfony/mime/Header/ParameterizedHeader.php
@@ -0,0 +1,176 @@
+<?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\Mime\Header;
+
+use Symfony\Component\Mime\Encoder\Rfc2231Encoder;
+
+/**
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+final class ParameterizedHeader extends UnstructuredHeader
+{
+    /**
+     * RFC 2231's definition of a token.
+     *
+     * @var string
+     */
+    const TOKEN_REGEX = '(?:[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+)';
+
+    private $encoder;
+    private $parameters = [];
+
+    public function __construct(string $name, string $value, array $parameters = [])
+    {
+        parent::__construct($name, $value);
+
+        foreach ($parameters as $k => $v) {
+            $this->setParameter($k, $v);
+        }
+
+        if ('content-type' !== strtolower($name)) {
+            $this->encoder = new Rfc2231Encoder();
+        }
+    }
+
+    public function setParameter(string $parameter, ?string $value)
+    {
+        $this->setParameters(array_merge($this->getParameters(), [$parameter => $value]));
+    }
+
+    public function getParameter(string $parameter): string
+    {
+        return $this->getParameters()[$parameter] ?? '';
+    }
+
+    /**
+     * @param string[] $parameters
+     */
+    public function setParameters(array $parameters)
+    {
+        $this->parameters = $parameters;
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getParameters(): array
+    {
+        return $this->parameters;
+    }
+
+    public function getBodyAsString(): string
+    {
+        $body = parent::getBodyAsString();
+        foreach ($this->parameters as $name => $value) {
+            if (null !== $value) {
+                $body .= '; '.$this->createParameter($name, $value);
+            }
+        }
+
+        return $body;
+    }
+
+    /**
+     * Generate a list of all tokens in the final header.
+     *
+     * This doesn't need to be overridden in theory, but it is for implementation
+     * reasons to prevent potential breakage of attributes.
+     */
+    protected function toTokens(string $string = null): array
+    {
+        $tokens = parent::toTokens(parent::getBodyAsString());
+
+        // Try creating any parameters
+        foreach ($this->parameters as $name => $value) {
+            if (null !== $value) {
+                // Add the semi-colon separator
+                $tokens[\count($tokens) - 1] .= ';';
+                $tokens = array_merge($tokens, $this->generateTokenLines(' '.$this->createParameter($name, $value)));
+            }
+        }
+
+        return $tokens;
+    }
+
+    /**
+     * Render a RFC 2047 compliant header parameter from the $name and $value.
+     */
+    private function createParameter(string $name, string $value): string
+    {
+        $origValue = $value;
+
+        $encoded = false;
+        // Allow room for parameter name, indices, "=" and DQUOTEs
+        $maxValueLength = $this->getMaxLineLength() - \strlen($name.'=*N"";') - 1;
+        $firstLineOffset = 0;
+
+        // If it's not already a valid parameter value...
+        if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) {
+            // TODO: text, or something else??
+            // ... and it's not ascii
+            if (!preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $value)) {
+                $encoded = true;
+                // Allow space for the indices, charset and language
+                $maxValueLength = $this->getMaxLineLength() - \strlen($name.'*N*="";') - 1;
+                $firstLineOffset = \strlen($this->getCharset()."'".$this->getLanguage()."'");
+            }
+        }
+
+        // Encode if we need to
+        if ($encoded || \strlen($value) > $maxValueLength) {
+            if (null !== $this->encoder) {
+                $value = $this->encoder->encodeString($origValue, $this->getCharset(), $firstLineOffset, $maxValueLength);
+            } else {
+                // We have to go against RFC 2183/2231 in some areas for interoperability
+                $value = $this->getTokenAsEncodedWord($origValue);
+                $encoded = false;
+            }
+        }
+
+        $valueLines = $this->encoder ? explode("\r\n", $value) : [$value];
+
+        // Need to add indices
+        if (\count($valueLines) > 1) {
+            $paramLines = [];
+            foreach ($valueLines as $i => $line) {
+                $paramLines[] = $name.'*'.$i.$this->getEndOfParameterValue($line, true, 0 === $i);
+            }
+
+            return implode(";\r\n ", $paramLines);
+        } else {
+            return $name.$this->getEndOfParameterValue($valueLines[0], $encoded, true);
+        }
+    }
+
+    /**
+     * Returns the parameter value from the "=" and beyond.
+     *
+     * @param string $value to append
+     */
+    private function getEndOfParameterValue(string $value, bool $encoded = false, bool $firstLine = false): string
+    {
+        if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) {
+            $value = '"'.$value.'"';
+        }
+        $prepend = '=';
+        if ($encoded) {
+            $prepend = '*=';
+            if ($firstLine) {
+                $prepend = '*='.$this->getCharset()."'".$this->getLanguage()."'";
+            }
+        }
+
+        return $prepend.$value;
+    }
+}
diff --git a/vendor/symfony/mime/Header/PathHeader.php b/vendor/symfony/mime/Header/PathHeader.php
new file mode 100644
index 0000000..6d16500
--- /dev/null
+++ b/vendor/symfony/mime/Header/PathHeader.php
@@ -0,0 +1,67 @@
+<?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\Mime\Header;
+
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Exception\RfcComplianceException;
+
+/**
+ * A Path Header, such a Return-Path (one address).
+ *
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+final class PathHeader extends AbstractHeader
+{
+    private $address;
+
+    public function __construct(string $name, Address $address)
+    {
+        parent::__construct($name);
+
+        $this->setAddress($address);
+    }
+
+    /**
+     * @param Address $body
+     *
+     * @throws RfcComplianceException
+     */
+    public function setBody($body)
+    {
+        $this->setAddress($body);
+    }
+
+    /**
+     * @return Address
+     */
+    public function getBody()
+    {
+        return $this->getAddress();
+    }
+
+    public function setAddress(Address $address)
+    {
+        $this->address = $address;
+    }
+
+    public function getAddress(): Address
+    {
+        return $this->address;
+    }
+
+    public function getBodyAsString(): string
+    {
+        return '<'.$this->address->toString().'>';
+    }
+}
diff --git a/vendor/symfony/mime/Header/UnstructuredHeader.php b/vendor/symfony/mime/Header/UnstructuredHeader.php
new file mode 100644
index 0000000..afb9615
--- /dev/null
+++ b/vendor/symfony/mime/Header/UnstructuredHeader.php
@@ -0,0 +1,71 @@
+<?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\Mime\Header;
+
+/**
+ * A Simple MIME Header.
+ *
+ * @author Chris Corbyn
+ *
+ * @experimental in 4.3
+ */
+class UnstructuredHeader extends AbstractHeader
+{
+    private $value;
+
+    public function __construct(string $name, string $value)
+    {
+        parent::__construct($name);
+
+        $this->setValue($value);
+    }
+
+    /**
+     * @param string $body
+     */
+    public function setBody($body)
+    {
+        $this->setValue($body);
+    }
+
+    /**
+     * @return string
+     */
+    public function getBody()
+    {
+        return $this->getValue();
+    }
+
+    /**
+     * Get the (unencoded) value of this header.
+     */
+    public function getValue(): string
+    {
+        return $this->value;
+    }
+
+    /**
+     * Set the (unencoded) value of this header.
+     */
+    public function setValue(string $value)
+    {
+        $this->value = $value;
+    }
+
+    /**
+     * Get the value of this header prepared for rendering.
+     */
+    public function getBodyAsString(): string
+    {
+        return $this->encodeWords($this, $this->value);
+    }
+}
diff --git a/vendor/symfony/mime/LICENSE b/vendor/symfony/mime/LICENSE
new file mode 100644
index 0000000..9a9a61b
--- /dev/null
+++ b/vendor/symfony/mime/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2010-2019 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.
diff --git a/vendor/symfony/mime/Message.php b/vendor/symfony/mime/Message.php
new file mode 100644
index 0000000..4d6af1c
--- /dev/null
+++ b/vendor/symfony/mime/Message.php
@@ -0,0 +1,142 @@
+<?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\Mime;
+
+use Symfony\Component\Mime\Exception\LogicException;
+use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\Part\AbstractPart;
+use Symfony\Component\Mime\Part\TextPart;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+class Message extends RawMessage
+{
+    private $headers;
+    private $body;
+
+    public function __construct(Headers $headers = null, AbstractPart $body = null)
+    {
+        $this->headers = $headers ? clone $headers : new Headers();
+        $this->body = $body;
+    }
+
+    public function __clone()
+    {
+        if (null !== $this->headers) {
+            $this->headers = clone $this->headers;
+        }
+
+        if (null !== $this->body) {
+            $this->body = clone $this->body;
+        }
+    }
+
+    /**
+     * @return $this
+     */
+    public function setBody(AbstractPart $body = null)
+    {
+        $this->body = $body;
+
+        return $this;
+    }
+
+    public function getBody(): ?AbstractPart
+    {
+        return $this->body;
+    }
+
+    /**
+     * @return $this
+     */
+    public function setHeaders(Headers $headers)
+    {
+        $this->headers = $headers;
+
+        return $this;
+    }
+
+    public function getHeaders(): Headers
+    {
+        return $this->headers;
+    }
+
+    public function getPreparedHeaders(): Headers
+    {
+        $headers = clone $this->headers;
+
+        if (!$headers->has('From')) {
+            throw new LogicException('An email must have a "From" header.');
+        }
+
+        $headers->addTextHeader('MIME-Version', '1.0');
+
+        if (!$headers->has('Date')) {
+            $headers->addDateHeader('Date', new \DateTimeImmutable());
+        }
+
+        // determine the "real" sender
+        $senders = $headers->get('From')->getAddresses();
+        $sender = $senders[0];
+        if ($headers->has('Sender')) {
+            $sender = $headers->get('Sender')->getAddress();
+        } elseif (\count($senders) > 1) {
+            $headers->addMailboxHeader('Sender', $sender);
+        }
+
+        if (!$headers->has('Message-ID')) {
+            $headers->addIdHeader('Message-ID', $this->generateMessageId($sender->getAddress()));
+        }
+
+        // remove the Bcc field which should NOT be part of the sent message
+        $headers->remove('Bcc');
+
+        return $headers;
+    }
+
+    public function toString(): string
+    {
+        if (null === $body = $this->getBody()) {
+            $body = new TextPart('');
+        }
+
+        return $this->getPreparedHeaders()->toString().$body->toString();
+    }
+
+    public function toIterable(): iterable
+    {
+        if (null === $body = $this->getBody()) {
+            $body = new TextPart('');
+        }
+
+        yield $this->getPreparedHeaders()->toString();
+        yield from $body->toIterable();
+    }
+
+    private function generateMessageId(string $email): string
+    {
+        return bin2hex(random_bytes(16)).strstr($email, '@');
+    }
+
+    public function __serialize(): array
+    {
+        return [$this->headers, $this->body];
+    }
+
+    public function __unserialize(array $data): void
+    {
+        [$this->headers, $this->body] = $data;
+    }
+}
diff --git a/vendor/symfony/mime/MessageConverter.php b/vendor/symfony/mime/MessageConverter.php
new file mode 100644
index 0000000..b139f1c
--- /dev/null
+++ b/vendor/symfony/mime/MessageConverter.php
@@ -0,0 +1,127 @@
+<?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\Mime;
+
+use Symfony\Component\Mime\Exception\RuntimeException;
+use Symfony\Component\Mime\Part\DataPart;
+use Symfony\Component\Mime\Part\Multipart\AlternativePart;
+use Symfony\Component\Mime\Part\Multipart\MixedPart;
+use Symfony\Component\Mime\Part\Multipart\RelatedPart;
+use Symfony\Component\Mime\Part\TextPart;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+final class MessageConverter
+{
+    /**
+     * @throws RuntimeException when unable to convert the message to an email
+     */
+    public static function toEmail(Message $message): Email
+    {
+        if ($message instanceof Email) {
+            return $message;
+        }
+
+        // try to convert to a "simple" Email instance
+        $body = $message->getBody();
+        if ($body instanceof TextPart) {
+            return self::createEmailFromTextPart($message, $body);
+        }
+
+        if ($body instanceof AlternativePart) {
+            return self::createEmailFromAlternativePart($message, $body);
+        }
+
+        if ($body instanceof RelatedPart) {
+            return self::createEmailFromRelatedPart($message, $body);
+        }
+
+        if ($body instanceof MixedPart) {
+            $parts = $body->getParts();
+            if ($parts[0] instanceof RelatedPart) {
+                $email = self::createEmailFromRelatedPart($message, $parts[0]);
+            } elseif ($parts[0] instanceof AlternativePart) {
+                $email = self::createEmailFromAlternativePart($message, $parts[0]);
+            } elseif ($parts[0] instanceof TextPart) {
+                $email = self::createEmailFromTextPart($message, $parts[0]);
+            } else {
+                throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', \get_class($message)));
+            }
+
+            return self::attachParts($email, \array_slice($parts, 1));
+        }
+
+        throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', \get_class($message)));
+    }
+
+    private static function createEmailFromTextPart(Message $message, TextPart $part): Email
+    {
+        if ('text' === $part->getMediaType() && 'plain' === $part->getMediaSubtype()) {
+            return (new Email(clone $message->getHeaders()))->text($part->getBody(), $part->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8');
+        }
+        if ('text' === $part->getMediaType() && 'html' === $part->getMediaSubtype()) {
+            return (new Email(clone $message->getHeaders()))->html($part->getBody(), $part->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8');
+        }
+
+        throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', \get_class($message)));
+    }
+
+    private static function createEmailFromAlternativePart(Message $message, AlternativePart $part): Email
+    {
+        $parts = $part->getParts();
+        if (
+            2 === \count($parts) &&
+            $parts[0] instanceof TextPart && 'text' === $parts[0]->getMediaType() && 'plain' === $parts[0]->getMediaSubtype() &&
+            $parts[1] instanceof TextPart && 'text' === $parts[1]->getMediaType() && 'html' === $parts[1]->getMediaSubtype()
+         ) {
+            return (new Email(clone $message->getHeaders()))
+                ->text($parts[0]->getBody(), $parts[0]->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8')
+                ->html($parts[1]->getBody(), $parts[1]->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8')
+            ;
+        }
+
+        throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', \get_class($message)));
+    }
+
+    private static function createEmailFromRelatedPart(Message $message, RelatedPart $part): Email
+    {
+        $parts = $part->getParts();
+        if ($parts[0] instanceof AlternativePart) {
+            $email = self::createEmailFromAlternativePart($message, $parts[0]);
+        } elseif ($parts[0] instanceof TextPart) {
+            $email = self::createEmailFromTextPart($message, $parts[0]);
+        } else {
+            throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', \get_class($message)));
+        }
+
+        return self::attachParts($email, \array_slice($parts, 1));
+    }
+
+    private static function attachParts(Email $email, array $parts): Email
+    {
+        foreach ($parts as $part) {
+            if (!$part instanceof DataPart) {
+                throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', \get_class($email)));
+            }
+
+            $headers = $part->getPreparedHeaders();
+            $method = 'inline' === $headers->getHeaderBody('Content-Disposition') ? 'embed' : 'attach';
+            $name = $headers->getHeaderParameter('Content-Disposition', 'filename');
+            $email->$method($part->getBody(), $name, $part->getMediaType().'/'.$part->getMediaSubtype());
+        }
+
+        return $email;
+    }
+}
diff --git a/vendor/symfony/mime/MimeTypeGuesserInterface.php b/vendor/symfony/mime/MimeTypeGuesserInterface.php
new file mode 100644
index 0000000..b7d27f5
--- /dev/null
+++ b/vendor/symfony/mime/MimeTypeGuesserInterface.php
@@ -0,0 +1,39 @@
+<?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\Mime;
+
+/**
+ * Guesses the MIME type of a file.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+interface MimeTypeGuesserInterface
+{
+    /**
+     * Returns true if this guesser is supported.
+     */
+    public function isGuesserSupported(): bool;
+
+    /**
+     * Guesses the MIME type of the file with the given path.
+     *
+     * @param string $path The path to the file
+     *
+     * @return string|null The MIME type or null, if none could be guessed
+     *
+     * @throws \LogicException           If the guesser is not supported
+     * @throws \InvalidArgumentException If the file does not exist or is not readable
+     */
+    public function guessMimeType(string $path): ?string;
+}
diff --git a/vendor/symfony/mime/MimeTypes.php b/vendor/symfony/mime/MimeTypes.php
new file mode 100644
index 0000000..02e8fe5
--- /dev/null
+++ b/vendor/symfony/mime/MimeTypes.php
@@ -0,0 +1,3154 @@
+<?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\Mime;
+
+use Symfony\Component\Mime\Exception\LogicException;
+
+/**
+ * Manages MIME types and file extensions.
+ *
+ * For MIME type guessing, you can register custom guessers
+ * by calling the registerGuesser() method.
+ * Custom guessers are always called before any default ones:
+ *
+ *     $guesser = new MimeTypes();
+ *     $guesser->registerGuesser(new MyCustomMimeTypeGuesser());
+ *
+ * If you want to change the order of the default guessers, just re-register your
+ * preferred one as a custom one. The last registered guesser is preferred over
+ * previously registered ones.
+ *
+ * Re-registering a built-in guesser also allows you to configure it:
+ *
+ *     $guesser = new MimeTypes();
+ *     $guesser->registerGuesser(new FileinfoMimeTypeGuesser('/path/to/magic/file'));
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+final class MimeTypes implements MimeTypesInterface
+{
+    private $extensions = [];
+    private $mimeTypes = [];
+
+    /**
+     * @var MimeTypeGuesserInterface[]
+     */
+    private $guessers = [];
+    private static $default;
+
+    public function __construct(array $map = [])
+    {
+        foreach ($map as $mimeType => $extensions) {
+            $this->extensions[$mimeType] = $extensions;
+
+            foreach ($extensions as $extension) {
+                $this->mimeTypes[$extension] = $mimeType;
+            }
+        }
+        $this->registerGuesser(new FileBinaryMimeTypeGuesser());
+        $this->registerGuesser(new FileinfoMimeTypeGuesser());
+    }
+
+    public static function setDefault(self $default)
+    {
+        self::$default = $default;
+    }
+
+    public static function getDefault(): self
+    {
+        return self::$default ?? self::$default = new self();
+    }
+
+    /**
+     * Registers a MIME type guesser.
+     *
+     * The last registered guesser has precedence over the other ones.
+     */
+    public function registerGuesser(MimeTypeGuesserInterface $guesser)
+    {
+        array_unshift($this->guessers, $guesser);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getExtensions(string $mimeType): array
+    {
+        if ($this->extensions) {
+            $extensions = $this->extensions[$mimeType] ?? $this->extensions[$lcMimeType = strtolower($mimeType)] ?? null;
+        }
+
+        return $extensions ?? self::$map[$mimeType] ?? self::$map[$lcMimeType ?? strtolower($mimeType)] ?? [];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMimeTypes(string $ext): array
+    {
+        if ($this->mimeTypes) {
+            $mimeTypes = $this->mimeTypes[$ext] ?? $this->mimeTypes[$lcExt = strtolower($ext)] ?? null;
+        }
+
+        return $mimeTypes ?? self::$reverseMap[$ext] ?? self::$reverseMap[$lcExt ?? strtolower($ext)] ?? [];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isGuesserSupported(): bool
+    {
+        foreach ($this->guessers as $guesser) {
+            if ($guesser->isGuesserSupported()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * The file is passed to each registered MIME type guesser in reverse order
+     * of their registration (last registered is queried first). Once a guesser
+     * returns a value that is not null, this method terminates and returns the
+     * value.
+     */
+    public function guessMimeType(string $path): ?string
+    {
+        foreach ($this->guessers as $guesser) {
+            if (!$guesser->isGuesserSupported()) {
+                continue;
+            }
+
+            if (null !== $mimeType = $guesser->guessMimeType($path)) {
+                return $mimeType;
+            }
+        }
+
+        if (!$this->isGuesserSupported()) {
+            throw new LogicException('Unable to guess the MIME type as no guessers are available (have you enable the php_fileinfo extension?).');
+        }
+
+        return null;
+    }
+
+    /**
+     * A map of MIME types and their default extensions.
+     *
+     * Updated from upstream on 2019-01-16
+     *
+     * @see Resources/bin/update_mime_types.php
+     */
+    private static $map = [
+        'application/acrobat' => ['pdf'],
+        'application/andrew-inset' => ['ez'],
+        'application/annodex' => ['anx'],
+        'application/applixware' => ['aw'],
+        'application/atom+xml' => ['atom'],
+        'application/atomcat+xml' => ['atomcat'],
+        'application/atomsvc+xml' => ['atomsvc'],
+        'application/ccxml+xml' => ['ccxml'],
+        'application/cdmi-capability' => ['cdmia'],
+        'application/cdmi-container' => ['cdmic'],
+        'application/cdmi-domain' => ['cdmid'],
+        'application/cdmi-object' => ['cdmio'],
+        'application/cdmi-queue' => ['cdmiq'],
+        'application/cdr' => ['cdr'],
+        'application/coreldraw' => ['cdr'],
+        'application/cu-seeme' => ['cu'],
+        'application/davmount+xml' => ['davmount'],
+        'application/dbase' => ['dbf'],
+        'application/dbf' => ['dbf'],
+        'application/dicom' => ['dcm'],
+        'application/docbook+xml' => ['dbk', 'docbook'],
+        'application/dssc+der' => ['dssc'],
+        'application/dssc+xml' => ['xdssc'],
+        'application/ecmascript' => ['ecma', 'es'],
+        'application/emf' => ['emf'],
+        'application/emma+xml' => ['emma'],
+        'application/epub+zip' => ['epub'],
+        'application/exi' => ['exi'],
+        'application/font-tdpfr' => ['pfr'],
+        'application/font-woff' => ['woff'],
+        'application/futuresplash' => ['swf', 'spl'],
+        'application/geo+json' => ['geojson', 'geo.json'],
+        'application/gml+xml' => ['gml'],
+        'application/gnunet-directory' => ['gnd'],
+        'application/gpx' => ['gpx'],
+        'application/gpx+xml' => ['gpx'],
+        'application/gxf' => ['gxf'],
+        'application/gzip' => ['gz'],
+        'application/hyperstudio' => ['stk'],
+        'application/ico' => ['ico'],
+        'application/ics' => ['vcs', 'ics'],
+        'application/illustrator' => ['ai'],
+        'application/inkml+xml' => ['ink', 'inkml'],
+        'application/ipfix' => ['ipfix'],
+        'application/java' => ['class'],
+        'application/java-archive' => ['jar'],
+        'application/java-byte-code' => ['class'],
+        'application/java-serialized-object' => ['ser'],
+        'application/java-vm' => ['class'],
+        'application/javascript' => ['js', 'jsm', 'mjs'],
+        'application/jrd+json' => ['jrd'],
+        'application/json' => ['json'],
+        'application/json-patch+json' => ['json-patch'],
+        'application/jsonml+json' => ['jsonml'],
+        'application/ld+json' => ['jsonld'],
+        'application/lost+xml' => ['lostxml'],
+        'application/lotus123' => ['123', 'wk1', 'wk3', 'wk4', 'wks'],
+        'application/m3u' => ['m3u', 'm3u8', 'vlc'],
+        'application/mac-binhex40' => ['hqx'],
+        'application/mac-compactpro' => ['cpt'],
+        'application/mads+xml' => ['mads'],
+        'application/marc' => ['mrc'],
+        'application/marcxml+xml' => ['mrcx'],
+        'application/mathematica' => ['ma', 'nb', 'mb'],
+        'application/mathml+xml' => ['mathml', 'mml'],
+        'application/mbox' => ['mbox'],
+        'application/mdb' => ['mdb'],
+        'application/mediaservercontrol+xml' => ['mscml'],
+        'application/metalink+xml' => ['metalink'],
+        'application/metalink4+xml' => ['meta4'],
+        'application/mets+xml' => ['mets'],
+        'application/mods+xml' => ['mods'],
+        'application/mp21' => ['m21', 'mp21'],
+        'application/mp4' => ['mp4s'],
+        'application/ms-tnef' => ['tnef', 'tnf'],
+        'application/msaccess' => ['mdb'],
+        'application/msexcel' => ['xls', 'xlc', 'xll', 'xlm', 'xlw', 'xla', 'xlt', 'xld'],
+        'application/mspowerpoint' => ['ppz', 'ppt', 'pps', 'pot'],
+        'application/msword' => ['doc', 'dot'],
+        'application/msword-template' => ['dot'],
+        'application/mxf' => ['mxf'],
+        'application/nappdf' => ['pdf'],
+        'application/octet-stream' => ['bin', 'dms', 'lrf', 'mar', 'so', 'dist', 'distz', 'pkg', 'bpk', 'dump', 'elc', 'deploy'],
+        'application/oda' => ['oda'],
+        'application/oebps-package+xml' => ['opf'],
+        'application/ogg' => ['ogx'],
+        'application/omdoc+xml' => ['omdoc'],
+        'application/onenote' => ['onetoc', 'onetoc2', 'onetmp', 'onepkg'],
+        'application/owl+xml' => ['owx'],
+        'application/oxps' => ['oxps', 'xps'],
+        'application/patch-ops-error+xml' => ['xer'],
+        'application/pcap' => ['pcap', 'cap', 'dmp'],
+        'application/pdf' => ['pdf'],
+        'application/pgp' => ['pgp', 'gpg', 'asc'],
+        'application/pgp-encrypted' => ['pgp', 'gpg', 'asc'],
+        'application/pgp-keys' => ['skr', 'pkr', 'asc', 'pgp', 'gpg'],
+        'application/pgp-signature' => ['asc', 'sig', 'pgp', 'gpg'],
+        'application/photoshop' => ['psd'],
+        'application/pics-rules' => ['prf'],
+        'application/pkcs10' => ['p10'],
+        'application/pkcs12' => ['p12', 'pfx'],
+        'application/pkcs7-mime' => ['p7m', 'p7c'],
+        'application/pkcs7-signature' => ['p7s'],
+        'application/pkcs8' => ['p8'],
+        'application/pkcs8-encrypted' => ['p8e'],
+        'application/pkix-attr-cert' => ['ac'],
+        'application/pkix-cert' => ['cer'],
+        'application/pkix-crl' => ['crl'],
+        'application/pkix-pkipath' => ['pkipath'],
+        'application/pkixcmp' => ['pki'],
+        'application/pls' => ['pls'],
+        'application/pls+xml' => ['pls'],
+        'application/postscript' => ['ai', 'eps', 'ps'],
+        'application/powerpoint' => ['ppz', 'ppt', 'pps', 'pot'],
+        'application/prs.cww' => ['cww'],
+        'application/pskc+xml' => ['pskcxml'],
+        'application/ram' => ['ram'],
+        'application/raml+yaml' => ['raml'],
+        'application/rdf+xml' => ['rdf', 'rdfs', 'owl'],
+        'application/reginfo+xml' => ['rif'],
+        'application/relax-ng-compact-syntax' => ['rnc'],
+        'application/resource-lists+xml' => ['rl'],
+        'application/resource-lists-diff+xml' => ['rld'],
+        'application/rls-services+xml' => ['rs'],
+        'application/rpki-ghostbusters' => ['gbr'],
+        'application/rpki-manifest' => ['mft'],
+        'application/rpki-roa' => ['roa'],
+        'application/rsd+xml' => ['rsd'],
+        'application/rss+xml' => ['rss'],
+        'application/rtf' => ['rtf'],
+        'application/sbml+xml' => ['sbml'],
+        'application/scvp-cv-request' => ['scq'],
+        'application/scvp-cv-response' => ['scs'],
+        'application/scvp-vp-request' => ['spq'],
+        'application/scvp-vp-response' => ['spp'],
+        'application/sdp' => ['sdp'],
+        'application/set-payment-initiation' => ['setpay'],
+        'application/set-registration-initiation' => ['setreg'],
+        'application/shf+xml' => ['shf'],
+        'application/sieve' => ['siv'],
+        'application/smil' => ['smil', 'smi', 'sml', 'kino'],
+        'application/smil+xml' => ['smi', 'smil', 'sml', 'kino'],
+        'application/sparql-query' => ['rq'],
+        'application/sparql-results+xml' => ['srx'],
+        'application/sql' => ['sql'],
+        'application/srgs' => ['gram'],
+        'application/srgs+xml' => ['grxml'],
+        'application/sru+xml' => ['sru'],
+        'application/ssdl+xml' => ['ssdl'],
+        'application/ssml+xml' => ['ssml'],
+        'application/stuffit' => ['sit'],
+        'application/tei+xml' => ['tei', 'teicorpus'],
+        'application/thraud+xml' => ['tfi'],
+        'application/timestamped-data' => ['tsd'],
+        'application/trig' => ['trig'],
+        'application/vnd.3gpp.pic-bw-large' => ['plb'],
+        'application/vnd.3gpp.pic-bw-small' => ['psb'],
+        'application/vnd.3gpp.pic-bw-var' => ['pvb'],
+        'application/vnd.3gpp2.tcap' => ['tcap'],
+        'application/vnd.3m.post-it-notes' => ['pwn'],
+        'application/vnd.accpac.simply.aso' => ['aso'],
+        'application/vnd.accpac.simply.imp' => ['imp'],
+        'application/vnd.acucobol' => ['acu'],
+        'application/vnd.acucorp' => ['atc', 'acutc'],
+        'application/vnd.adobe.air-application-installer-package+zip' => ['air'],
+        'application/vnd.adobe.flash.movie' => ['swf', 'spl'],
+        'application/vnd.adobe.formscentral.fcdt' => ['fcdt'],
+        'application/vnd.adobe.fxp' => ['fxp', 'fxpl'],
+        'application/vnd.adobe.illustrator' => ['ai'],
+        'application/vnd.adobe.xdp+xml' => ['xdp'],
+        'application/vnd.adobe.xfdf' => ['xfdf'],
+        'application/vnd.ahead.space' => ['ahead'],
+        'application/vnd.airzip.filesecure.azf' => ['azf'],
+        'application/vnd.airzip.filesecure.azs' => ['azs'],
+        'application/vnd.amazon.ebook' => ['azw'],
+        'application/vnd.americandynamics.acc' => ['acc'],
+        'application/vnd.amiga.ami' => ['ami'],
+        'application/vnd.android.package-archive' => ['apk'],
+        'application/vnd.anser-web-certificate-issue-initiation' => ['cii'],
+        'application/vnd.anser-web-funds-transfer-initiation' => ['fti'],
+        'application/vnd.antix.game-component' => ['atx'],
+        'application/vnd.appimage' => ['appimage'],
+        'application/vnd.apple.installer+xml' => ['mpkg'],
+        'application/vnd.apple.keynote' => ['key'],
+        'application/vnd.apple.mpegurl' => ['m3u8', 'm3u'],
+        'application/vnd.aristanetworks.swi' => ['swi'],
+        'application/vnd.astraea-software.iota' => ['iota'],
+        'application/vnd.audiograph' => ['aep'],
+        'application/vnd.blueice.multipass' => ['mpm'],
+        'application/vnd.bmi' => ['bmi'],
+        'application/vnd.businessobjects' => ['rep'],
+        'application/vnd.chemdraw+xml' => ['cdxml'],
+        'application/vnd.chess-pgn' => ['pgn'],
+        'application/vnd.chipnuts.karaoke-mmd' => ['mmd'],
+        'application/vnd.cinderella' => ['cdy'],
+        'application/vnd.claymore' => ['cla'],
+        'application/vnd.cloanto.rp9' => ['rp9'],
+        'application/vnd.clonk.c4group' => ['c4g', 'c4d', 'c4f', 'c4p', 'c4u'],
+        'application/vnd.cluetrust.cartomobile-config' => ['c11amc'],
+        'application/vnd.cluetrust.cartomobile-config-pkg' => ['c11amz'],
+        'application/vnd.coffeescript' => ['coffee'],
+        'application/vnd.comicbook+zip' => ['cbz'],
+        'application/vnd.comicbook-rar' => ['cbr'],
+        'application/vnd.commonspace' => ['csp'],
+        'application/vnd.contact.cmsg' => ['cdbcmsg'],
+        'application/vnd.corel-draw' => ['cdr'],
+        'application/vnd.cosmocaller' => ['cmc'],
+        'application/vnd.crick.clicker' => ['clkx'],
+        'application/vnd.crick.clicker.keyboard' => ['clkk'],
+        'application/vnd.crick.clicker.palette' => ['clkp'],
+        'application/vnd.crick.clicker.template' => ['clkt'],
+        'application/vnd.crick.clicker.wordbank' => ['clkw'],
+        'application/vnd.criticaltools.wbs+xml' => ['wbs'],
+        'application/vnd.ctc-posml' => ['pml'],
+        'application/vnd.cups-ppd' => ['ppd'],
+        'application/vnd.curl.car' => ['car'],
+        'application/vnd.curl.pcurl' => ['pcurl'],
+        'application/vnd.dart' => ['dart'],
+        'application/vnd.data-vision.rdz' => ['rdz'],
+        'application/vnd.debian.binary-package' => ['deb', 'udeb'],
+        'application/vnd.dece.data' => ['uvf', 'uvvf', 'uvd', 'uvvd'],
+        'application/vnd.dece.ttml+xml' => ['uvt', 'uvvt'],
+        'application/vnd.dece.unspecified' => ['uvx', 'uvvx'],
+        'application/vnd.dece.zip' => ['uvz', 'uvvz'],
+        'application/vnd.denovo.fcselayout-link' => ['fe_launch'],
+        'application/vnd.dna' => ['dna'],
+        'application/vnd.dolby.mlp' => ['mlp'],
+        'application/vnd.dpgraph' => ['dpg'],
+        'application/vnd.dreamfactory' => ['dfac'],
+        'application/vnd.ds-keypoint' => ['kpxx'],
+        'application/vnd.dvb.ait' => ['ait'],
+        'application/vnd.dvb.service' => ['svc'],
+        'application/vnd.dynageo' => ['geo'],
+        'application/vnd.ecowin.chart' => ['mag'],
+        'application/vnd.emusic-emusic_package' => ['emp'],
+        'application/vnd.enliven' => ['nml'],
+        'application/vnd.epson.esf' => ['esf'],
+        'application/vnd.epson.msf' => ['msf'],
+        'application/vnd.epson.quickanime' => ['qam'],
+        'application/vnd.epson.salt' => ['slt'],
+        'application/vnd.epson.ssf' => ['ssf'],
+        'application/vnd.eszigno3+xml' => ['es3', 'et3'],
+        'application/vnd.ezpix-album' => ['ez2'],
+        'application/vnd.ezpix-package' => ['ez3'],
+        'application/vnd.fdf' => ['fdf'],
+        'application/vnd.fdsn.mseed' => ['mseed'],
+        'application/vnd.fdsn.seed' => ['seed', 'dataless'],
+        'application/vnd.flatpak' => ['flatpak', 'xdgapp'],
+        'application/vnd.flatpak.ref' => ['flatpakref'],
+        'application/vnd.flatpak.repo' => ['flatpakrepo'],
+        'application/vnd.flographit' => ['gph'],
+        'application/vnd.fluxtime.clip' => ['ftc'],
+        'application/vnd.framemaker' => ['fm', 'frame', 'maker', 'book'],
+        'application/vnd.frogans.fnc' => ['fnc'],
+        'application/vnd.frogans.ltf' => ['ltf'],
+        'application/vnd.fsc.weblaunch' => ['fsc'],
+        'application/vnd.fujitsu.oasys' => ['oas'],
+        'application/vnd.fujitsu.oasys2' => ['oa2'],
+        'application/vnd.fujitsu.oasys3' => ['oa3'],
+        'application/vnd.fujitsu.oasysgp' => ['fg5'],
+        'application/vnd.fujitsu.oasysprs' => ['bh2'],
+        'application/vnd.fujixerox.ddd' => ['ddd'],
+        'application/vnd.fujixerox.docuworks' => ['xdw'],
+        'application/vnd.fujixerox.docuworks.binder' => ['xbd'],
+        'application/vnd.fuzzysheet' => ['fzs'],
+        'application/vnd.genomatix.tuxedo' => ['txd'],
+        'application/vnd.geo+json' => ['geojson', 'geo.json'],
+        'application/vnd.geogebra.file' => ['ggb'],
+        'application/vnd.geogebra.tool' => ['ggt'],
+        'application/vnd.geometry-explorer' => ['gex', 'gre'],
+        'application/vnd.geonext' => ['gxt'],
+        'application/vnd.geoplan' => ['g2w'],
+        'application/vnd.geospace' => ['g3w'],
+        'application/vnd.gmx' => ['gmx'],
+        'application/vnd.google-earth.kml+xml' => ['kml'],
+        'application/vnd.google-earth.kmz' => ['kmz'],
+        'application/vnd.grafeq' => ['gqf', 'gqs'],
+        'application/vnd.groove-account' => ['gac'],
+        'application/vnd.groove-help' => ['ghf'],
+        'application/vnd.groove-identity-message' => ['gim'],
+        'application/vnd.groove-injector' => ['grv'],
+        'application/vnd.groove-tool-message' => ['gtm'],
+        'application/vnd.groove-tool-template' => ['tpl'],
+        'application/vnd.groove-vcard' => ['vcg'],
+        'application/vnd.haansoft-hwp' => ['hwp'],
+        'application/vnd.haansoft-hwt' => ['hwt'],
+        'application/vnd.hal+xml' => ['hal'],
+        'application/vnd.handheld-entertainment+xml' => ['zmm'],
+        'application/vnd.hbci' => ['hbci'],
+        'application/vnd.hhe.lesson-player' => ['les'],
+        'application/vnd.hp-hpgl' => ['hpgl'],
+        'application/vnd.hp-hpid' => ['hpid'],
+        'application/vnd.hp-hps' => ['hps'],
+        'application/vnd.hp-jlyt' => ['jlt'],
+        'application/vnd.hp-pcl' => ['pcl'],
+        'application/vnd.hp-pclxl' => ['pclxl'],
+        'application/vnd.hydrostatix.sof-data' => ['sfd-hdstx'],
+        'application/vnd.ibm.minipay' => ['mpy'],
+        'application/vnd.ibm.modcap' => ['afp', 'listafp', 'list3820'],
+        'application/vnd.ibm.rights-management' => ['irm'],
+        'application/vnd.ibm.secure-container' => ['sc'],
+        'application/vnd.iccprofile' => ['icc', 'icm'],
+        'application/vnd.igloader' => ['igl'],
+        'application/vnd.immervision-ivp' => ['ivp'],
+        'application/vnd.immervision-ivu' => ['ivu'],
+        'application/vnd.insors.igm' => ['igm'],
+        'application/vnd.intercon.formnet' => ['xpw', 'xpx'],
+        'application/vnd.intergeo' => ['i2g'],
+        'application/vnd.intu.qbo' => ['qbo'],
+        'application/vnd.intu.qfx' => ['qfx'],
+        'application/vnd.ipunplugged.rcprofile' => ['rcprofile'],
+        'application/vnd.irepository.package+xml' => ['irp'],
+        'application/vnd.is-xpr' => ['xpr'],
+        'application/vnd.isac.fcs' => ['fcs'],
+        'application/vnd.jam' => ['jam'],
+        'application/vnd.jcp.javame.midlet-rms' => ['rms'],
+        'application/vnd.jisp' => ['jisp'],
+        'application/vnd.joost.joda-archive' => ['joda'],
+        'application/vnd.kahootz' => ['ktz', 'ktr'],
+        'application/vnd.kde.karbon' => ['karbon'],
+        'application/vnd.kde.kchart' => ['chrt'],
+        'application/vnd.kde.kformula' => ['kfo'],
+        'application/vnd.kde.kivio' => ['flw'],
+        'application/vnd.kde.kontour' => ['kon'],
+        'application/vnd.kde.kpresenter' => ['kpr', 'kpt'],
+        'application/vnd.kde.kspread' => ['ksp'],
+        'application/vnd.kde.kword' => ['kwd', 'kwt'],
+        'application/vnd.kenameaapp' => ['htke'],
+        'application/vnd.kidspiration' => ['kia'],
+        'application/vnd.kinar' => ['kne', 'knp'],
+        'application/vnd.koan' => ['skp', 'skd', 'skt', 'skm'],
+        'application/vnd.kodak-descriptor' => ['sse'],
+        'application/vnd.las.las+xml' => ['lasxml'],
+        'application/vnd.llamagraphics.life-balance.desktop' => ['lbd'],
+        'application/vnd.llamagraphics.life-balance.exchange+xml' => ['lbe'],
+        'application/vnd.lotus-1-2-3' => ['123', 'wk1', 'wk3', 'wk4', 'wks'],
+        'application/vnd.lotus-approach' => ['apr'],
+        'application/vnd.lotus-freelance' => ['pre'],
+        'application/vnd.lotus-notes' => ['nsf'],
+        'application/vnd.lotus-organizer' => ['org'],
+        'application/vnd.lotus-screencam' => ['scm'],
+        'application/vnd.lotus-wordpro' => ['lwp'],
+        'application/vnd.macports.portpkg' => ['portpkg'],
+        'application/vnd.mcd' => ['mcd'],
+        'application/vnd.medcalcdata' => ['mc1'],
+        'application/vnd.mediastation.cdkey' => ['cdkey'],
+        'application/vnd.mfer' => ['mwf'],
+        'application/vnd.mfmp' => ['mfm'],
+        'application/vnd.micrografx.flo' => ['flo'],
+        'application/vnd.micrografx.igx' => ['igx'],
+        'application/vnd.mif' => ['mif'],
+        'application/vnd.mobius.daf' => ['daf'],
+        'application/vnd.mobius.dis' => ['dis'],
+        'application/vnd.mobius.mbk' => ['mbk'],
+        'application/vnd.mobius.mqy' => ['mqy'],
+        'application/vnd.mobius.msl' => ['msl'],
+        'application/vnd.mobius.plc' => ['plc'],
+        'application/vnd.mobius.txf' => ['txf'],
+        'application/vnd.mophun.application' => ['mpn'],
+        'application/vnd.mophun.certificate' => ['mpc'],
+        'application/vnd.mozilla.xul+xml' => ['xul'],
+        'application/vnd.ms-access' => ['mdb'],
+        'application/vnd.ms-artgalry' => ['cil'],
+        'application/vnd.ms-asf' => ['asf'],
+        'application/vnd.ms-cab-compressed' => ['cab'],
+        'application/vnd.ms-excel' => ['xls', 'xlm', 'xla', 'xlc', 'xlt', 'xlw', 'xll', 'xld'],
+        'application/vnd.ms-excel.addin.macroenabled.12' => ['xlam'],
+        'application/vnd.ms-excel.sheet.binary.macroenabled.12' => ['xlsb'],
+        'application/vnd.ms-excel.sheet.macroenabled.12' => ['xlsm'],
+        'application/vnd.ms-excel.template.macroenabled.12' => ['xltm'],
+        'application/vnd.ms-fontobject' => ['eot'],
+        'application/vnd.ms-htmlhelp' => ['chm'],
+        'application/vnd.ms-ims' => ['ims'],
+        'application/vnd.ms-lrm' => ['lrm'],
+        'application/vnd.ms-officetheme' => ['thmx'],
+        'application/vnd.ms-pki.seccat' => ['cat'],
+        'application/vnd.ms-pki.stl' => ['stl'],
+        'application/vnd.ms-powerpoint' => ['ppt', 'pps', 'pot', 'ppz'],
+        'application/vnd.ms-powerpoint.addin.macroenabled.12' => ['ppam'],
+        'application/vnd.ms-powerpoint.presentation.macroenabled.12' => ['pptm'],
+        'application/vnd.ms-powerpoint.slide.macroenabled.12' => ['sldm'],
+        'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => ['ppsm'],
+        'application/vnd.ms-powerpoint.template.macroenabled.12' => ['potm'],
+        'application/vnd.ms-project' => ['mpp', 'mpt'],
+        'application/vnd.ms-publisher' => ['pub'],
+        'application/vnd.ms-tnef' => ['tnef', 'tnf'],
+        'application/vnd.ms-visio.drawing.macroenabled.main+xml' => ['vsdm'],
+        'application/vnd.ms-visio.drawing.main+xml' => ['vsdx'],
+        'application/vnd.ms-visio.stencil.macroenabled.main+xml' => ['vssm'],
+        'application/vnd.ms-visio.stencil.main+xml' => ['vssx'],
+        'application/vnd.ms-visio.template.macroenabled.main+xml' => ['vstm'],
+        'application/vnd.ms-visio.template.main+xml' => ['vstx'],
+        'application/vnd.ms-word' => ['doc'],
+        'application/vnd.ms-word.document.macroenabled.12' => ['docm'],
+        'application/vnd.ms-word.template.macroenabled.12' => ['dotm'],
+        'application/vnd.ms-works' => ['wps', 'wks', 'wcm', 'wdb', 'xlr'],
+        'application/vnd.ms-wpl' => ['wpl'],
+        'application/vnd.ms-xpsdocument' => ['xps', 'oxps'],
+        'application/vnd.msaccess' => ['mdb'],
+        'application/vnd.mseq' => ['mseq'],
+        'application/vnd.musician' => ['mus'],
+        'application/vnd.muvee.style' => ['msty'],
+        'application/vnd.mynfc' => ['taglet'],
+        'application/vnd.neurolanguage.nlu' => ['nlu'],
+        'application/vnd.nintendo.snes.rom' => ['sfc', 'smc'],
+        'application/vnd.nitf' => ['ntf', 'nitf'],
+        'application/vnd.noblenet-directory' => ['nnd'],
+        'application/vnd.noblenet-sealer' => ['nns'],
+        'application/vnd.noblenet-web' => ['nnw'],
+        'application/vnd.nokia.n-gage.data' => ['ngdat'],
+        'application/vnd.nokia.n-gage.symbian.install' => ['n-gage'],
+        'application/vnd.nokia.radio-preset' => ['rpst'],
+        'application/vnd.nokia.radio-presets' => ['rpss'],
+        'application/vnd.novadigm.edm' => ['edm'],
+        'application/vnd.novadigm.edx' => ['edx'],
+        'application/vnd.novadigm.ext' => ['ext'],
+        'application/vnd.oasis.docbook+xml' => ['dbk', 'docbook'],
+        'application/vnd.oasis.opendocument.chart' => ['odc'],
+        'application/vnd.oasis.opendocument.chart-template' => ['otc'],
+        'application/vnd.oasis.opendocument.database' => ['odb'],
+        'application/vnd.oasis.opendocument.formula' => ['odf'],
+        'application/vnd.oasis.opendocument.formula-template' => ['odft', 'otf'],
+        'application/vnd.oasis.opendocument.graphics' => ['odg'],
+        'application/vnd.oasis.opendocument.graphics-flat-xml' => ['fodg'],
+        'application/vnd.oasis.opendocument.graphics-template' => ['otg'],
+        'application/vnd.oasis.opendocument.image' => ['odi'],
+        'application/vnd.oasis.opendocument.image-template' => ['oti'],
+        'application/vnd.oasis.opendocument.presentation' => ['odp'],
+        'application/vnd.oasis.opendocument.presentation-flat-xml' => ['fodp'],
+        'application/vnd.oasis.opendocument.presentation-template' => ['otp'],
+        'application/vnd.oasis.opendocument.spreadsheet' => ['ods'],
+        'application/vnd.oasis.opendocument.spreadsheet-flat-xml' => ['fods'],
+        'application/vnd.oasis.opendocument.spreadsheet-template' => ['ots'],
+        'application/vnd.oasis.opendocument.text' => ['odt'],
+        'application/vnd.oasis.opendocument.text-flat-xml' => ['fodt'],
+        'application/vnd.oasis.opendocument.text-master' => ['odm'],
+        'application/vnd.oasis.opendocument.text-template' => ['ott'],
+        'application/vnd.oasis.opendocument.text-web' => ['oth'],
+        'application/vnd.olpc-sugar' => ['xo'],
+        'application/vnd.oma.dd2+xml' => ['dd2'],
+        'application/vnd.openofficeorg.extension' => ['oxt'],
+        'application/vnd.openxmlformats-officedocument.presentationml.presentation' => ['pptx'],
+        'application/vnd.openxmlformats-officedocument.presentationml.slide' => ['sldx'],
+        'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => ['ppsx'],
+        'application/vnd.openxmlformats-officedocument.presentationml.template' => ['potx'],
+        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['xlsx'],
+        'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => ['xltx'],
+        'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => ['docx'],
+        'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => ['dotx'],
+        'application/vnd.osgeo.mapguide.package' => ['mgp'],
+        'application/vnd.osgi.dp' => ['dp'],
+        'application/vnd.osgi.subsystem' => ['esa'],
+        'application/vnd.palm' => ['pdb', 'pqa', 'oprc', 'prc'],
+        'application/vnd.pawaafile' => ['paw'],
+        'application/vnd.pg.format' => ['str'],
+        'application/vnd.pg.osasli' => ['ei6'],
+        'application/vnd.picsel' => ['efif'],
+        'application/vnd.pmi.widget' => ['wg'],
+        'application/vnd.pocketlearn' => ['plf'],
+        'application/vnd.powerbuilder6' => ['pbd'],
+        'application/vnd.previewsystems.box' => ['box'],
+        'application/vnd.proteus.magazine' => ['mgz'],
+        'application/vnd.publishare-delta-tree' => ['qps'],
+        'application/vnd.pvi.ptid1' => ['ptid'],
+        'application/vnd.quark.quarkxpress' => ['qxd', 'qxt', 'qwd', 'qwt', 'qxl', 'qxb'],
+        'application/vnd.rar' => ['rar'],
+        'application/vnd.realvnc.bed' => ['bed'],
+        'application/vnd.recordare.musicxml' => ['mxl'],
+        'application/vnd.recordare.musicxml+xml' => ['musicxml'],
+        'application/vnd.rig.cryptonote' => ['cryptonote'],
+        'application/vnd.rim.cod' => ['cod'],
+        'application/vnd.rn-realmedia' => ['rm', 'rmj', 'rmm', 'rms', 'rmx', 'rmvb'],
+        'application/vnd.rn-realmedia-vbr' => ['rmvb', 'rm', 'rmj', 'rmm', 'rms', 'rmx'],
+        'application/vnd.route66.link66+xml' => ['link66'],
+        'application/vnd.sailingtracker.track' => ['st'],
+        'application/vnd.sdp' => ['sdp'],
+        'application/vnd.seemail' => ['see'],
+        'application/vnd.sema' => ['sema'],
+        'application/vnd.semd' => ['semd'],
+        'application/vnd.semf' => ['semf'],
+        'application/vnd.shana.informed.formdata' => ['ifm'],
+        'application/vnd.shana.informed.formtemplate' => ['itp'],
+        'application/vnd.shana.informed.interchange' => ['iif'],
+        'application/vnd.shana.informed.package' => ['ipk'],
+        'application/vnd.simtech-mindmapper' => ['twd', 'twds'],
+        'application/vnd.smaf' => ['mmf', 'smaf'],
+        'application/vnd.smart.teacher' => ['teacher'],
+        'application/vnd.snap' => ['snap'],
+        'application/vnd.solent.sdkm+xml' => ['sdkm', 'sdkd'],
+        'application/vnd.spotfire.dxp' => ['dxp'],
+        'application/vnd.spotfire.sfs' => ['sfs'],
+        'application/vnd.sqlite3' => ['sqlite3'],
+        'application/vnd.squashfs' => ['sqsh'],
+        'application/vnd.stardivision.calc' => ['sdc'],
+        'application/vnd.stardivision.chart' => ['sds'],
+        'application/vnd.stardivision.draw' => ['sda'],
+        'application/vnd.stardivision.impress' => ['sdd', 'sdp'],
+        'application/vnd.stardivision.mail' => ['smd'],
+        'application/vnd.stardivision.math' => ['smf'],
+        'application/vnd.stardivision.writer' => ['sdw', 'vor', 'sgl'],
+        'application/vnd.stardivision.writer-global' => ['sgl', 'sdw', 'vor'],
+        'application/vnd.stepmania.package' => ['smzip'],
+        'application/vnd.stepmania.stepchart' => ['sm'],
+        'application/vnd.sun.xml.base' => ['odb'],
+        'application/vnd.sun.xml.calc' => ['sxc'],
+        'application/vnd.sun.xml.calc.template' => ['stc'],
+        'application/vnd.sun.xml.draw' => ['sxd'],
+        'application/vnd.sun.xml.draw.template' => ['std'],
+        'application/vnd.sun.xml.impress' => ['sxi'],
+        'application/vnd.sun.xml.impress.template' => ['sti'],
+        'application/vnd.sun.xml.math' => ['sxm'],
+        'application/vnd.sun.xml.writer' => ['sxw'],
+        'application/vnd.sun.xml.writer.global' => ['sxg'],
+        'application/vnd.sun.xml.writer.template' => ['stw'],
+        'application/vnd.sus-calendar' => ['sus', 'susp'],
+        'application/vnd.svd' => ['svd'],
+        'application/vnd.symbian.install' => ['sis', 'sisx'],
+        'application/vnd.syncml+xml' => ['xsm'],
+        'application/vnd.syncml.dm+wbxml' => ['bdm'],
+        'application/vnd.syncml.dm+xml' => ['xdm'],
+        'application/vnd.tao.intent-module-archive' => ['tao'],
+        'application/vnd.tcpdump.pcap' => ['pcap', 'cap', 'dmp'],
+        'application/vnd.tmobile-livetv' => ['tmo'],
+        'application/vnd.trid.tpt' => ['tpt'],
+        'application/vnd.triscape.mxs' => ['mxs'],
+        'application/vnd.trueapp' => ['tra'],
+        'application/vnd.ufdl' => ['ufd', 'ufdl'],
+        'application/vnd.uiq.theme' => ['utz'],
+        'application/vnd.umajin' => ['umj'],
+        'application/vnd.unity' => ['unityweb'],
+        'application/vnd.uoml+xml' => ['uoml'],
+        'application/vnd.vcx' => ['vcx'],
+        'application/vnd.visio' => ['vsd', 'vst', 'vss', 'vsw'],
+        'application/vnd.visionary' => ['vis'],
+        'application/vnd.vsf' => ['vsf'],
+        'application/vnd.wap.wbxml' => ['wbxml'],
+        'application/vnd.wap.wmlc' => ['wmlc'],
+        'application/vnd.wap.wmlscriptc' => ['wmlsc'],
+        'application/vnd.webturbo' => ['wtb'],
+        'application/vnd.wolfram.player' => ['nbp'],
+        'application/vnd.wordperfect' => ['wpd', 'wp', 'wp4', 'wp5', 'wp6', 'wpp'],
+        'application/vnd.wqd' => ['wqd'],
+        'application/vnd.wt.stf' => ['stf'],
+        'application/vnd.xara' => ['xar'],
+        'application/vnd.xdgapp' => ['flatpak', 'xdgapp'],
+        'application/vnd.xfdl' => ['xfdl'],
+        'application/vnd.yamaha.hv-dic' => ['hvd'],
+        'application/vnd.yamaha.hv-script' => ['hvs'],
+        'application/vnd.yamaha.hv-voice' => ['hvp'],
+        'application/vnd.yamaha.openscoreformat' => ['osf'],
+        'application/vnd.yamaha.openscoreformat.osfpvg+xml' => ['osfpvg'],
+        'application/vnd.yamaha.smaf-audio' => ['saf'],
+        'application/vnd.yamaha.smaf-phrase' => ['spf'],
+        'application/vnd.yellowriver-custom-menu' => ['cmp'],
+        'application/vnd.youtube.yt' => ['yt'],
+        'application/vnd.zul' => ['zir', 'zirz'],
+        'application/vnd.zzazz.deck+xml' => ['zaz'],
+        'application/voicexml+xml' => ['vxml'],
+        'application/widget' => ['wgt'],
+        'application/winhlp' => ['hlp'],
+        'application/wk1' => ['123', 'wk1', 'wk3', 'wk4', 'wks'],
+        'application/wmf' => ['wmf'],
+        'application/wordperfect' => ['wp', 'wp4', 'wp5', 'wp6', 'wpd', 'wpp'],
+        'application/wsdl+xml' => ['wsdl'],
+        'application/wspolicy+xml' => ['wspolicy'],
+        'application/wwf' => ['wwf'],
+        'application/x-123' => ['123', 'wk1', 'wk3', 'wk4', 'wks'],
+        'application/x-7z-compressed' => ['7z'],
+        'application/x-abiword' => ['abw', 'abw.CRASHED', 'abw.gz', 'zabw'],
+        'application/x-ace' => ['ace'],
+        'application/x-ace-compressed' => ['ace'],
+        'application/x-alz' => ['alz'],
+        'application/x-amiga-disk-format' => ['adf'],
+        'application/x-amipro' => ['sam'],
+        'application/x-annodex' => ['anx'],
+        'application/x-aportisdoc' => ['pdb', 'pdc'],
+        'application/x-apple-diskimage' => ['dmg'],
+        'application/x-applix-spreadsheet' => ['as'],
+        'application/x-applix-word' => ['aw'],
+        'application/x-archive' => ['a', 'ar'],
+        'application/x-arj' => ['arj'],
+        'application/x-asp' => ['asp'],
+        'application/x-atari-2600-rom' => ['a26'],
+        'application/x-atari-7800-rom' => ['a78'],
+        'application/x-atari-lynx-rom' => ['lnx'],
+        'application/x-authorware-bin' => ['aab', 'x32', 'u32', 'vox'],
+        'application/x-authorware-map' => ['aam'],
+        'application/x-authorware-seg' => ['aas'],
+        'application/x-awk' => ['awk'],
+        'application/x-bcpio' => ['bcpio'],
+        'application/x-bittorrent' => ['torrent'],
+        'application/x-blender' => ['blender', 'blend', 'BLEND'],
+        'application/x-blorb' => ['blb', 'blorb'],
+        'application/x-bsdiff' => ['bsdiff'],
+        'application/x-bzdvi' => ['dvi.bz2'],
+        'application/x-bzip' => ['bz', 'bz2'],
+        'application/x-bzip-compressed-tar' => ['tar.bz2', 'tar.bz', 'tbz2', 'tbz', 'tb2'],
+        'application/x-bzip2' => ['bz2', 'boz', 'bz'],
+        'application/x-bzpdf' => ['pdf.bz2'],
+        'application/x-bzpostscript' => ['ps.bz2'],
+        'application/x-cb7' => ['cb7'],
+        'application/x-cbr' => ['cbr', 'cba', 'cbt', 'cbz', 'cb7'],
+        'application/x-cbt' => ['cbt'],
+        'application/x-cbz' => ['cbz'],
+        'application/x-ccmx' => ['ccmx'],
+        'application/x-cd-image' => ['iso', 'iso9660'],
+        'application/x-cdlink' => ['vcd'],
+        'application/x-cdr' => ['cdr'],
+        'application/x-cdrdao-toc' => ['toc'],
+        'application/x-cfs-compressed' => ['cfs'],
+        'application/x-chat' => ['chat'],
+        'application/x-chess-pgn' => ['pgn'],
+        'application/x-chm' => ['chm'],
+        'application/x-cisco-vpn-settings' => ['pcf'],
+        'application/x-compress' => ['Z'],
+        'application/x-compressed-tar' => ['tar.gz', 'tgz'],
+        'application/x-conference' => ['nsc'],
+        'application/x-coreldraw' => ['cdr'],
+        'application/x-cpio' => ['cpio'],
+        'application/x-cpio-compressed' => ['cpio.gz'],
+        'application/x-csh' => ['csh'],
+        'application/x-cue' => ['cue'],
+        'application/x-dar' => ['dar'],
+        'application/x-dbase' => ['dbf'],
+        'application/x-dbf' => ['dbf'],
+        'application/x-dc-rom' => ['dc'],
+        'application/x-deb' => ['deb', 'udeb'],
+        'application/x-debian-package' => ['deb', 'udeb'],
+        'application/x-designer' => ['ui'],
+        'application/x-desktop' => ['desktop', 'kdelnk'],
+        'application/x-dgc-compressed' => ['dgc'],
+        'application/x-dia-diagram' => ['dia'],
+        'application/x-dia-shape' => ['shape'],
+        'application/x-director' => ['dir', 'dcr', 'dxr', 'cst', 'cct', 'cxt', 'w3d', 'fgd', 'swa'],
+        'application/x-docbook+xml' => ['dbk', 'docbook'],
+        'application/x-doom' => ['wad'],
+        'application/x-doom-wad' => ['wad'],
+        'application/x-dtbncx+xml' => ['ncx'],
+        'application/x-dtbook+xml' => ['dtb'],
+        'application/x-dtbresource+xml' => ['res'],
+        'application/x-dvi' => ['dvi'],
+        'application/x-e-theme' => ['etheme'],
+        'application/x-egon' => ['egon'],
+        'application/x-emf' => ['emf'],
+        'application/x-envoy' => ['evy'],
+        'application/x-eva' => ['eva'],
+        'application/x-fd-file' => ['fd', 'qd'],
+        'application/x-fds-disk' => ['fds'],
+        'application/x-fictionbook' => ['fb2'],
+        'application/x-fictionbook+xml' => ['fb2'],
+        'application/x-flash-video' => ['flv'],
+        'application/x-fluid' => ['fl'],
+        'application/x-font-afm' => ['afm'],
+        'application/x-font-bdf' => ['bdf'],
+        'application/x-font-ghostscript' => ['gsf'],
+        'application/x-font-linux-psf' => ['psf'],
+        'application/x-font-otf' => ['otf'],
+        'application/x-font-pcf' => ['pcf', 'pcf.Z', 'pcf.gz'],
+        'application/x-font-snf' => ['snf'],
+        'application/x-font-speedo' => ['spd'],
+        'application/x-font-ttf' => ['ttf'],
+        'application/x-font-ttx' => ['ttx'],
+        'application/x-font-type1' => ['pfa', 'pfb', 'pfm', 'afm', 'gsf'],
+        'application/x-font-woff' => ['woff'],
+        'application/x-frame' => ['fm'],
+        'application/x-freearc' => ['arc'],
+        'application/x-futuresplash' => ['spl'],
+        'application/x-gameboy-color-rom' => ['gbc', 'cgb'],
+        'application/x-gameboy-rom' => ['gb', 'sgb'],
+        'application/x-gamecube-iso-image' => ['iso'],
+        'application/x-gamecube-rom' => ['iso'],
+        'application/x-gamegear-rom' => ['gg'],
+        'application/x-gba-rom' => ['gba', 'agb'],
+        'application/x-gca-compressed' => ['gca'],
+        'application/x-gedcom' => ['ged', 'gedcom'],
+        'application/x-genesis-32x-rom' => ['32x', 'mdx'],
+        'application/x-genesis-rom' => ['gen', 'smd'],
+        'application/x-gettext' => ['po'],
+        'application/x-gettext-translation' => ['gmo', 'mo'],
+        'application/x-glade' => ['glade'],
+        'application/x-glulx' => ['ulx'],
+        'application/x-gnome-app-info' => ['desktop', 'kdelnk'],
+        'application/x-gnucash' => ['gnucash', 'gnc', 'xac'],
+        'application/x-gnumeric' => ['gnumeric'],
+        'application/x-gnuplot' => ['gp', 'gplt', 'gnuplot'],
+        'application/x-go-sgf' => ['sgf'],
+        'application/x-gpx' => ['gpx'],
+        'application/x-gpx+xml' => ['gpx'],
+        'application/x-gramps-xml' => ['gramps'],
+        'application/x-graphite' => ['gra'],
+        'application/x-gtar' => ['gtar', 'tar', 'gem'],
+        'application/x-gtk-builder' => ['ui'],
+        'application/x-gz-font-linux-psf' => ['psf.gz'],
+        'application/x-gzdvi' => ['dvi.gz'],
+        'application/x-gzip' => ['gz'],
+        'application/x-gzpdf' => ['pdf.gz'],
+        'application/x-gzpostscript' => ['ps.gz'],
+        'application/x-hdf' => ['hdf', 'hdf4', 'h4', 'hdf5', 'h5'],
+        'application/x-hfe-file' => ['hfe'],
+        'application/x-hfe-floppy-image' => ['hfe'],
+        'application/x-hwp' => ['hwp'],
+        'application/x-hwt' => ['hwt'],
+        'application/x-ica' => ['ica'],
+        'application/x-install-instructions' => ['install'],
+        'application/x-ipynb+json' => ['ipynb'],
+        'application/x-iso9660-appimage' => ['appimage'],
+        'application/x-iso9660-image' => ['iso', 'iso9660'],
+        'application/x-it87' => ['it87'],
+        'application/x-iwork-keynote-sffkey' => ['key'],
+        'application/x-jar' => ['jar'],
+        'application/x-java' => ['class'],
+        'application/x-java-archive' => ['jar'],
+        'application/x-java-class' => ['class'],
+        'application/x-java-jce-keystore' => ['jceks'],
+        'application/x-java-jnlp-file' => ['jnlp'],
+        'application/x-java-keystore' => ['jks', 'ks'],
+        'application/x-java-pack200' => ['pack'],
+        'application/x-java-vm' => ['class'],
+        'application/x-javascript' => ['js', 'jsm', 'mjs'],
+        'application/x-jbuilder-project' => ['jpr', 'jpx'],
+        'application/x-karbon' => ['karbon'],
+        'application/x-kchart' => ['chrt'],
+        'application/x-kexi-connectiondata' => ['kexic'],
+        'application/x-kexiproject-shortcut' => ['kexis'],
+        'application/x-kexiproject-sqlite' => ['kexi'],
+        'application/x-kexiproject-sqlite2' => ['kexi'],
+        'application/x-kexiproject-sqlite3' => ['kexi'],
+        'application/x-kformula' => ['kfo'],
+        'application/x-killustrator' => ['kil'],
+        'application/x-kivio' => ['flw'],
+        'application/x-kontour' => ['kon'],
+        'application/x-kpovmodeler' => ['kpm'],
+        'application/x-kpresenter' => ['kpr', 'kpt'],
+        'application/x-krita' => ['kra'],
+        'application/x-kspread' => ['ksp'],
+        'application/x-kugar' => ['kud'],
+        'application/x-kword' => ['kwd', 'kwt'],
+        'application/x-latex' => ['latex'],
+        'application/x-lha' => ['lha', 'lzh'],
+        'application/x-lhz' => ['lhz'],
+        'application/x-linguist' => ['ts'],
+        'application/x-lotus123' => ['123', 'wk1', 'wk3', 'wk4', 'wks'],
+        'application/x-lrzip' => ['lrz'],
+        'application/x-lrzip-compressed-tar' => ['tar.lrz', 'tlrz'],
+        'application/x-lyx' => ['lyx'],
+        'application/x-lz4' => ['lz4'],
+        'application/x-lz4-compressed-tar' => ['tar.lz4'],
+        'application/x-lzh-compressed' => ['lzh', 'lha'],
+        'application/x-lzip' => ['lz'],
+        'application/x-lzip-compressed-tar' => ['tar.lz'],
+        'application/x-lzma' => ['lzma'],
+        'application/x-lzma-compressed-tar' => ['tar.lzma', 'tlz'],
+        'application/x-lzop' => ['lzo'],
+        'application/x-lzpdf' => ['pdf.lz'],
+        'application/x-m4' => ['m4'],
+        'application/x-magicpoint' => ['mgp'],
+        'application/x-markaby' => ['mab'],
+        'application/x-mathematica' => ['nb'],
+        'application/x-mdb' => ['mdb'],
+        'application/x-mie' => ['mie'],
+        'application/x-mif' => ['mif'],
+        'application/x-mimearchive' => ['mhtml', 'mht'],
+        'application/x-mobipocket-ebook' => ['prc', 'mobi'],
+        'application/x-ms-application' => ['application'],
+        'application/x-ms-asx' => ['asx', 'wax', 'wvx', 'wmx'],
+        'application/x-ms-dos-executable' => ['exe'],
+        'application/x-ms-shortcut' => ['lnk'],
+        'application/x-ms-wim' => ['wim', 'swm'],
+        'application/x-ms-wmd' => ['wmd'],
+        'application/x-ms-wmz' => ['wmz'],
+        'application/x-ms-xbap' => ['xbap'],
+        'application/x-msaccess' => ['mdb'],
+        'application/x-msbinder' => ['obd'],
+        'application/x-mscardfile' => ['crd'],
+        'application/x-msclip' => ['clp'],
+        'application/x-msdownload' => ['exe', 'dll', 'com', 'bat', 'msi'],
+        'application/x-msexcel' => ['xls', 'xlc', 'xll', 'xlm', 'xlw', 'xla', 'xlt', 'xld'],
+        'application/x-msi' => ['msi'],
+        'application/x-msmediaview' => ['mvb', 'm13', 'm14'],
+        'application/x-msmetafile' => ['wmf', 'wmz', 'emf', 'emz'],
+        'application/x-msmoney' => ['mny'],
+        'application/x-mspowerpoint' => ['ppz', 'ppt', 'pps', 'pot'],
+        'application/x-mspublisher' => ['pub'],
+        'application/x-msschedule' => ['scd'],
+        'application/x-msterminal' => ['trm'],
+        'application/x-mswinurl' => ['url'],
+        'application/x-msword' => ['doc'],
+        'application/x-mswrite' => ['wri'],
+        'application/x-msx-rom' => ['msx'],
+        'application/x-n64-rom' => ['n64', 'z64', 'v64'],
+        'application/x-navi-animation' => ['ani'],
+        'application/x-neo-geo-pocket-color-rom' => ['ngc'],
+        'application/x-neo-geo-pocket-rom' => ['ngp'],
+        'application/x-nes-rom' => ['nes', 'nez', 'unf', 'unif'],
+        'application/x-netcdf' => ['nc', 'cdf'],
+        'application/x-netshow-channel' => ['nsc'],
+        'application/x-nintendo-ds-rom' => ['nds'],
+        'application/x-nzb' => ['nzb'],
+        'application/x-object' => ['o'],
+        'application/x-ogg' => ['ogx'],
+        'application/x-oleo' => ['oleo'],
+        'application/x-pagemaker' => ['p65', 'pm', 'pm6', 'pmd'],
+        'application/x-pak' => ['pak'],
+        'application/x-palm-database' => ['prc', 'pdb', 'pqa', 'oprc'],
+        'application/x-par2' => ['PAR2', 'par2'],
+        'application/x-partial-download' => ['wkdownload', 'crdownload', 'part'],
+        'application/x-pc-engine-rom' => ['pce'],
+        'application/x-pcap' => ['pcap', 'cap', 'dmp'],
+        'application/x-pdf' => ['pdf'],
+        'application/x-perl' => ['pl', 'PL', 'pm', 'al', 'perl', 'pod', 't'],
+        'application/x-photoshop' => ['psd'],
+        'application/x-php' => ['php', 'php3', 'php4', 'php5', 'phps'],
+        'application/x-pkcs12' => ['p12', 'pfx'],
+        'application/x-pkcs7-certificates' => ['p7b', 'spc'],
+        'application/x-pkcs7-certreqresp' => ['p7r'],
+        'application/x-planperfect' => ['pln'],
+        'application/x-pocket-word' => ['psw'],
+        'application/x-pw' => ['pw'],
+        'application/x-python-bytecode' => ['pyc', 'pyo'],
+        'application/x-qpress' => ['qp'],
+        'application/x-qtiplot' => ['qti', 'qti.gz'],
+        'application/x-quattropro' => ['wb1', 'wb2', 'wb3'],
+        'application/x-quicktime-media-link' => ['qtl'],
+        'application/x-quicktimeplayer' => ['qtl'],
+        'application/x-qw' => ['qif'],
+        'application/x-rar' => ['rar'],
+        'application/x-rar-compressed' => ['rar'],
+        'application/x-raw-disk-image' => ['raw-disk-image', 'img'],
+        'application/x-raw-disk-image-xz-compressed' => ['raw-disk-image.xz', 'img.xz'],
+        'application/x-raw-floppy-disk-image' => ['fd', 'qd'],
+        'application/x-redhat-package-manager' => ['rpm'],
+        'application/x-reject' => ['rej'],
+        'application/x-research-info-systems' => ['ris'],
+        'application/x-rnc' => ['rnc'],
+        'application/x-rpm' => ['rpm'],
+        'application/x-ruby' => ['rb'],
+        'application/x-sami' => ['smi', 'sami'],
+        'application/x-sap-file' => ['sap'],
+        'application/x-saturn-rom' => ['bin', 'iso'],
+        'application/x-sdp' => ['sdp'],
+        'application/x-sega-cd-rom' => ['bin', 'iso'],
+        'application/x-sg1000-rom' => ['sg'],
+        'application/x-sh' => ['sh'],
+        'application/x-shar' => ['shar'],
+        'application/x-shared-library-la' => ['la'],
+        'application/x-sharedlib' => ['so'],
+        'application/x-shellscript' => ['sh'],
+        'application/x-shockwave-flash' => ['swf', 'spl'],
+        'application/x-shorten' => ['shn'],
+        'application/x-siag' => ['siag'],
+        'application/x-silverlight-app' => ['xap'],
+        'application/x-sit' => ['sit'],
+        'application/x-smaf' => ['mmf', 'smaf'],
+        'application/x-sms-rom' => ['sms'],
+        'application/x-snes-rom' => ['sfc', 'smc'],
+        'application/x-source-rpm' => ['src.rpm', 'spm'],
+        'application/x-spss-por' => ['por'],
+        'application/x-spss-sav' => ['sav', 'zsav'],
+        'application/x-spss-savefile' => ['sav', 'zsav'],
+        'application/x-sql' => ['sql'],
+        'application/x-sqlite2' => ['sqlite2'],
+        'application/x-sqlite3' => ['sqlite3'],
+        'application/x-srt' => ['srt'],
+        'application/x-stuffit' => ['sit'],
+        'application/x-stuffitx' => ['sitx'],
+        'application/x-subrip' => ['srt'],
+        'application/x-sv4cpio' => ['sv4cpio'],
+        'application/x-sv4crc' => ['sv4crc'],
+        'application/x-t3vm-image' => ['t3'],
+        'application/x-t602' => ['602'],
+        'application/x-tads' => ['gam'],
+        'application/x-tar' => ['tar', 'gtar', 'gem'],
+        'application/x-tarz' => ['tar.Z', 'taz'],
+        'application/x-tcl' => ['tcl'],
+        'application/x-tex' => ['tex', 'ltx', 'sty', 'cls', 'dtx', 'ins', 'latex'],
+        'application/x-tex-gf' => ['gf'],
+        'application/x-tex-pk' => ['pk'],
+        'application/x-tex-tfm' => ['tfm'],
+        'application/x-texinfo' => ['texinfo', 'texi'],
+        'application/x-tgif' => ['obj'],
+        'application/x-theme' => ['theme'],
+        'application/x-thomson-cartridge-memo7' => ['m7'],
+        'application/x-thomson-cassette' => ['k7'],
+        'application/x-thomson-sap-image' => ['sap'],
+        'application/x-trash' => ['bak', 'old', 'sik'],
+        'application/x-trig' => ['trig'],
+        'application/x-troff' => ['tr', 'roff', 't'],
+        'application/x-troff-man' => ['man'],
+        'application/x-tzo' => ['tar.lzo', 'tzo'],
+        'application/x-ufraw' => ['ufraw'],
+        'application/x-ustar' => ['ustar'],
+        'application/x-virtual-boy-rom' => ['vb'],
+        'application/x-vnd.kde.kexi' => ['kexi'],
+        'application/x-wais-source' => ['src'],
+        'application/x-wbfs' => ['iso'],
+        'application/x-wia' => ['iso'],
+        'application/x-wii-iso-image' => ['iso'],
+        'application/x-wii-rom' => ['iso'],
+        'application/x-wii-wad' => ['wad'],
+        'application/x-windows-themepack' => ['themepack'],
+        'application/x-wmf' => ['wmf'],
+        'application/x-wonderswan-color-rom' => ['wsc'],
+        'application/x-wonderswan-rom' => ['ws'],
+        'application/x-wordperfect' => ['wp', 'wp4', 'wp5', 'wp6', 'wpd', 'wpp'],
+        'application/x-wpg' => ['wpg'],
+        'application/x-wwf' => ['wwf'],
+        'application/x-x509-ca-cert' => ['der', 'crt', 'cert', 'pem'],
+        'application/x-xar' => ['xar', 'pkg'],
+        'application/x-xbel' => ['xbel'],
+        'application/x-xfig' => ['fig'],
+        'application/x-xliff' => ['xlf', 'xliff'],
+        'application/x-xliff+xml' => ['xlf'],
+        'application/x-xpinstall' => ['xpi'],
+        'application/x-xspf+xml' => ['xspf'],
+        'application/x-xz' => ['xz'],
+        'application/x-xz-compressed-tar' => ['tar.xz', 'txz'],
+        'application/x-xzpdf' => ['pdf.xz'],
+        'application/x-yaml' => ['yaml', 'yml'],
+        'application/x-zip' => ['zip'],
+        'application/x-zip-compressed' => ['zip'],
+        'application/x-zip-compressed-fb2' => ['fb2.zip'],
+        'application/x-zmachine' => ['z1', 'z2', 'z3', 'z4', 'z5', 'z6', 'z7', 'z8'],
+        'application/x-zoo' => ['zoo'],
+        'application/xaml+xml' => ['xaml'],
+        'application/xcap-diff+xml' => ['xdf'],
+        'application/xenc+xml' => ['xenc'],
+        'application/xhtml+xml' => ['xhtml', 'xht'],
+        'application/xliff+xml' => ['xlf', 'xliff'],
+        'application/xml' => ['xml', 'xsl', 'xbl', 'xsd', 'rng'],
+        'application/xml-dtd' => ['dtd'],
+        'application/xml-external-parsed-entity' => ['ent'],
+        'application/xop+xml' => ['xop'],
+        'application/xproc+xml' => ['xpl'],
+        'application/xps' => ['oxps', 'xps'],
+        'application/xslt+xml' => ['xslt', 'xsl'],
+        'application/xspf+xml' => ['xspf'],
+        'application/xv+xml' => ['mxml', 'xhvml', 'xvml', 'xvm'],
+        'application/yang' => ['yang'],
+        'application/yin+xml' => ['yin'],
+        'application/zip' => ['zip'],
+        'application/zlib' => ['zz'],
+        'audio/3gpp' => ['3gp', '3gpp', '3ga'],
+        'audio/3gpp-encrypted' => ['3gp', '3gpp', '3ga'],
+        'audio/3gpp2' => ['3g2', '3gp2', '3gpp2'],
+        'audio/aac' => ['aac', 'adts', 'ass'],
+        'audio/ac3' => ['ac3'],
+        'audio/adpcm' => ['adp'],
+        'audio/amr' => ['amr'],
+        'audio/amr-encrypted' => ['amr'],
+        'audio/amr-wb' => ['awb'],
+        'audio/amr-wb-encrypted' => ['awb'],
+        'audio/annodex' => ['axa'],
+        'audio/basic' => ['au', 'snd'],
+        'audio/flac' => ['flac'],
+        'audio/imelody' => ['imy', 'ime'],
+        'audio/m3u' => ['m3u', 'm3u8', 'vlc'],
+        'audio/m4a' => ['m4a', 'f4a'],
+        'audio/midi' => ['mid', 'midi', 'kar', 'rmi'],
+        'audio/mobile-xmf' => ['xmf'],
+        'audio/mp2' => ['mp2'],
+        'audio/mp3' => ['mp3', 'mpga'],
+        'audio/mp4' => ['m4a', 'mp4a', 'f4a'],
+        'audio/mpeg' => ['mpga', 'mp2', 'mp2a', 'mp3', 'm2a', 'm3a'],
+        'audio/mpegurl' => ['m3u', 'm3u8', 'vlc'],
+        'audio/ogg' => ['oga', 'ogg', 'spx', 'opus'],
+        'audio/prs.sid' => ['sid', 'psid'],
+        'audio/s3m' => ['s3m'],
+        'audio/scpls' => ['pls'],
+        'audio/silk' => ['sil'],
+        'audio/tta' => ['tta'],
+        'audio/usac' => ['loas', 'xhe'],
+        'audio/vnd.audible' => ['aa', 'aax'],
+        'audio/vnd.audible.aax' => ['aa', 'aax'],
+        'audio/vnd.dece.audio' => ['uva', 'uvva'],
+        'audio/vnd.digital-winds' => ['eol'],
+        'audio/vnd.dra' => ['dra'],
+        'audio/vnd.dts' => ['dts'],
+        'audio/vnd.dts.hd' => ['dtshd'],
+        'audio/vnd.lucent.voice' => ['lvp'],
+        'audio/vnd.m-realaudio' => ['ra', 'rax'],
+        'audio/vnd.ms-playready.media.pya' => ['pya'],
+        'audio/vnd.nuera.ecelp4800' => ['ecelp4800'],
+        'audio/vnd.nuera.ecelp7470' => ['ecelp7470'],
+        'audio/vnd.nuera.ecelp9600' => ['ecelp9600'],
+        'audio/vnd.rip' => ['rip'],
+        'audio/vnd.rn-realaudio' => ['ra', 'rax'],
+        'audio/vnd.wave' => ['wav'],
+        'audio/vorbis' => ['oga', 'ogg'],
+        'audio/wav' => ['wav'],
+        'audio/webm' => ['weba'],
+        'audio/wma' => ['wma'],
+        'audio/x-aac' => ['aac', 'adts', 'ass'],
+        'audio/x-aifc' => ['aifc', 'aiffc'],
+        'audio/x-aiff' => ['aif', 'aiff', 'aifc'],
+        'audio/x-aiffc' => ['aifc', 'aiffc'],
+        'audio/x-amzxml' => ['amz'],
+        'audio/x-annodex' => ['axa'],
+        'audio/x-ape' => ['ape'],
+        'audio/x-caf' => ['caf'],
+        'audio/x-dts' => ['dts'],
+        'audio/x-dtshd' => ['dtshd'],
+        'audio/x-flac' => ['flac'],
+        'audio/x-flac+ogg' => ['oga', 'ogg'],
+        'audio/x-gsm' => ['gsm'],
+        'audio/x-hx-aac-adts' => ['aac', 'adts', 'ass'],
+        'audio/x-imelody' => ['imy', 'ime'],
+        'audio/x-iriver-pla' => ['pla'],
+        'audio/x-it' => ['it'],
+        'audio/x-m3u' => ['m3u', 'm3u8', 'vlc'],
+        'audio/x-m4a' => ['m4a', 'f4a'],
+        'audio/x-m4b' => ['m4b', 'f4b'],
+        'audio/x-m4r' => ['m4r'],
+        'audio/x-matroska' => ['mka'],
+        'audio/x-midi' => ['mid', 'midi', 'kar'],
+        'audio/x-minipsf' => ['minipsf'],
+        'audio/x-mo3' => ['mo3'],
+        'audio/x-mod' => ['mod', 'ult', 'uni', 'm15', 'mtm', '669', 'med'],
+        'audio/x-mp2' => ['mp2'],
+        'audio/x-mp3' => ['mp3', 'mpga'],
+        'audio/x-mp3-playlist' => ['m3u', 'm3u8', 'vlc'],
+        'audio/x-mpeg' => ['mp3', 'mpga'],
+        'audio/x-mpegurl' => ['m3u', 'm3u8', 'vlc'],
+        'audio/x-mpg' => ['mp3', 'mpga'],
+        'audio/x-ms-asx' => ['asx', 'wax', 'wvx', 'wmx'],
+        'audio/x-ms-wax' => ['wax'],
+        'audio/x-ms-wma' => ['wma'],
+        'audio/x-musepack' => ['mpc', 'mpp', 'mp+'],
+        'audio/x-ogg' => ['oga', 'ogg', 'opus'],
+        'audio/x-oggflac' => ['oga', 'ogg'],
+        'audio/x-opus+ogg' => ['opus'],
+        'audio/x-pn-audibleaudio' => ['aa', 'aax'],
+        'audio/x-pn-realaudio' => ['ram', 'ra', 'rax'],
+        'audio/x-pn-realaudio-plugin' => ['rmp'],
+        'audio/x-psf' => ['psf'],
+        'audio/x-psflib' => ['psflib'],
+        'audio/x-rn-3gpp-amr' => ['3gp', '3gpp', '3ga'],
+        'audio/x-rn-3gpp-amr-encrypted' => ['3gp', '3gpp', '3ga'],
+        'audio/x-rn-3gpp-amr-wb' => ['3gp', '3gpp', '3ga'],
+        'audio/x-rn-3gpp-amr-wb-encrypted' => ['3gp', '3gpp', '3ga'],
+        'audio/x-s3m' => ['s3m'],
+        'audio/x-scpls' => ['pls'],
+        'audio/x-shorten' => ['shn'],
+        'audio/x-speex' => ['spx'],
+        'audio/x-speex+ogg' => ['oga', 'ogg'],
+        'audio/x-stm' => ['stm'],
+        'audio/x-tta' => ['tta'],
+        'audio/x-voc' => ['voc'],
+        'audio/x-vorbis' => ['oga', 'ogg'],
+        'audio/x-vorbis+ogg' => ['oga', 'ogg'],
+        'audio/x-wav' => ['wav'],
+        'audio/x-wavpack' => ['wv', 'wvp'],
+        'audio/x-wavpack-correction' => ['wvc'],
+        'audio/x-xi' => ['xi'],
+        'audio/x-xm' => ['xm'],
+        'audio/x-xmf' => ['xmf'],
+        'audio/xm' => ['xm'],
+        'audio/xmf' => ['xmf'],
+        'chemical/x-cdx' => ['cdx'],
+        'chemical/x-cif' => ['cif'],
+        'chemical/x-cmdf' => ['cmdf'],
+        'chemical/x-cml' => ['cml'],
+        'chemical/x-csml' => ['csml'],
+        'chemical/x-xyz' => ['xyz'],
+        'flv-application/octet-stream' => ['flv'],
+        'font/collection' => ['ttc'],
+        'font/otf' => ['otf'],
+        'font/ttf' => ['ttf'],
+        'font/woff' => ['woff', 'woff2'],
+        'font/woff2' => ['woff2'],
+        'image/bmp' => ['bmp', 'dib'],
+        'image/cdr' => ['cdr'],
+        'image/cgm' => ['cgm'],
+        'image/emf' => ['emf'],
+        'image/fax-g3' => ['g3'],
+        'image/fits' => ['fits'],
+        'image/g3fax' => ['g3'],
+        'image/gif' => ['gif'],
+        'image/heic' => ['heic', 'heif'],
+        'image/heic-sequence' => ['heic', 'heif'],
+        'image/heif' => ['heic', 'heif'],
+        'image/heif-sequence' => ['heic', 'heif'],
+        'image/ico' => ['ico'],
+        'image/icon' => ['ico'],
+        'image/ief' => ['ief'],
+        'image/jp2' => ['jp2', 'jpg2'],
+        'image/jpeg' => ['jpeg', 'jpg', 'jpe'],
+        'image/jpeg2000' => ['jp2', 'jpg2'],
+        'image/jpeg2000-image' => ['jp2', 'jpg2'],
+        'image/jpm' => ['jpm', 'jpgm'],
+        'image/jpx' => ['jpf', 'jpx'],
+        'image/ktx' => ['ktx'],
+        'image/openraster' => ['ora'],
+        'image/pdf' => ['pdf'],
+        'image/photoshop' => ['psd'],
+        'image/pjpeg' => ['jpeg', 'jpg', 'jpe'],
+        'image/png' => ['png'],
+        'image/prs.btif' => ['btif'],
+        'image/psd' => ['psd'],
+        'image/rle' => ['rle'],
+        'image/sgi' => ['sgi'],
+        'image/svg+xml' => ['svg', 'svgz'],
+        'image/svg+xml-compressed' => ['svgz'],
+        'image/tiff' => ['tiff', 'tif'],
+        'image/vnd.adobe.photoshop' => ['psd'],
+        'image/vnd.dece.graphic' => ['uvi', 'uvvi', 'uvg', 'uvvg'],
+        'image/vnd.djvu' => ['djvu', 'djv'],
+        'image/vnd.djvu+multipage' => ['djvu', 'djv'],
+        'image/vnd.dvb.subtitle' => ['sub'],
+        'image/vnd.dwg' => ['dwg'],
+        'image/vnd.dxf' => ['dxf'],
+        'image/vnd.fastbidsheet' => ['fbs'],
+        'image/vnd.fpx' => ['fpx'],
+        'image/vnd.fst' => ['fst'],
+        'image/vnd.fujixerox.edmics-mmr' => ['mmr'],
+        'image/vnd.fujixerox.edmics-rlc' => ['rlc'],
+        'image/vnd.microsoft.icon' => ['ico'],
+        'image/vnd.ms-modi' => ['mdi'],
+        'image/vnd.ms-photo' => ['wdp'],
+        'image/vnd.net-fpx' => ['npx'],
+        'image/vnd.rn-realpix' => ['rp'],
+        'image/vnd.wap.wbmp' => ['wbmp'],
+        'image/vnd.xiff' => ['xif'],
+        'image/vnd.zbrush.pcx' => ['pcx'],
+        'image/webp' => ['webp'],
+        'image/wmf' => ['wmf'],
+        'image/x-3ds' => ['3ds'],
+        'image/x-adobe-dng' => ['dng'],
+        'image/x-applix-graphics' => ['ag'],
+        'image/x-bmp' => ['bmp', 'dib'],
+        'image/x-bzeps' => ['eps.bz2', 'epsi.bz2', 'epsf.bz2'],
+        'image/x-canon-cr2' => ['cr2'],
+        'image/x-canon-crw' => ['crw'],
+        'image/x-cdr' => ['cdr'],
+        'image/x-cmu-raster' => ['ras'],
+        'image/x-cmx' => ['cmx'],
+        'image/x-compressed-xcf' => ['xcf.gz', 'xcf.bz2'],
+        'image/x-dds' => ['dds'],
+        'image/x-djvu' => ['djvu', 'djv'],
+        'image/x-emf' => ['emf'],
+        'image/x-eps' => ['eps', 'epsi', 'epsf'],
+        'image/x-exr' => ['exr'],
+        'image/x-fits' => ['fits'],
+        'image/x-freehand' => ['fh', 'fhc', 'fh4', 'fh5', 'fh7'],
+        'image/x-fuji-raf' => ['raf'],
+        'image/x-gimp-gbr' => ['gbr'],
+        'image/x-gimp-gih' => ['gih'],
+        'image/x-gimp-pat' => ['pat'],
+        'image/x-gzeps' => ['eps.gz', 'epsi.gz', 'epsf.gz'],
+        'image/x-icb' => ['tga', 'icb', 'tpic', 'vda', 'vst'],
+        'image/x-icns' => ['icns'],
+        'image/x-ico' => ['ico'],
+        'image/x-icon' => ['ico'],
+        'image/x-iff' => ['iff', 'ilbm', 'lbm'],
+        'image/x-ilbm' => ['iff', 'ilbm', 'lbm'],
+        'image/x-jng' => ['jng'],
+        'image/x-jp2-codestream' => ['j2c', 'j2k', 'jpc'],
+        'image/x-jpeg2000-image' => ['jp2', 'jpg2'],
+        'image/x-kodak-dcr' => ['dcr'],
+        'image/x-kodak-k25' => ['k25'],
+        'image/x-kodak-kdc' => ['kdc'],
+        'image/x-lwo' => ['lwo', 'lwob'],
+        'image/x-lws' => ['lws'],
+        'image/x-macpaint' => ['pntg'],
+        'image/x-minolta-mrw' => ['mrw'],
+        'image/x-mrsid-image' => ['sid'],
+        'image/x-ms-bmp' => ['bmp', 'dib'],
+        'image/x-msod' => ['msod'],
+        'image/x-nikon-nef' => ['nef'],
+        'image/x-olympus-orf' => ['orf'],
+        'image/x-panasonic-raw' => ['raw'],
+        'image/x-panasonic-raw2' => ['rw2'],
+        'image/x-panasonic-rw' => ['raw'],
+        'image/x-panasonic-rw2' => ['rw2'],
+        'image/x-pcx' => ['pcx'],
+        'image/x-pentax-pef' => ['pef'],
+        'image/x-photo-cd' => ['pcd'],
+        'image/x-photoshop' => ['psd'],
+        'image/x-pict' => ['pic', 'pct', 'pict', 'pict1', 'pict2'],
+        'image/x-portable-anymap' => ['pnm'],
+        'image/x-portable-bitmap' => ['pbm'],
+        'image/x-portable-graymap' => ['pgm'],
+        'image/x-portable-pixmap' => ['ppm'],
+        'image/x-psd' => ['psd'],
+        'image/x-quicktime' => ['qtif', 'qif'],
+        'image/x-rgb' => ['rgb'],
+        'image/x-sgi' => ['sgi'],
+        'image/x-sigma-x3f' => ['x3f'],
+        'image/x-skencil' => ['sk', 'sk1'],
+        'image/x-sony-arw' => ['arw'],
+        'image/x-sony-sr2' => ['sr2'],
+        'image/x-sony-srf' => ['srf'],
+        'image/x-sun-raster' => ['sun'],
+        'image/x-tga' => ['tga', 'icb', 'tpic', 'vda', 'vst'],
+        'image/x-win-bitmap' => ['cur'],
+        'image/x-win-metafile' => ['wmf'],
+        'image/x-wmf' => ['wmf'],
+        'image/x-xbitmap' => ['xbm'],
+        'image/x-xcf' => ['xcf'],
+        'image/x-xfig' => ['fig'],
+        'image/x-xpixmap' => ['xpm'],
+        'image/x-xpm' => ['xpm'],
+        'image/x-xwindowdump' => ['xwd'],
+        'image/x.djvu' => ['djvu', 'djv'],
+        'message/rfc822' => ['eml', 'mime'],
+        'model/iges' => ['igs', 'iges'],
+        'model/mesh' => ['msh', 'mesh', 'silo'],
+        'model/stl' => ['stl'],
+        'model/vnd.collada+xml' => ['dae'],
+        'model/vnd.dwf' => ['dwf'],
+        'model/vnd.gdl' => ['gdl'],
+        'model/vnd.gtw' => ['gtw'],
+        'model/vnd.mts' => ['mts'],
+        'model/vnd.vtu' => ['vtu'],
+        'model/vrml' => ['wrl', 'vrml', 'vrm'],
+        'model/x.stl-ascii' => ['stl'],
+        'model/x.stl-binary' => ['stl'],
+        'model/x3d+binary' => ['x3db', 'x3dbz'],
+        'model/x3d+vrml' => ['x3dv', 'x3dvz'],
+        'model/x3d+xml' => ['x3d', 'x3dz'],
+        'text/cache-manifest' => ['appcache', 'manifest'],
+        'text/calendar' => ['ics', 'ifb', 'vcs'],
+        'text/css' => ['css'],
+        'text/csv' => ['csv'],
+        'text/csv-schema' => ['csvs'],
+        'text/directory' => ['vcard', 'vcf', 'vct', 'gcrd'],
+        'text/ecmascript' => ['es'],
+        'text/gedcom' => ['ged', 'gedcom'],
+        'text/google-video-pointer' => ['gvp'],
+        'text/html' => ['html', 'htm'],
+        'text/ico' => ['ico'],
+        'text/javascript' => ['js', 'jsm', 'mjs'],
+        'text/markdown' => ['md', 'mkd', 'markdown'],
+        'text/mathml' => ['mml'],
+        'text/n3' => ['n3'],
+        'text/plain' => ['txt', 'text', 'conf', 'def', 'list', 'log', 'in', 'asc'],
+        'text/prs.lines.tag' => ['dsc'],
+        'text/rdf' => ['rdf', 'rdfs', 'owl'],
+        'text/richtext' => ['rtx'],
+        'text/rss' => ['rss'],
+        'text/rtf' => ['rtf'],
+        'text/rust' => ['rs'],
+        'text/sgml' => ['sgml', 'sgm'],
+        'text/spreadsheet' => ['sylk', 'slk'],
+        'text/tab-separated-values' => ['tsv'],
+        'text/troff' => ['t', 'tr', 'roff', 'man', 'me', 'ms'],
+        'text/turtle' => ['ttl'],
+        'text/uri-list' => ['uri', 'uris', 'urls'],
+        'text/vcard' => ['vcard', 'vcf', 'vct', 'gcrd'],
+        'text/vnd.curl' => ['curl'],
+        'text/vnd.curl.dcurl' => ['dcurl'],
+        'text/vnd.curl.mcurl' => ['mcurl'],
+        'text/vnd.curl.scurl' => ['scurl'],
+        'text/vnd.dvb.subtitle' => ['sub'],
+        'text/vnd.fly' => ['fly'],
+        'text/vnd.fmi.flexstor' => ['flx'],
+        'text/vnd.graphviz' => ['gv', 'dot'],
+        'text/vnd.in3d.3dml' => ['3dml'],
+        'text/vnd.in3d.spot' => ['spot'],
+        'text/vnd.qt.linguist' => ['ts'],
+        'text/vnd.rn-realtext' => ['rt'],
+        'text/vnd.sun.j2me.app-descriptor' => ['jad'],
+        'text/vnd.trolltech.linguist' => ['ts'],
+        'text/vnd.wap.wml' => ['wml'],
+        'text/vnd.wap.wmlscript' => ['wmls'],
+        'text/vtt' => ['vtt'],
+        'text/x-adasrc' => ['adb', 'ads'],
+        'text/x-asm' => ['s', 'asm'],
+        'text/x-bibtex' => ['bib'],
+        'text/x-c' => ['c', 'cc', 'cxx', 'cpp', 'h', 'hh', 'dic'],
+        'text/x-c++hdr' => ['hh', 'hp', 'hpp', 'h++', 'hxx'],
+        'text/x-c++src' => ['cpp', 'cxx', 'cc', 'C', 'c++'],
+        'text/x-chdr' => ['h'],
+        'text/x-cmake' => ['cmake'],
+        'text/x-cobol' => ['cbl', 'cob'],
+        'text/x-comma-separated-values' => ['csv'],
+        'text/x-csharp' => ['cs'],
+        'text/x-csrc' => ['c'],
+        'text/x-csv' => ['csv'],
+        'text/x-dbus-service' => ['service'],
+        'text/x-dcl' => ['dcl'],
+        'text/x-diff' => ['diff', 'patch'],
+        'text/x-dsl' => ['dsl'],
+        'text/x-dsrc' => ['d', 'di'],
+        'text/x-dtd' => ['dtd'],
+        'text/x-eiffel' => ['e', 'eif'],
+        'text/x-emacs-lisp' => ['el'],
+        'text/x-erlang' => ['erl'],
+        'text/x-fortran' => ['f', 'for', 'f77', 'f90', 'f95'],
+        'text/x-genie' => ['gs'],
+        'text/x-gettext-translation' => ['po'],
+        'text/x-gettext-translation-template' => ['pot'],
+        'text/x-gherkin' => ['feature'],
+        'text/x-go' => ['go'],
+        'text/x-google-video-pointer' => ['gvp'],
+        'text/x-haskell' => ['hs'],
+        'text/x-idl' => ['idl'],
+        'text/x-imelody' => ['imy', 'ime'],
+        'text/x-iptables' => ['iptables'],
+        'text/x-java' => ['java'],
+        'text/x-java-source' => ['java'],
+        'text/x-ldif' => ['ldif'],
+        'text/x-lilypond' => ['ly'],
+        'text/x-literate-haskell' => ['lhs'],
+        'text/x-log' => ['log'],
+        'text/x-lua' => ['lua'],
+        'text/x-lyx' => ['lyx'],
+        'text/x-makefile' => ['mk', 'mak'],
+        'text/x-markdown' => ['md', 'mkd', 'markdown'],
+        'text/x-matlab' => ['m'],
+        'text/x-microdvd' => ['sub'],
+        'text/x-moc' => ['moc'],
+        'text/x-modelica' => ['mo'],
+        'text/x-mof' => ['mof'],
+        'text/x-mpsub' => ['sub'],
+        'text/x-mrml' => ['mrml', 'mrl'],
+        'text/x-ms-regedit' => ['reg'],
+        'text/x-mup' => ['mup', 'not'],
+        'text/x-nfo' => ['nfo'],
+        'text/x-objcsrc' => ['m'],
+        'text/x-ocaml' => ['ml', 'mli'],
+        'text/x-ocl' => ['ocl'],
+        'text/x-octave' => ['m'],
+        'text/x-ooc' => ['ooc'],
+        'text/x-opencl-src' => ['cl'],
+        'text/x-opml' => ['opml'],
+        'text/x-opml+xml' => ['opml'],
+        'text/x-pascal' => ['p', 'pas'],
+        'text/x-patch' => ['diff', 'patch'],
+        'text/x-perl' => ['pl', 'PL', 'pm', 'al', 'perl', 'pod', 't'],
+        'text/x-po' => ['po'],
+        'text/x-pot' => ['pot'],
+        'text/x-python' => ['py', 'pyx', 'wsgi'],
+        'text/x-python3' => ['py', 'py3', 'py3x'],
+        'text/x-qml' => ['qml', 'qmltypes', 'qmlproject'],
+        'text/x-reject' => ['rej'],
+        'text/x-rpm-spec' => ['spec'],
+        'text/x-sass' => ['sass'],
+        'text/x-scala' => ['scala'],
+        'text/x-scheme' => ['scm', 'ss'],
+        'text/x-scss' => ['scss'],
+        'text/x-setext' => ['etx'],
+        'text/x-sfv' => ['sfv'],
+        'text/x-sh' => ['sh'],
+        'text/x-sql' => ['sql'],
+        'text/x-ssa' => ['ssa', 'ass'],
+        'text/x-subviewer' => ['sub'],
+        'text/x-svhdr' => ['svh'],
+        'text/x-svsrc' => ['sv'],
+        'text/x-systemd-unit' => ['automount', 'device', 'mount', 'path', 'scope', 'service', 'slice', 'socket', 'swap', 'target', 'timer'],
+        'text/x-tcl' => ['tcl', 'tk'],
+        'text/x-tex' => ['tex', 'ltx', 'sty', 'cls', 'dtx', 'ins', 'latex'],
+        'text/x-texinfo' => ['texi', 'texinfo'],
+        'text/x-troff' => ['tr', 'roff', 't'],
+        'text/x-troff-me' => ['me'],
+        'text/x-troff-mm' => ['mm'],
+        'text/x-troff-ms' => ['ms'],
+        'text/x-twig' => ['twig'],
+        'text/x-txt2tags' => ['t2t'],
+        'text/x-uil' => ['uil'],
+        'text/x-uuencode' => ['uu', 'uue'],
+        'text/x-vala' => ['vala', 'vapi'],
+        'text/x-vcalendar' => ['vcs', 'ics'],
+        'text/x-vcard' => ['vcf', 'vcard', 'vct', 'gcrd'],
+        'text/x-verilog' => ['v'],
+        'text/x-vhdl' => ['vhd', 'vhdl'],
+        'text/x-xmi' => ['xmi'],
+        'text/x-xslfo' => ['fo', 'xslfo'],
+        'text/x-yaml' => ['yaml', 'yml'],
+        'text/x.gcode' => ['gcode'],
+        'text/xml' => ['xml', 'xbl', 'xsd', 'rng'],
+        'text/xml-external-parsed-entity' => ['ent'],
+        'text/yaml' => ['yaml', 'yml'],
+        'video/3gp' => ['3gp', '3gpp', '3ga'],
+        'video/3gpp' => ['3gp', '3gpp', '3ga'],
+        'video/3gpp-encrypted' => ['3gp', '3gpp', '3ga'],
+        'video/3gpp2' => ['3g2', '3gp2', '3gpp2'],
+        'video/annodex' => ['axv'],
+        'video/avi' => ['avi', 'avf', 'divx'],
+        'video/divx' => ['avi', 'avf', 'divx'],
+        'video/dv' => ['dv'],
+        'video/fli' => ['fli', 'flc'],
+        'video/flv' => ['flv'],
+        'video/h261' => ['h261'],
+        'video/h263' => ['h263'],
+        'video/h264' => ['h264'],
+        'video/jpeg' => ['jpgv'],
+        'video/jpm' => ['jpm', 'jpgm'],
+        'video/mj2' => ['mj2', 'mjp2'],
+        'video/mp2t' => ['m2t', 'm2ts', 'ts', 'mts', 'cpi', 'clpi', 'mpl', 'mpls', 'bdm', 'bdmv'],
+        'video/mp4' => ['mp4', 'mp4v', 'mpg4', 'm4v', 'f4v', 'lrv'],
+        'video/mp4v-es' => ['mp4', 'm4v', 'f4v', 'lrv'],
+        'video/mpeg' => ['mpeg', 'mpg', 'mpe', 'm1v', 'm2v', 'mp2', 'vob'],
+        'video/mpeg-system' => ['mpeg', 'mpg', 'mp2', 'mpe', 'vob'],
+        'video/msvideo' => ['avi', 'avf', 'divx'],
+        'video/ogg' => ['ogv', 'ogg'],
+        'video/quicktime' => ['qt', 'mov', 'moov', 'qtvr'],
+        'video/vivo' => ['viv', 'vivo'],
+        'video/vnd.dece.hd' => ['uvh', 'uvvh'],
+        'video/vnd.dece.mobile' => ['uvm', 'uvvm'],
+        'video/vnd.dece.pd' => ['uvp', 'uvvp'],
+        'video/vnd.dece.sd' => ['uvs', 'uvvs'],
+        'video/vnd.dece.video' => ['uvv', 'uvvv'],
+        'video/vnd.divx' => ['avi', 'avf', 'divx'],
+        'video/vnd.dvb.file' => ['dvb'],
+        'video/vnd.fvt' => ['fvt'],
+        'video/vnd.mpegurl' => ['mxu', 'm4u', 'm1u'],
+        'video/vnd.ms-playready.media.pyv' => ['pyv'],
+        'video/vnd.rn-realvideo' => ['rv', 'rvx'],
+        'video/vnd.uvvu.mp4' => ['uvu', 'uvvu'],
+        'video/vnd.vivo' => ['viv', 'vivo'],
+        'video/webm' => ['webm'],
+        'video/x-anim' => ['anim[1-9j]'],
+        'video/x-annodex' => ['axv'],
+        'video/x-avi' => ['avi', 'avf', 'divx'],
+        'video/x-f4v' => ['f4v'],
+        'video/x-fli' => ['fli', 'flc'],
+        'video/x-flic' => ['fli', 'flc'],
+        'video/x-flv' => ['flv'],
+        'video/x-javafx' => ['fxm'],
+        'video/x-m4v' => ['m4v', 'mp4', 'f4v', 'lrv'],
+        'video/x-matroska' => ['mkv', 'mk3d', 'mks'],
+        'video/x-matroska-3d' => ['mk3d'],
+        'video/x-mjpeg' => ['mjpeg', 'mjpg'],
+        'video/x-mng' => ['mng'],
+        'video/x-mpeg' => ['mpeg', 'mpg', 'mp2', 'mpe', 'vob'],
+        'video/x-mpeg-system' => ['mpeg', 'mpg', 'mp2', 'mpe', 'vob'],
+        'video/x-mpeg2' => ['mpeg', 'mpg', 'mp2', 'mpe', 'vob'],
+        'video/x-mpegurl' => ['m1u', 'm4u', 'mxu'],
+        'video/x-ms-asf' => ['asf', 'asx'],
+        'video/x-ms-asf-plugin' => ['asf'],
+        'video/x-ms-vob' => ['vob'],
+        'video/x-ms-wax' => ['asx', 'wax', 'wvx', 'wmx'],
+        'video/x-ms-wm' => ['wm', 'asf'],
+        'video/x-ms-wmv' => ['wmv'],
+        'video/x-ms-wmx' => ['wmx', 'asx', 'wax', 'wvx'],
+        'video/x-ms-wvx' => ['wvx', 'asx', 'wax', 'wmx'],
+        'video/x-msvideo' => ['avi', 'avf', 'divx'],
+        'video/x-nsv' => ['nsv'],
+        'video/x-ogg' => ['ogv', 'ogg'],
+        'video/x-ogm' => ['ogm'],
+        'video/x-ogm+ogg' => ['ogm'],
+        'video/x-real-video' => ['rv', 'rvx'],
+        'video/x-sgi-movie' => ['movie'],
+        'video/x-smv' => ['smv'],
+        'video/x-theora' => ['ogg'],
+        'video/x-theora+ogg' => ['ogg'],
+        'x-conference/x-cooltalk' => ['ice'],
+        'x-epoc/x-sisx-app' => ['sisx'],
+        'zz-application/zz-winassoc-123' => ['123', 'wk1', 'wk3', 'wk4', 'wks'],
+        'zz-application/zz-winassoc-cab' => ['cab'],
+        'zz-application/zz-winassoc-cdr' => ['cdr'],
+        'zz-application/zz-winassoc-doc' => ['doc'],
+        'zz-application/zz-winassoc-hlp' => ['hlp'],
+        'zz-application/zz-winassoc-mdb' => ['mdb'],
+        'zz-application/zz-winassoc-uu' => ['uue'],
+        'zz-application/zz-winassoc-xls' => ['xls', 'xlc', 'xll', 'xlm', 'xlw', 'xla', 'xlt', 'xld'],
+    ];
+
+    private static $reverseMap = [
+        '32x' => ['application/x-genesis-32x-rom'],
+        '3dml' => ['text/vnd.in3d.3dml'],
+        '3ds' => ['image/x-3ds'],
+        '3g2' => ['audio/3gpp2', 'video/3gpp2'],
+        '3ga' => ['audio/3gpp', 'audio/3gpp-encrypted', 'audio/x-rn-3gpp-amr', 'audio/x-rn-3gpp-amr-encrypted', 'audio/x-rn-3gpp-amr-wb', 'audio/x-rn-3gpp-amr-wb-encrypted', 'video/3gp', 'video/3gpp', 'video/3gpp-encrypted'],
+        '3gp' => ['audio/3gpp', 'audio/3gpp-encrypted', 'audio/x-rn-3gpp-amr', 'audio/x-rn-3gpp-amr-encrypted', 'audio/x-rn-3gpp-amr-wb', 'audio/x-rn-3gpp-amr-wb-encrypted', 'video/3gp', 'video/3gpp', 'video/3gpp-encrypted'],
+        '3gp2' => ['audio/3gpp2', 'video/3gpp2'],
+        '3gpp' => ['audio/3gpp', 'audio/3gpp-encrypted', 'audio/x-rn-3gpp-amr', 'audio/x-rn-3gpp-amr-encrypted', 'audio/x-rn-3gpp-amr-wb', 'audio/x-rn-3gpp-amr-wb-encrypted', 'video/3gp', 'video/3gpp', 'video/3gpp-encrypted'],
+        '3gpp2' => ['audio/3gpp2', 'video/3gpp2'],
+        '7z' => ['application/x-7z-compressed'],
+        'BLEND' => ['application/x-blender'],
+        'C' => ['text/x-c++src'],
+        'PAR2' => ['application/x-par2'],
+        'PL' => ['application/x-perl', 'text/x-perl'],
+        'Z' => ['application/x-compress'],
+        'a' => ['application/x-archive'],
+        'a26' => ['application/x-atari-2600-rom'],
+        'a78' => ['application/x-atari-7800-rom'],
+        'aa' => ['audio/vnd.audible', 'audio/vnd.audible.aax', 'audio/x-pn-audibleaudio'],
+        'aab' => ['application/x-authorware-bin'],
+        'aac' => ['audio/aac', 'audio/x-aac', 'audio/x-hx-aac-adts'],
+        'aam' => ['application/x-authorware-map'],
+        'aas' => ['application/x-authorware-seg'],
+        'aax' => ['audio/vnd.audible', 'audio/vnd.audible.aax', 'audio/x-pn-audibleaudio'],
+        'abw' => ['application/x-abiword'],
+        'abw.CRASHED' => ['application/x-abiword'],
+        'abw.gz' => ['application/x-abiword'],
+        'ac' => ['application/pkix-attr-cert'],
+        'ac3' => ['audio/ac3'],
+        'acc' => ['application/vnd.americandynamics.acc'],
+        'ace' => ['application/x-ace', 'application/x-ace-compressed'],
+        'acu' => ['application/vnd.acucobol'],
+        'acutc' => ['application/vnd.acucorp'],
+        'adb' => ['text/x-adasrc'],
+        'adf' => ['application/x-amiga-disk-format'],
+        'adp' => ['audio/adpcm'],
+        'ads' => ['text/x-adasrc'],
+        'adts' => ['audio/aac', 'audio/x-aac', 'audio/x-hx-aac-adts'],
+        'aep' => ['application/vnd.audiograph'],
+        'afm' => ['application/x-font-afm', 'application/x-font-type1'],
+        'afp' => ['application/vnd.ibm.modcap'],
+        'ag' => ['image/x-applix-graphics'],
+        'agb' => ['application/x-gba-rom'],
+        'ahead' => ['application/vnd.ahead.space'],
+        'ai' => ['application/illustrator', 'application/postscript', 'application/vnd.adobe.illustrator'],
+        'aif' => ['audio/x-aiff'],
+        'aifc' => ['audio/x-aifc', 'audio/x-aiff', 'audio/x-aiffc'],
+        'aiff' => ['audio/x-aiff'],
+        'aiffc' => ['audio/x-aifc', 'audio/x-aiffc'],
+        'air' => ['application/vnd.adobe.air-application-installer-package+zip'],
+        'ait' => ['application/vnd.dvb.ait'],
+        'al' => ['application/x-perl', 'text/x-perl'],
+        'alz' => ['application/x-alz'],
+        'ami' => ['application/vnd.amiga.ami'],
+        'amr' => ['audio/amr', 'audio/amr-encrypted'],
+        'amz' => ['audio/x-amzxml'],
+        'ani' => ['application/x-navi-animation'],
+        'anim[1-9j]' => ['video/x-anim'],
+        'anx' => ['application/annodex', 'application/x-annodex'],
+        'ape' => ['audio/x-ape'],
+        'apk' => ['application/vnd.android.package-archive'],
+        'appcache' => ['text/cache-manifest'],
+        'appimage' => ['application/vnd.appimage', 'application/x-iso9660-appimage'],
+        'application' => ['application/x-ms-application'],
+        'apr' => ['application/vnd.lotus-approach'],
+        'aps' => ['application/postscript'],
+        'ar' => ['application/x-archive'],
+        'arc' => ['application/x-freearc'],
+        'arj' => ['application/x-arj'],
+        'arw' => ['image/x-sony-arw'],
+        'as' => ['application/x-applix-spreadsheet'],
+        'asc' => ['application/pgp', 'application/pgp-encrypted', 'application/pgp-keys', 'application/pgp-signature', 'text/plain'],
+        'asf' => ['application/vnd.ms-asf', 'video/x-ms-asf', 'video/x-ms-asf-plugin', 'video/x-ms-wm'],
+        'asm' => ['text/x-asm'],
+        'aso' => ['application/vnd.accpac.simply.aso'],
+        'asp' => ['application/x-asp'],
+        'ass' => ['audio/aac', 'audio/x-aac', 'audio/x-hx-aac-adts', 'text/x-ssa'],
+        'asx' => ['application/x-ms-asx', 'audio/x-ms-asx', 'video/x-ms-asf', 'video/x-ms-wax', 'video/x-ms-wmx', 'video/x-ms-wvx'],
+        'atc' => ['application/vnd.acucorp'],
+        'atom' => ['application/atom+xml'],
+        'atomcat' => ['application/atomcat+xml'],
+        'atomsvc' => ['application/atomsvc+xml'],
+        'atx' => ['application/vnd.antix.game-component'],
+        'au' => ['audio/basic'],
+        'automount' => ['text/x-systemd-unit'],
+        'avf' => ['video/avi', 'video/divx', 'video/msvideo', 'video/vnd.divx', 'video/x-avi', 'video/x-msvideo'],
+        'avi' => ['video/avi', 'video/divx', 'video/msvideo', 'video/vnd.divx', 'video/x-avi', 'video/x-msvideo'],
+        'aw' => ['application/applixware', 'application/x-applix-word'],
+        'awb' => ['audio/amr-wb', 'audio/amr-wb-encrypted'],
+        'awk' => ['application/x-awk'],
+        'axa' => ['audio/annodex', 'audio/x-annodex'],
+        'axv' => ['video/annodex', 'video/x-annodex'],
+        'azf' => ['application/vnd.airzip.filesecure.azf'],
+        'azs' => ['application/vnd.airzip.filesecure.azs'],
+        'azw' => ['application/vnd.amazon.ebook'],
+        'bak' => ['application/x-trash'],
+        'bat' => ['application/x-msdownload'],
+        'bcpio' => ['application/x-bcpio'],
+        'bdf' => ['application/x-font-bdf'],
+        'bdm' => ['application/vnd.syncml.dm+wbxml', 'video/mp2t'],
+        'bdmv' => ['video/mp2t'],
+        'bed' => ['application/vnd.realvnc.bed'],
+        'bh2' => ['application/vnd.fujitsu.oasysprs'],
+        'bib' => ['text/x-bibtex'],
+        'bin' => ['application/octet-stream', 'application/x-saturn-rom', 'application/x-sega-cd-rom'],
+        'blb' => ['application/x-blorb'],
+        'blend' => ['application/x-blender'],
+        'blender' => ['application/x-blender'],
+        'blorb' => ['application/x-blorb'],
+        'bmi' => ['application/vnd.bmi'],
+        'bmp' => ['image/bmp', 'image/x-bmp', 'image/x-ms-bmp'],
+        'book' => ['application/vnd.framemaker'],
+        'box' => ['application/vnd.previewsystems.box'],
+        'boz' => ['application/x-bzip2'],
+        'bpk' => ['application/octet-stream'],
+        'bsdiff' => ['application/x-bsdiff'],
+        'btif' => ['image/prs.btif'],
+        'bz' => ['application/x-bzip', 'application/x-bzip2'],
+        'bz2' => ['application/x-bz2', 'application/x-bzip', 'application/x-bzip2'],
+        'c' => ['text/x-c', 'text/x-csrc'],
+        'c++' => ['text/x-c++src'],
+        'c11amc' => ['application/vnd.cluetrust.cartomobile-config'],
+        'c11amz' => ['application/vnd.cluetrust.cartomobile-config-pkg'],
+        'c4d' => ['application/vnd.clonk.c4group'],
+        'c4f' => ['application/vnd.clonk.c4group'],
+        'c4g' => ['application/vnd.clonk.c4group'],
+        'c4p' => ['application/vnd.clonk.c4group'],
+        'c4u' => ['application/vnd.clonk.c4group'],
+        'cab' => ['application/vnd.ms-cab-compressed', 'zz-application/zz-winassoc-cab'],
+        'caf' => ['audio/x-caf'],
+        'cap' => ['application/pcap', 'application/vnd.tcpdump.pcap', 'application/x-pcap'],
+        'car' => ['application/vnd.curl.car'],
+        'cat' => ['application/vnd.ms-pki.seccat'],
+        'cb7' => ['application/x-cb7', 'application/x-cbr'],
+        'cba' => ['application/x-cbr'],
+        'cbl' => ['text/x-cobol'],
+        'cbr' => ['application/vnd.comicbook-rar', 'application/x-cbr'],
+        'cbt' => ['application/x-cbr', 'application/x-cbt'],
+        'cbz' => ['application/vnd.comicbook+zip', 'application/x-cbr', 'application/x-cbz'],
+        'cc' => ['text/x-c', 'text/x-c++src'],
+        'ccmx' => ['application/x-ccmx'],
+        'cct' => ['application/x-director'],
+        'ccxml' => ['application/ccxml+xml'],
+        'cdbcmsg' => ['application/vnd.contact.cmsg'],
+        'cdf' => ['application/x-netcdf'],
+        'cdkey' => ['application/vnd.mediastation.cdkey'],
+        'cdmia' => ['application/cdmi-capability'],
+        'cdmic' => ['application/cdmi-container'],
+        'cdmid' => ['application/cdmi-domain'],
+        'cdmio' => ['application/cdmi-object'],
+        'cdmiq' => ['application/cdmi-queue'],
+        'cdr' => ['application/cdr', 'application/coreldraw', 'application/vnd.corel-draw', 'application/x-cdr', 'application/x-coreldraw', 'image/cdr', 'image/x-cdr', 'zz-application/zz-winassoc-cdr'],
+        'cdx' => ['chemical/x-cdx'],
+        'cdxml' => ['application/vnd.chemdraw+xml'],
+        'cdy' => ['application/vnd.cinderella'],
+        'cer' => ['application/pkix-cert'],
+        'cert' => ['application/x-x509-ca-cert'],
+        'cfs' => ['application/x-cfs-compressed'],
+        'cgb' => ['application/x-gameboy-color-rom'],
+        'cgm' => ['image/cgm'],
+        'chat' => ['application/x-chat'],
+        'chm' => ['application/vnd.ms-htmlhelp', 'application/x-chm'],
+        'chrt' => ['application/vnd.kde.kchart', 'application/x-kchart'],
+        'cif' => ['chemical/x-cif'],
+        'cii' => ['application/vnd.anser-web-certificate-issue-initiation'],
+        'cil' => ['application/vnd.ms-artgalry'],
+        'cl' => ['text/x-opencl-src'],
+        'cla' => ['application/vnd.claymore'],
+        'class' => ['application/java', 'application/java-byte-code', 'application/java-vm', 'application/x-java', 'application/x-java-class', 'application/x-java-vm'],
+        'clkk' => ['application/vnd.crick.clicker.keyboard'],
+        'clkp' => ['application/vnd.crick.clicker.palette'],
+        'clkt' => ['application/vnd.crick.clicker.template'],
+        'clkw' => ['application/vnd.crick.clicker.wordbank'],
+        'clkx' => ['application/vnd.crick.clicker'],
+        'clp' => ['application/x-msclip'],
+        'clpi' => ['video/mp2t'],
+        'cls' => ['application/x-tex', 'text/x-tex'],
+        'cmake' => ['text/x-cmake'],
+        'cmc' => ['application/vnd.cosmocaller'],
+        'cmdf' => ['chemical/x-cmdf'],
+        'cml' => ['chemical/x-cml'],
+        'cmp' => ['application/vnd.yellowriver-custom-menu'],
+        'cmx' => ['image/x-cmx'],
+        'cob' => ['text/x-cobol'],
+        'cod' => ['application/vnd.rim.cod'],
+        'coffee' => ['application/vnd.coffeescript'],
+        'com' => ['application/x-msdownload'],
+        'conf' => ['text/plain'],
+        'cpi' => ['video/mp2t'],
+        'cpio' => ['application/x-cpio'],
+        'cpio.gz' => ['application/x-cpio-compressed'],
+        'cpp' => ['text/x-c', 'text/x-c++src'],
+        'cpt' => ['application/mac-compactpro'],
+        'cr2' => ['image/x-canon-cr2'],
+        'crd' => ['application/x-mscardfile'],
+        'crdownload' => ['application/x-partial-download'],
+        'crl' => ['application/pkix-crl'],
+        'crt' => ['application/x-x509-ca-cert'],
+        'crw' => ['image/x-canon-crw'],
+        'cryptonote' => ['application/vnd.rig.cryptonote'],
+        'cs' => ['text/x-csharp'],
+        'csh' => ['application/x-csh'],
+        'csml' => ['chemical/x-csml'],
+        'csp' => ['application/vnd.commonspace'],
+        'css' => ['text/css'],
+        'cst' => ['application/x-director'],
+        'csv' => ['text/csv', 'text/x-comma-separated-values', 'text/x-csv'],
+        'csvs' => ['text/csv-schema'],
+        'cu' => ['application/cu-seeme'],
+        'cue' => ['application/x-cue'],
+        'cur' => ['image/x-win-bitmap'],
+        'curl' => ['text/vnd.curl'],
+        'cww' => ['application/prs.cww'],
+        'cxt' => ['application/x-director'],
+        'cxx' => ['text/x-c', 'text/x-c++src'],
+        'd' => ['text/x-dsrc'],
+        'dae' => ['model/vnd.collada+xml'],
+        'daf' => ['application/vnd.mobius.daf'],
+        'dar' => ['application/x-dar'],
+        'dart' => ['application/vnd.dart'],
+        'dataless' => ['application/vnd.fdsn.seed'],
+        'davmount' => ['application/davmount+xml'],
+        'dbf' => ['application/dbase', 'application/dbf', 'application/x-dbase', 'application/x-dbf'],
+        'dbk' => ['application/docbook+xml', 'application/vnd.oasis.docbook+xml', 'application/x-docbook+xml'],
+        'dc' => ['application/x-dc-rom'],
+        'dcl' => ['text/x-dcl'],
+        'dcm' => ['application/dicom'],
+        'dcr' => ['application/x-director', 'image/x-kodak-dcr'],
+        'dcurl' => ['text/vnd.curl.dcurl'],
+        'dd2' => ['application/vnd.oma.dd2+xml'],
+        'ddd' => ['application/vnd.fujixerox.ddd'],
+        'dds' => ['image/x-dds'],
+        'deb' => ['application/vnd.debian.binary-package', 'application/x-deb', 'application/x-debian-package'],
+        'def' => ['text/plain'],
+        'deploy' => ['application/octet-stream'],
+        'der' => ['application/x-x509-ca-cert'],
+        'desktop' => ['application/x-desktop', 'application/x-gnome-app-info'],
+        'device' => ['text/x-systemd-unit'],
+        'dfac' => ['application/vnd.dreamfactory'],
+        'dgc' => ['application/x-dgc-compressed'],
+        'di' => ['text/x-dsrc'],
+        'dia' => ['application/x-dia-diagram'],
+        'dib' => ['image/bmp', 'image/x-bmp', 'image/x-ms-bmp'],
+        'dic' => ['text/x-c'],
+        'diff' => ['text/x-diff', 'text/x-patch'],
+        'dir' => ['application/x-director'],
+        'dis' => ['application/vnd.mobius.dis'],
+        'dist' => ['application/octet-stream'],
+        'distz' => ['application/octet-stream'],
+        'divx' => ['video/avi', 'video/divx', 'video/msvideo', 'video/vnd.divx', 'video/x-avi', 'video/x-msvideo'],
+        'djv' => ['image/vnd.djvu', 'image/vnd.djvu+multipage', 'image/x-djvu', 'image/x.djvu'],
+        'djvu' => ['image/vnd.djvu', 'image/vnd.djvu+multipage', 'image/x-djvu', 'image/x.djvu'],
+        'dll' => ['application/x-msdownload'],
+        'dmg' => ['application/x-apple-diskimage'],
+        'dmp' => ['application/pcap', 'application/vnd.tcpdump.pcap', 'application/x-pcap'],
+        'dms' => ['application/octet-stream'],
+        'dna' => ['application/vnd.dna'],
+        'dng' => ['image/x-adobe-dng'],
+        'doc' => ['application/msword', 'application/vnd.ms-word', 'application/x-msword', 'zz-application/zz-winassoc-doc'],
+        'docbook' => ['application/docbook+xml', 'application/vnd.oasis.docbook+xml', 'application/x-docbook+xml'],
+        'docm' => ['application/vnd.ms-word.document.macroenabled.12'],
+        'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
+        'dot' => ['application/msword', 'application/msword-template', 'text/vnd.graphviz'],
+        'dotm' => ['application/vnd.ms-word.template.macroenabled.12'],
+        'dotx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.template'],
+        'dp' => ['application/vnd.osgi.dp'],
+        'dpg' => ['application/vnd.dpgraph'],
+        'dra' => ['audio/vnd.dra'],
+        'dsc' => ['text/prs.lines.tag'],
+        'dsl' => ['text/x-dsl'],
+        'dssc' => ['application/dssc+der'],
+        'dtb' => ['application/x-dtbook+xml'],
+        'dtd' => ['application/xml-dtd', 'text/x-dtd'],
+        'dts' => ['audio/vnd.dts', 'audio/x-dts'],
+        'dtshd' => ['audio/vnd.dts.hd', 'audio/x-dtshd'],
+        'dtx' => ['application/x-tex', 'text/x-tex'],
+        'dump' => ['application/octet-stream'],
+        'dv' => ['video/dv'],
+        'dvb' => ['video/vnd.dvb.file'],
+        'dvi' => ['application/x-dvi'],
+        'dvi.bz2' => ['application/x-bzdvi'],
+        'dvi.gz' => ['application/x-gzdvi'],
+        'dwf' => ['model/vnd.dwf'],
+        'dwg' => ['image/vnd.dwg'],
+        'dxf' => ['image/vnd.dxf'],
+        'dxp' => ['application/vnd.spotfire.dxp'],
+        'dxr' => ['application/x-director'],
+        'e' => ['text/x-eiffel'],
+        'ecelp4800' => ['audio/vnd.nuera.ecelp4800'],
+        'ecelp7470' => ['audio/vnd.nuera.ecelp7470'],
+        'ecelp9600' => ['audio/vnd.nuera.ecelp9600'],
+        'ecma' => ['application/ecmascript'],
+        'edm' => ['application/vnd.novadigm.edm'],
+        'edx' => ['application/vnd.novadigm.edx'],
+        'efif' => ['application/vnd.picsel'],
+        'egon' => ['application/x-egon'],
+        'ei6' => ['application/vnd.pg.osasli'],
+        'eif' => ['text/x-eiffel'],
+        'el' => ['text/x-emacs-lisp'],
+        'elc' => ['application/octet-stream'],
+        'emf' => ['application/emf', 'application/x-emf', 'application/x-msmetafile', 'image/emf', 'image/x-emf'],
+        'eml' => ['message/rfc822'],
+        'emma' => ['application/emma+xml'],
+        'emp' => ['application/vnd.emusic-emusic_package'],
+        'emz' => ['application/x-msmetafile'],
+        'ent' => ['application/xml-external-parsed-entity', 'text/xml-external-parsed-entity'],
+        'eol' => ['audio/vnd.digital-winds'],
+        'eot' => ['application/vnd.ms-fontobject'],
+        'eps' => ['application/postscript', 'image/x-eps'],
+        'eps.bz2' => ['image/x-bzeps'],
+        'eps.gz' => ['image/x-gzeps'],
+        'epsf' => ['image/x-eps'],
+        'epsf.bz2' => ['image/x-bzeps'],
+        'epsf.gz' => ['image/x-gzeps'],
+        'epsi' => ['image/x-eps'],
+        'epsi.bz2' => ['image/x-bzeps'],
+        'epsi.gz' => ['image/x-gzeps'],
+        'epub' => ['application/epub+zip'],
+        'erl' => ['text/x-erlang'],
+        'es' => ['application/ecmascript', 'text/ecmascript'],
+        'es3' => ['application/vnd.eszigno3+xml'],
+        'esa' => ['application/vnd.osgi.subsystem'],
+        'esf' => ['application/vnd.epson.esf'],
+        'et3' => ['application/vnd.eszigno3+xml'],
+        'etheme' => ['application/x-e-theme'],
+        'etx' => ['text/x-setext'],
+        'eva' => ['application/x-eva'],
+        'evy' => ['application/x-envoy'],
+        'exe' => ['application/x-ms-dos-executable', 'application/x-msdownload'],
+        'exi' => ['application/exi'],
+        'exr' => ['image/x-exr'],
+        'ext' => ['application/vnd.novadigm.ext'],
+        'ez' => ['application/andrew-inset'],
+        'ez2' => ['application/vnd.ezpix-album'],
+        'ez3' => ['application/vnd.ezpix-package'],
+        'f' => ['text/x-fortran'],
+        'f4a' => ['audio/m4a', 'audio/mp4', 'audio/x-m4a'],
+        'f4b' => ['audio/x-m4b'],
+        'f4v' => ['video/mp4', 'video/mp4v-es', 'video/x-f4v', 'video/x-m4v'],
+        'f77' => ['text/x-fortran'],
+        'f90' => ['text/x-fortran'],
+        'f95' => ['text/x-fortran'],
+        'fb2' => ['application/x-fictionbook', 'application/x-fictionbook+xml'],
+        'fb2.zip' => ['application/x-zip-compressed-fb2'],
+        'fbs' => ['image/vnd.fastbidsheet'],
+        'fcdt' => ['application/vnd.adobe.formscentral.fcdt'],
+        'fcs' => ['application/vnd.isac.fcs'],
+        'fd' => ['application/x-fd-file', 'application/x-raw-floppy-disk-image'],
+        'fdf' => ['application/vnd.fdf'],
+        'fds' => ['application/x-fds-disk'],
+        'fe_launch' => ['application/vnd.denovo.fcselayout-link'],
+        'feature' => ['text/x-gherkin'],
+        'fg5' => ['application/vnd.fujitsu.oasysgp'],
+        'fgd' => ['application/x-director'],
+        'fh' => ['image/x-freehand'],
+        'fh4' => ['image/x-freehand'],
+        'fh5' => ['image/x-freehand'],
+        'fh7' => ['image/x-freehand'],
+        'fhc' => ['image/x-freehand'],
+        'fig' => ['application/x-xfig', 'image/x-xfig'],
+        'fits' => ['image/fits', 'image/x-fits'],
+        'fl' => ['application/x-fluid'],
+        'flac' => ['audio/flac', 'audio/x-flac'],
+        'flatpak' => ['application/vnd.flatpak', 'application/vnd.xdgapp'],
+        'flatpakref' => ['application/vnd.flatpak.ref'],
+        'flatpakrepo' => ['application/vnd.flatpak.repo'],
+        'flc' => ['video/fli', 'video/x-fli', 'video/x-flic'],
+        'fli' => ['video/fli', 'video/x-fli', 'video/x-flic'],
+        'flo' => ['application/vnd.micrografx.flo'],
+        'flv' => ['video/x-flv', 'application/x-flash-video', 'flv-application/octet-stream', 'video/flv'],
+        'flw' => ['application/vnd.kde.kivio', 'application/x-kivio'],
+        'flx' => ['text/vnd.fmi.flexstor'],
+        'fly' => ['text/vnd.fly'],
+        'fm' => ['application/vnd.framemaker', 'application/x-frame'],
+        'fnc' => ['application/vnd.frogans.fnc'],
+        'fo' => ['text/x-xslfo'],
+        'fodg' => ['application/vnd.oasis.opendocument.graphics-flat-xml'],
+        'fodp' => ['application/vnd.oasis.opendocument.presentation-flat-xml'],
+        'fods' => ['application/vnd.oasis.opendocument.spreadsheet-flat-xml'],
+        'fodt' => ['application/vnd.oasis.opendocument.text-flat-xml'],
+        'for' => ['text/x-fortran'],
+        'fpx' => ['image/vnd.fpx'],
+        'frame' => ['application/vnd.framemaker'],
+        'fsc' => ['application/vnd.fsc.weblaunch'],
+        'fst' => ['image/vnd.fst'],
+        'ftc' => ['application/vnd.fluxtime.clip'],
+        'fti' => ['application/vnd.anser-web-funds-transfer-initiation'],
+        'fvt' => ['video/vnd.fvt'],
+        'fxm' => ['video/x-javafx'],
+        'fxp' => ['application/vnd.adobe.fxp'],
+        'fxpl' => ['application/vnd.adobe.fxp'],
+        'fzs' => ['application/vnd.fuzzysheet'],
+        'g2w' => ['application/vnd.geoplan'],
+        'g3' => ['image/fax-g3', 'image/g3fax'],
+        'g3w' => ['application/vnd.geospace'],
+        'gac' => ['application/vnd.groove-account'],
+        'gam' => ['application/x-tads'],
+        'gb' => ['application/x-gameboy-rom'],
+        'gba' => ['application/x-gba-rom'],
+        'gbc' => ['application/x-gameboy-color-rom'],
+        'gbr' => ['application/rpki-ghostbusters', 'image/x-gimp-gbr'],
+        'gca' => ['application/x-gca-compressed'],
+        'gcode' => ['text/x.gcode'],
+        'gcrd' => ['text/directory', 'text/vcard', 'text/x-vcard'],
+        'gdl' => ['model/vnd.gdl'],
+        'ged' => ['application/x-gedcom', 'text/gedcom'],
+        'gedcom' => ['application/x-gedcom', 'text/gedcom'],
+        'gem' => ['application/x-gtar', 'application/x-tar'],
+        'gen' => ['application/x-genesis-rom'],
+        'geo' => ['application/vnd.dynageo'],
+        'geo.json' => ['application/geo+json', 'application/vnd.geo+json'],
+        'geojson' => ['application/geo+json', 'application/vnd.geo+json'],
+        'gex' => ['application/vnd.geometry-explorer'],
+        'gf' => ['application/x-tex-gf'],
+        'gg' => ['application/x-gamegear-rom'],
+        'ggb' => ['application/vnd.geogebra.file'],
+        'ggt' => ['application/vnd.geogebra.tool'],
+        'ghf' => ['application/vnd.groove-help'],
+        'gif' => ['image/gif'],
+        'gih' => ['image/x-gimp-gih'],
+        'gim' => ['application/vnd.groove-identity-message'],
+        'glade' => ['application/x-glade'],
+        'gml' => ['application/gml+xml'],
+        'gmo' => ['application/x-gettext-translation'],
+        'gmx' => ['application/vnd.gmx'],
+        'gnc' => ['application/x-gnucash'],
+        'gnd' => ['application/gnunet-directory'],
+        'gnucash' => ['application/x-gnucash'],
+        'gnumeric' => ['application/x-gnumeric'],
+        'gnuplot' => ['application/x-gnuplot'],
+        'go' => ['text/x-go'],
+        'gp' => ['application/x-gnuplot'],
+        'gpg' => ['application/pgp', 'application/pgp-encrypted', 'application/pgp-keys', 'application/pgp-signature'],
+        'gph' => ['application/vnd.flographit'],
+        'gplt' => ['application/x-gnuplot'],
+        'gpx' => ['application/gpx', 'application/gpx+xml', 'application/x-gpx', 'application/x-gpx+xml'],
+        'gqf' => ['application/vnd.grafeq'],
+        'gqs' => ['application/vnd.grafeq'],
+        'gra' => ['application/x-graphite'],
+        'gram' => ['application/srgs'],
+        'gramps' => ['application/x-gramps-xml'],
+        'gre' => ['application/vnd.geometry-explorer'],
+        'grv' => ['application/vnd.groove-injector'],
+        'grxml' => ['application/srgs+xml'],
+        'gs' => ['text/x-genie'],
+        'gsf' => ['application/x-font-ghostscript', 'application/x-font-type1'],
+        'gsm' => ['audio/x-gsm'],
+        'gtar' => ['application/x-gtar', 'application/x-tar'],
+        'gtm' => ['application/vnd.groove-tool-message'],
+        'gtw' => ['model/vnd.gtw'],
+        'gv' => ['text/vnd.graphviz'],
+        'gvp' => ['text/google-video-pointer', 'text/x-google-video-pointer'],
+        'gxf' => ['application/gxf'],
+        'gxt' => ['application/vnd.geonext'],
+        'gz' => ['application/x-gzip', 'application/gzip'],
+        'h' => ['text/x-c', 'text/x-chdr'],
+        'h++' => ['text/x-c++hdr'],
+        'h261' => ['video/h261'],
+        'h263' => ['video/h263'],
+        'h264' => ['video/h264'],
+        'h4' => ['application/x-hdf'],
+        'h5' => ['application/x-hdf'],
+        'hal' => ['application/vnd.hal+xml'],
+        'hbci' => ['application/vnd.hbci'],
+        'hdf' => ['application/x-hdf'],
+        'hdf4' => ['application/x-hdf'],
+        'hdf5' => ['application/x-hdf'],
+        'heic' => ['image/heic', 'image/heic-sequence', 'image/heif', 'image/heif-sequence'],
+        'heif' => ['image/heic', 'image/heic-sequence', 'image/heif', 'image/heif-sequence'],
+        'hfe' => ['application/x-hfe-file', 'application/x-hfe-floppy-image'],
+        'hh' => ['text/x-c', 'text/x-c++hdr'],
+        'hlp' => ['application/winhlp', 'zz-application/zz-winassoc-hlp'],
+        'hp' => ['text/x-c++hdr'],
+        'hpgl' => ['application/vnd.hp-hpgl'],
+        'hpid' => ['application/vnd.hp-hpid'],
+        'hpp' => ['text/x-c++hdr'],
+        'hps' => ['application/vnd.hp-hps'],
+        'hqx' => ['application/stuffit', 'application/mac-binhex40'],
+        'hs' => ['text/x-haskell'],
+        'htke' => ['application/vnd.kenameaapp'],
+        'htm' => ['text/html'],
+        'html' => ['text/html'],
+        'hvd' => ['application/vnd.yamaha.hv-dic'],
+        'hvp' => ['application/vnd.yamaha.hv-voice'],
+        'hvs' => ['application/vnd.yamaha.hv-script'],
+        'hwp' => ['application/vnd.haansoft-hwp', 'application/x-hwp'],
+        'hwt' => ['application/vnd.haansoft-hwt', 'application/x-hwt'],
+        'hxx' => ['text/x-c++hdr'],
+        'i2g' => ['application/vnd.intergeo'],
+        'ica' => ['application/x-ica'],
+        'icb' => ['image/x-icb', 'image/x-tga'],
+        'icc' => ['application/vnd.iccprofile'],
+        'ice' => ['x-conference/x-cooltalk'],
+        'icm' => ['application/vnd.iccprofile'],
+        'icns' => ['image/x-icns'],
+        'ico' => ['application/ico', 'image/ico', 'image/icon', 'image/vnd.microsoft.icon', 'image/x-ico', 'image/x-icon', 'text/ico'],
+        'ics' => ['application/ics', 'text/calendar', 'text/x-vcalendar'],
+        'idl' => ['text/x-idl'],
+        'ief' => ['image/ief'],
+        'ifb' => ['text/calendar'],
+        'iff' => ['image/x-iff', 'image/x-ilbm'],
+        'ifm' => ['application/vnd.shana.informed.formdata'],
+        'iges' => ['model/iges'],
+        'igl' => ['application/vnd.igloader'],
+        'igm' => ['application/vnd.insors.igm'],
+        'igs' => ['model/iges'],
+        'igx' => ['application/vnd.micrografx.igx'],
+        'iif' => ['application/vnd.shana.informed.interchange'],
+        'ilbm' => ['image/x-iff', 'image/x-ilbm'],
+        'ime' => ['audio/imelody', 'audio/x-imelody', 'text/x-imelody'],
+        'img' => ['application/x-raw-disk-image'],
+        'img.xz' => ['application/x-raw-disk-image-xz-compressed'],
+        'imp' => ['application/vnd.accpac.simply.imp'],
+        'ims' => ['application/vnd.ms-ims'],
+        'imy' => ['audio/imelody', 'audio/x-imelody', 'text/x-imelody'],
+        'in' => ['text/plain'],
+        'ink' => ['application/inkml+xml'],
+        'inkml' => ['application/inkml+xml'],
+        'ins' => ['application/x-tex', 'text/x-tex'],
+        'install' => ['application/x-install-instructions'],
+        'iota' => ['application/vnd.astraea-software.iota'],
+        'ipfix' => ['application/ipfix'],
+        'ipk' => ['application/vnd.shana.informed.package'],
+        'iptables' => ['text/x-iptables'],
+        'ipynb' => ['application/x-ipynb+json'],
+        'irm' => ['application/vnd.ibm.rights-management'],
+        'irp' => ['application/vnd.irepository.package+xml'],
+        'iso' => ['application/x-cd-image', 'application/x-gamecube-iso-image', 'application/x-gamecube-rom', 'application/x-iso9660-image', 'application/x-saturn-rom', 'application/x-sega-cd-rom', 'application/x-wbfs', 'application/x-wia', 'application/x-wii-iso-image', 'application/x-wii-rom'],
+        'iso9660' => ['application/x-cd-image', 'application/x-iso9660-image'],
+        'it' => ['audio/x-it'],
+        'it87' => ['application/x-it87'],
+        'itp' => ['application/vnd.shana.informed.formtemplate'],
+        'ivp' => ['application/vnd.immervision-ivp'],
+        'ivu' => ['application/vnd.immervision-ivu'],
+        'j2c' => ['image/x-jp2-codestream'],
+        'j2k' => ['image/x-jp2-codestream'],
+        'jad' => ['text/vnd.sun.j2me.app-descriptor'],
+        'jam' => ['application/vnd.jam'],
+        'jar' => ['application/x-java-archive', 'application/java-archive', 'application/x-jar'],
+        'java' => ['text/x-java', 'text/x-java-source'],
+        'jceks' => ['application/x-java-jce-keystore'],
+        'jisp' => ['application/vnd.jisp'],
+        'jks' => ['application/x-java-keystore'],
+        'jlt' => ['application/vnd.hp-jlyt'],
+        'jng' => ['image/x-jng'],
+        'jnlp' => ['application/x-java-jnlp-file'],
+        'joda' => ['application/vnd.joost.joda-archive'],
+        'jp2' => ['image/jp2', 'image/jpeg2000', 'image/jpeg2000-image', 'image/x-jpeg2000-image'],
+        'jpc' => ['image/x-jp2-codestream'],
+        'jpe' => ['image/jpeg', 'image/pjpeg'],
+        'jpeg' => ['image/jpeg', 'image/pjpeg'],
+        'jpf' => ['image/jpx'],
+        'jpg' => ['image/jpeg', 'image/pjpeg'],
+        'jpg2' => ['image/jp2', 'image/jpeg2000', 'image/jpeg2000-image', 'image/x-jpeg2000-image'],
+        'jpgm' => ['image/jpm', 'video/jpm'],
+        'jpgv' => ['video/jpeg'],
+        'jpm' => ['image/jpm', 'video/jpm'],
+        'jpr' => ['application/x-jbuilder-project'],
+        'jpx' => ['application/x-jbuilder-project', 'image/jpx'],
+        'jrd' => ['application/jrd+json'],
+        'js' => ['text/javascript', 'application/javascript', 'application/x-javascript'],
+        'jsm' => ['application/javascript', 'application/x-javascript', 'text/javascript'],
+        'json' => ['application/json'],
+        'json-patch' => ['application/json-patch+json'],
+        'jsonld' => ['application/ld+json'],
+        'jsonml' => ['application/jsonml+json'],
+        'k25' => ['image/x-kodak-k25'],
+        'k7' => ['application/x-thomson-cassette'],
+        'kar' => ['audio/midi', 'audio/x-midi'],
+        'karbon' => ['application/vnd.kde.karbon', 'application/x-karbon'],
+        'kdc' => ['image/x-kodak-kdc'],
+        'kdelnk' => ['application/x-desktop', 'application/x-gnome-app-info'],
+        'kexi' => ['application/x-kexiproject-sqlite', 'application/x-kexiproject-sqlite2', 'application/x-kexiproject-sqlite3', 'application/x-vnd.kde.kexi'],
+        'kexic' => ['application/x-kexi-connectiondata'],
+        'kexis' => ['application/x-kexiproject-shortcut'],
+        'key' => ['application/vnd.apple.keynote', 'application/x-iwork-keynote-sffkey'],
+        'kfo' => ['application/vnd.kde.kformula', 'application/x-kformula'],
+        'kia' => ['application/vnd.kidspiration'],
+        'kil' => ['application/x-killustrator'],
+        'kino' => ['application/smil', 'application/smil+xml'],
+        'kml' => ['application/vnd.google-earth.kml+xml'],
+        'kmz' => ['application/vnd.google-earth.kmz'],
+        'kne' => ['application/vnd.kinar'],
+        'knp' => ['application/vnd.kinar'],
+        'kon' => ['application/vnd.kde.kontour', 'application/x-kontour'],
+        'kpm' => ['application/x-kpovmodeler'],
+        'kpr' => ['application/vnd.kde.kpresenter', 'application/x-kpresenter'],
+        'kpt' => ['application/vnd.kde.kpresenter', 'application/x-kpresenter'],
+        'kpxx' => ['application/vnd.ds-keypoint'],
+        'kra' => ['application/x-krita'],
+        'ks' => ['application/x-java-keystore'],
+        'ksp' => ['application/vnd.kde.kspread', 'application/x-kspread'],
+        'ktr' => ['application/vnd.kahootz'],
+        'ktx' => ['image/ktx'],
+        'ktz' => ['application/vnd.kahootz'],
+        'kud' => ['application/x-kugar'],
+        'kwd' => ['application/vnd.kde.kword', 'application/x-kword'],
+        'kwt' => ['application/vnd.kde.kword', 'application/x-kword'],
+        'la' => ['application/x-shared-library-la'],
+        'lasxml' => ['application/vnd.las.las+xml'],
+        'latex' => ['application/x-latex', 'application/x-tex', 'text/x-tex'],
+        'lbd' => ['application/vnd.llamagraphics.life-balance.desktop'],
+        'lbe' => ['application/vnd.llamagraphics.life-balance.exchange+xml'],
+        'lbm' => ['image/x-iff', 'image/x-ilbm'],
+        'ldif' => ['text/x-ldif'],
+        'les' => ['application/vnd.hhe.lesson-player'],
+        'lha' => ['application/x-lha', 'application/x-lzh-compressed'],
+        'lhs' => ['text/x-literate-haskell'],
+        'lhz' => ['application/x-lhz'],
+        'link66' => ['application/vnd.route66.link66+xml'],
+        'list' => ['text/plain'],
+        'list3820' => ['application/vnd.ibm.modcap'],
+        'listafp' => ['application/vnd.ibm.modcap'],
+        'lnk' => ['application/x-ms-shortcut'],
+        'lnx' => ['application/x-atari-lynx-rom'],
+        'loas' => ['audio/usac'],
+        'log' => ['text/plain', 'text/x-log'],
+        'lostxml' => ['application/lost+xml'],
+        'lrf' => ['application/octet-stream'],
+        'lrm' => ['application/vnd.ms-lrm'],
+        'lrv' => ['video/mp4', 'video/mp4v-es', 'video/x-m4v'],
+        'lrz' => ['application/x-lrzip'],
+        'ltf' => ['application/vnd.frogans.ltf'],
+        'ltx' => ['application/x-tex', 'text/x-tex'],
+        'lua' => ['text/x-lua'],
+        'lvp' => ['audio/vnd.lucent.voice'],
+        'lwo' => ['image/x-lwo'],
+        'lwob' => ['image/x-lwo'],
+        'lwp' => ['application/vnd.lotus-wordpro'],
+        'lws' => ['image/x-lws'],
+        'ly' => ['text/x-lilypond'],
+        'lyx' => ['application/x-lyx', 'text/x-lyx'],
+        'lz' => ['application/x-lzip'],
+        'lz4' => ['application/x-lz4'],
+        'lzh' => ['application/x-lha', 'application/x-lzh-compressed'],
+        'lzma' => ['application/x-lzma'],
+        'lzo' => ['application/x-lzop'],
+        'm' => ['text/x-matlab', 'text/x-objcsrc', 'text/x-octave'],
+        'm13' => ['application/x-msmediaview'],
+        'm14' => ['application/x-msmediaview'],
+        'm15' => ['audio/x-mod'],
+        'm1u' => ['video/vnd.mpegurl', 'video/x-mpegurl'],
+        'm1v' => ['video/mpeg'],
+        'm21' => ['application/mp21'],
+        'm2a' => ['audio/mpeg'],
+        'm2t' => ['video/mp2t'],
+        'm2ts' => ['video/mp2t'],
+        'm2v' => ['video/mpeg'],
+        'm3a' => ['audio/mpeg'],
+        'm3u' => ['audio/x-mpegurl', 'application/m3u', 'application/vnd.apple.mpegurl', 'audio/m3u', 'audio/mpegurl', 'audio/x-m3u', 'audio/x-mp3-playlist'],
+        'm3u8' => ['application/m3u', 'application/vnd.apple.mpegurl', 'audio/m3u', 'audio/mpegurl', 'audio/x-m3u', 'audio/x-mp3-playlist', 'audio/x-mpegurl'],
+        'm4' => ['application/x-m4'],
+        'm4a' => ['audio/mp4', 'audio/m4a', 'audio/x-m4a'],
+        'm4b' => ['audio/x-m4b'],
+        'm4r' => ['audio/x-m4r'],
+        'm4u' => ['video/vnd.mpegurl', 'video/x-mpegurl'],
+        'm4v' => ['video/mp4', 'video/mp4v-es', 'video/x-m4v'],
+        'm7' => ['application/x-thomson-cartridge-memo7'],
+        'ma' => ['application/mathematica'],
+        'mab' => ['application/x-markaby'],
+        'mads' => ['application/mads+xml'],
+        'mag' => ['application/vnd.ecowin.chart'],
+        'mak' => ['text/x-makefile'],
+        'maker' => ['application/vnd.framemaker'],
+        'man' => ['application/x-troff-man', 'text/troff'],
+        'manifest' => ['text/cache-manifest'],
+        'mar' => ['application/octet-stream'],
+        'markdown' => ['text/markdown', 'text/x-markdown'],
+        'mathml' => ['application/mathml+xml'],
+        'mb' => ['application/mathematica'],
+        'mbk' => ['application/vnd.mobius.mbk'],
+        'mbox' => ['application/mbox'],
+        'mc1' => ['application/vnd.medcalcdata'],
+        'mcd' => ['application/vnd.mcd'],
+        'mcurl' => ['text/vnd.curl.mcurl'],
+        'md' => ['text/markdown', 'text/x-markdown'],
+        'mdb' => ['application/x-msaccess', 'application/mdb', 'application/msaccess', 'application/vnd.ms-access', 'application/vnd.msaccess', 'application/x-mdb', 'zz-application/zz-winassoc-mdb'],
+        'mdi' => ['image/vnd.ms-modi'],
+        'mdx' => ['application/x-genesis-32x-rom'],
+        'me' => ['text/troff', 'text/x-troff-me'],
+        'med' => ['audio/x-mod'],
+        'mesh' => ['model/mesh'],
+        'meta4' => ['application/metalink4+xml'],
+        'metalink' => ['application/metalink+xml'],
+        'mets' => ['application/mets+xml'],
+        'mfm' => ['application/vnd.mfmp'],
+        'mft' => ['application/rpki-manifest'],
+        'mgp' => ['application/vnd.osgeo.mapguide.package', 'application/x-magicpoint'],
+        'mgz' => ['application/vnd.proteus.magazine'],
+        'mht' => ['application/x-mimearchive'],
+        'mhtml' => ['application/x-mimearchive'],
+        'mid' => ['audio/midi', 'audio/x-midi'],
+        'midi' => ['audio/midi', 'audio/x-midi'],
+        'mie' => ['application/x-mie'],
+        'mif' => ['application/vnd.mif', 'application/x-mif'],
+        'mime' => ['message/rfc822'],
+        'minipsf' => ['audio/x-minipsf'],
+        'mj2' => ['video/mj2'],
+        'mjp2' => ['video/mj2'],
+        'mjpeg' => ['video/x-mjpeg'],
+        'mjpg' => ['video/x-mjpeg'],
+        'mjs' => ['application/javascript', 'application/x-javascript', 'text/javascript'],
+        'mk' => ['text/x-makefile'],
+        'mk3d' => ['video/x-matroska', 'video/x-matroska-3d'],
+        'mka' => ['audio/x-matroska'],
+        'mkd' => ['text/markdown', 'text/x-markdown'],
+        'mks' => ['video/x-matroska'],
+        'mkv' => ['video/x-matroska'],
+        'ml' => ['text/x-ocaml'],
+        'mli' => ['text/x-ocaml'],
+        'mlp' => ['application/vnd.dolby.mlp'],
+        'mm' => ['text/x-troff-mm'],
+        'mmd' => ['application/vnd.chipnuts.karaoke-mmd'],
+        'mmf' => ['application/vnd.smaf', 'application/x-smaf'],
+        'mml' => ['application/mathml+xml', 'text/mathml'],
+        'mmr' => ['image/vnd.fujixerox.edmics-mmr'],
+        'mng' => ['video/x-mng'],
+        'mny' => ['application/x-msmoney'],
+        'mo' => ['application/x-gettext-translation', 'text/x-modelica'],
+        'mo3' => ['audio/x-mo3'],
+        'mobi' => ['application/x-mobipocket-ebook'],
+        'moc' => ['text/x-moc'],
+        'mod' => ['audio/x-mod'],
+        'mods' => ['application/mods+xml'],
+        'mof' => ['text/x-mof'],
+        'moov' => ['video/quicktime'],
+        'mount' => ['text/x-systemd-unit'],
+        'mov' => ['video/quicktime'],
+        'movie' => ['video/x-sgi-movie'],
+        'mp+' => ['audio/x-musepack'],
+        'mp2' => ['audio/mp2', 'audio/mpeg', 'audio/x-mp2', 'video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2'],
+        'mp21' => ['application/mp21'],
+        'mp2a' => ['audio/mpeg'],
+        'mp3' => ['audio/mpeg', 'audio/mp3', 'audio/x-mp3', 'audio/x-mpeg', 'audio/x-mpg'],
+        'mp4' => ['video/mp4', 'video/mp4v-es', 'video/x-m4v'],
+        'mp4a' => ['audio/mp4'],
+        'mp4s' => ['application/mp4'],
+        'mp4v' => ['video/mp4'],
+        'mpc' => ['application/vnd.mophun.certificate', 'audio/x-musepack'],
+        'mpe' => ['video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2'],
+        'mpeg' => ['video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2'],
+        'mpg' => ['video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2'],
+        'mpg4' => ['video/mp4'],
+        'mpga' => ['audio/mp3', 'audio/mpeg', 'audio/x-mp3', 'audio/x-mpeg', 'audio/x-mpg'],
+        'mpkg' => ['application/vnd.apple.installer+xml'],
+        'mpl' => ['video/mp2t'],
+        'mpls' => ['video/mp2t'],
+        'mpm' => ['application/vnd.blueice.multipass'],
+        'mpn' => ['application/vnd.mophun.application'],
+        'mpp' => ['application/vnd.ms-project', 'audio/x-musepack'],
+        'mpt' => ['application/vnd.ms-project'],
+        'mpy' => ['application/vnd.ibm.minipay'],
+        'mqy' => ['application/vnd.mobius.mqy'],
+        'mrc' => ['application/marc'],
+        'mrcx' => ['application/marcxml+xml'],
+        'mrl' => ['text/x-mrml'],
+        'mrml' => ['text/x-mrml'],
+        'mrw' => ['image/x-minolta-mrw'],
+        'ms' => ['text/troff', 'text/x-troff-ms'],
+        'mscml' => ['application/mediaservercontrol+xml'],
+        'mseed' => ['application/vnd.fdsn.mseed'],
+        'mseq' => ['application/vnd.mseq'],
+        'msf' => ['application/vnd.epson.msf'],
+        'msh' => ['model/mesh'],
+        'msi' => ['application/x-msdownload', 'application/x-msi'],
+        'msl' => ['application/vnd.mobius.msl'],
+        'msod' => ['image/x-msod'],
+        'msty' => ['application/vnd.muvee.style'],
+        'msx' => ['application/x-msx-rom'],
+        'mtm' => ['audio/x-mod'],
+        'mts' => ['model/vnd.mts', 'video/mp2t'],
+        'mup' => ['text/x-mup'],
+        'mus' => ['application/vnd.musician'],
+        'musicxml' => ['application/vnd.recordare.musicxml+xml'],
+        'mvb' => ['application/x-msmediaview'],
+        'mwf' => ['application/vnd.mfer'],
+        'mxf' => ['application/mxf'],
+        'mxl' => ['application/vnd.recordare.musicxml'],
+        'mxml' => ['application/xv+xml'],
+        'mxs' => ['application/vnd.triscape.mxs'],
+        'mxu' => ['video/vnd.mpegurl', 'video/x-mpegurl'],
+        'n-gage' => ['application/vnd.nokia.n-gage.symbian.install'],
+        'n3' => ['text/n3'],
+        'n64' => ['application/x-n64-rom'],
+        'nb' => ['application/mathematica', 'application/x-mathematica'],
+        'nbp' => ['application/vnd.wolfram.player'],
+        'nc' => ['application/x-netcdf'],
+        'ncx' => ['application/x-dtbncx+xml'],
+        'nds' => ['application/x-nintendo-ds-rom'],
+        'nef' => ['image/x-nikon-nef'],
+        'nes' => ['application/x-nes-rom'],
+        'nez' => ['application/x-nes-rom'],
+        'nfo' => ['text/x-nfo'],
+        'ngc' => ['application/x-neo-geo-pocket-color-rom'],
+        'ngdat' => ['application/vnd.nokia.n-gage.data'],
+        'ngp' => ['application/x-neo-geo-pocket-rom'],
+        'nitf' => ['application/vnd.nitf'],
+        'nlu' => ['application/vnd.neurolanguage.nlu'],
+        'nml' => ['application/vnd.enliven'],
+        'nnd' => ['application/vnd.noblenet-directory'],
+        'nns' => ['application/vnd.noblenet-sealer'],
+        'nnw' => ['application/vnd.noblenet-web'],
+        'not' => ['text/x-mup'],
+        'npx' => ['image/vnd.net-fpx'],
+        'nsc' => ['application/x-conference', 'application/x-netshow-channel'],
+        'nsf' => ['application/vnd.lotus-notes'],
+        'nsv' => ['video/x-nsv'],
+        'ntf' => ['application/vnd.nitf'],
+        'nzb' => ['application/x-nzb'],
+        'o' => ['application/x-object'],
+        'oa2' => ['application/vnd.fujitsu.oasys2'],
+        'oa3' => ['application/vnd.fujitsu.oasys3'],
+        'oas' => ['application/vnd.fujitsu.oasys'],
+        'obd' => ['application/x-msbinder'],
+        'obj' => ['application/x-tgif'],
+        'ocl' => ['text/x-ocl'],
+        'oda' => ['application/oda'],
+        'odb' => ['application/vnd.oasis.opendocument.database', 'application/vnd.sun.xml.base'],
+        'odc' => ['application/vnd.oasis.opendocument.chart'],
+        'odf' => ['application/vnd.oasis.opendocument.formula'],
+        'odft' => ['application/vnd.oasis.opendocument.formula-template'],
+        'odg' => ['application/vnd.oasis.opendocument.graphics'],
+        'odi' => ['application/vnd.oasis.opendocument.image'],
+        'odm' => ['application/vnd.oasis.opendocument.text-master'],
+        'odp' => ['application/vnd.oasis.opendocument.presentation'],
+        'ods' => ['application/vnd.oasis.opendocument.spreadsheet'],
+        'odt' => ['application/vnd.oasis.opendocument.text'],
+        'oga' => ['audio/ogg', 'audio/vorbis', 'audio/x-flac+ogg', 'audio/x-ogg', 'audio/x-oggflac', 'audio/x-speex+ogg', 'audio/x-vorbis', 'audio/x-vorbis+ogg'],
+        'ogg' => ['audio/ogg', 'audio/vorbis', 'audio/x-flac+ogg', 'audio/x-ogg', 'audio/x-oggflac', 'audio/x-speex+ogg', 'audio/x-vorbis', 'audio/x-vorbis+ogg', 'video/ogg', 'video/x-ogg', 'video/x-theora', 'video/x-theora+ogg'],
+        'ogm' => ['video/x-ogm', 'video/x-ogm+ogg'],
+        'ogv' => ['video/ogg', 'video/x-ogg'],
+        'ogx' => ['application/ogg', 'application/x-ogg'],
+        'old' => ['application/x-trash'],
+        'oleo' => ['application/x-oleo'],
+        'omdoc' => ['application/omdoc+xml'],
+        'onepkg' => ['application/onenote'],
+        'onetmp' => ['application/onenote'],
+        'onetoc' => ['application/onenote'],
+        'onetoc2' => ['application/onenote'],
+        'ooc' => ['text/x-ooc'],
+        'opf' => ['application/oebps-package+xml'],
+        'opml' => ['text/x-opml', 'text/x-opml+xml'],
+        'oprc' => ['application/vnd.palm', 'application/x-palm-database'],
+        'opus' => ['audio/ogg', 'audio/x-ogg', 'audio/x-opus+ogg'],
+        'ora' => ['image/openraster'],
+        'orf' => ['image/x-olympus-orf'],
+        'org' => ['application/vnd.lotus-organizer'],
+        'osf' => ['application/vnd.yamaha.openscoreformat'],
+        'osfpvg' => ['application/vnd.yamaha.openscoreformat.osfpvg+xml'],
+        'otc' => ['application/vnd.oasis.opendocument.chart-template'],
+        'otf' => ['application/vnd.oasis.opendocument.formula-template', 'application/x-font-otf', 'font/otf'],
+        'otg' => ['application/vnd.oasis.opendocument.graphics-template'],
+        'oth' => ['application/vnd.oasis.opendocument.text-web'],
+        'oti' => ['application/vnd.oasis.opendocument.image-template'],
+        'otp' => ['application/vnd.oasis.opendocument.presentation-template'],
+        'ots' => ['application/vnd.oasis.opendocument.spreadsheet-template'],
+        'ott' => ['application/vnd.oasis.opendocument.text-template'],
+        'owl' => ['application/rdf+xml', 'text/rdf'],
+        'owx' => ['application/owl+xml'],
+        'oxps' => ['application/oxps', 'application/vnd.ms-xpsdocument', 'application/xps'],
+        'oxt' => ['application/vnd.openofficeorg.extension'],
+        'p' => ['text/x-pascal'],
+        'p10' => ['application/pkcs10'],
+        'p12' => ['application/pkcs12', 'application/x-pkcs12'],
+        'p65' => ['application/x-pagemaker'],
+        'p7b' => ['application/x-pkcs7-certificates'],
+        'p7c' => ['application/pkcs7-mime'],
+        'p7m' => ['application/pkcs7-mime'],
+        'p7r' => ['application/x-pkcs7-certreqresp'],
+        'p7s' => ['application/pkcs7-signature'],
+        'p8' => ['application/pkcs8'],
+        'p8e' => ['application/pkcs8-encrypted'],
+        'pack' => ['application/x-java-pack200'],
+        'pak' => ['application/x-pak'],
+        'par2' => ['application/x-par2'],
+        'part' => ['application/x-partial-download'],
+        'pas' => ['text/x-pascal'],
+        'pat' => ['image/x-gimp-pat'],
+        'patch' => ['text/x-diff', 'text/x-patch'],
+        'path' => ['text/x-systemd-unit'],
+        'paw' => ['application/vnd.pawaafile'],
+        'pbd' => ['application/vnd.powerbuilder6'],
+        'pbm' => ['image/x-portable-bitmap'],
+        'pcap' => ['application/pcap', 'application/vnd.tcpdump.pcap', 'application/x-pcap'],
+        'pcd' => ['image/x-photo-cd'],
+        'pce' => ['application/x-pc-engine-rom'],
+        'pcf' => ['application/x-cisco-vpn-settings', 'application/x-font-pcf'],
+        'pcf.Z' => ['application/x-font-pcf'],
+        'pcf.gz' => ['application/x-font-pcf'],
+        'pcl' => ['application/vnd.hp-pcl'],
+        'pclxl' => ['application/vnd.hp-pclxl'],
+        'pct' => ['image/x-pict'],
+        'pcurl' => ['application/vnd.curl.pcurl'],
+        'pcx' => ['image/vnd.zbrush.pcx', 'image/x-pcx'],
+        'pdb' => ['application/vnd.palm', 'application/x-aportisdoc', 'application/x-palm-database'],
+        'pdc' => ['application/x-aportisdoc'],
+        'pdf' => ['application/pdf', 'application/acrobat', 'application/nappdf', 'application/x-pdf', 'image/pdf'],
+        'pdf.bz2' => ['application/x-bzpdf'],
+        'pdf.gz' => ['application/x-gzpdf'],
+        'pdf.lz' => ['application/x-lzpdf'],
+        'pdf.xz' => ['application/x-xzpdf'],
+        'pef' => ['image/x-pentax-pef'],
+        'pem' => ['application/x-x509-ca-cert'],
+        'perl' => ['application/x-perl', 'text/x-perl'],
+        'pfa' => ['application/x-font-type1'],
+        'pfb' => ['application/x-font-type1'],
+        'pfm' => ['application/x-font-type1'],
+        'pfr' => ['application/font-tdpfr'],
+        'pfx' => ['application/pkcs12', 'application/x-pkcs12'],
+        'pgm' => ['image/x-portable-graymap'],
+        'pgn' => ['application/vnd.chess-pgn', 'application/x-chess-pgn'],
+        'pgp' => ['application/pgp', 'application/pgp-encrypted', 'application/pgp-keys', 'application/pgp-signature'],
+        'php' => ['application/x-php'],
+        'php3' => ['application/x-php'],
+        'php4' => ['application/x-php'],
+        'php5' => ['application/x-php'],
+        'phps' => ['application/x-php'],
+        'pic' => ['image/x-pict'],
+        'pict' => ['image/x-pict'],
+        'pict1' => ['image/x-pict'],
+        'pict2' => ['image/x-pict'],
+        'pk' => ['application/x-tex-pk'],
+        'pkg' => ['application/octet-stream', 'application/x-xar'],
+        'pki' => ['application/pkixcmp'],
+        'pkipath' => ['application/pkix-pkipath'],
+        'pkr' => ['application/pgp-keys'],
+        'pl' => ['application/x-perl', 'text/x-perl'],
+        'pla' => ['audio/x-iriver-pla'],
+        'plb' => ['application/vnd.3gpp.pic-bw-large'],
+        'plc' => ['application/vnd.mobius.plc'],
+        'plf' => ['application/vnd.pocketlearn'],
+        'pln' => ['application/x-planperfect'],
+        'pls' => ['application/pls', 'application/pls+xml', 'audio/scpls', 'audio/x-scpls'],
+        'pm' => ['application/x-pagemaker', 'application/x-perl', 'text/x-perl'],
+        'pm6' => ['application/x-pagemaker'],
+        'pmd' => ['application/x-pagemaker'],
+        'pml' => ['application/vnd.ctc-posml'],
+        'png' => ['image/png'],
+        'pnm' => ['image/x-portable-anymap'],
+        'pntg' => ['image/x-macpaint'],
+        'po' => ['application/x-gettext', 'text/x-gettext-translation', 'text/x-po'],
+        'pod' => ['application/x-perl', 'text/x-perl'],
+        'por' => ['application/x-spss-por'],
+        'portpkg' => ['application/vnd.macports.portpkg'],
+        'pot' => ['application/mspowerpoint', 'application/powerpoint', 'application/vnd.ms-powerpoint', 'application/x-mspowerpoint', 'text/x-gettext-translation-template', 'text/x-pot'],
+        'potm' => ['application/vnd.ms-powerpoint.template.macroenabled.12'],
+        'potx' => ['application/vnd.openxmlformats-officedocument.presentationml.template'],
+        'ppam' => ['application/vnd.ms-powerpoint.addin.macroenabled.12'],
+        'ppd' => ['application/vnd.cups-ppd'],
+        'ppm' => ['image/x-portable-pixmap'],
+        'pps' => ['application/mspowerpoint', 'application/powerpoint', 'application/vnd.ms-powerpoint', 'application/x-mspowerpoint'],
+        'ppsm' => ['application/vnd.ms-powerpoint.slideshow.macroenabled.12'],
+        'ppsx' => ['application/vnd.openxmlformats-officedocument.presentationml.slideshow'],
+        'ppt' => ['application/vnd.ms-powerpoint', 'application/mspowerpoint', 'application/powerpoint', 'application/x-mspowerpoint'],
+        'pptm' => ['application/vnd.ms-powerpoint.presentation.macroenabled.12'],
+        'pptx' => ['application/vnd.openxmlformats-officedocument.presentationml.presentation'],
+        'ppz' => ['application/mspowerpoint', 'application/powerpoint', 'application/vnd.ms-powerpoint', 'application/x-mspowerpoint'],
+        'pqa' => ['application/vnd.palm', 'application/x-palm-database'],
+        'prc' => ['application/vnd.palm', 'application/x-mobipocket-ebook', 'application/x-palm-database'],
+        'pre' => ['application/vnd.lotus-freelance'],
+        'prf' => ['application/pics-rules'],
+        'ps' => ['application/postscript'],
+        'ps.bz2' => ['application/x-bzpostscript'],
+        'ps.gz' => ['application/x-gzpostscript'],
+        'psb' => ['application/vnd.3gpp.pic-bw-small'],
+        'psd' => ['application/photoshop', 'application/x-photoshop', 'image/photoshop', 'image/psd', 'image/vnd.adobe.photoshop', 'image/x-photoshop', 'image/x-psd'],
+        'psf' => ['application/x-font-linux-psf', 'audio/x-psf'],
+        'psf.gz' => ['application/x-gz-font-linux-psf'],
+        'psflib' => ['audio/x-psflib'],
+        'psid' => ['audio/prs.sid'],
+        'pskcxml' => ['application/pskc+xml'],
+        'psw' => ['application/x-pocket-word'],
+        'ptid' => ['application/vnd.pvi.ptid1'],
+        'pub' => ['application/vnd.ms-publisher', 'application/x-mspublisher'],
+        'pvb' => ['application/vnd.3gpp.pic-bw-var'],
+        'pw' => ['application/x-pw'],
+        'pwn' => ['application/vnd.3m.post-it-notes'],
+        'py' => ['text/x-python', 'text/x-python3'],
+        'py3' => ['text/x-python3'],
+        'py3x' => ['text/x-python3'],
+        'pya' => ['audio/vnd.ms-playready.media.pya'],
+        'pyc' => ['application/x-python-bytecode'],
+        'pyo' => ['application/x-python-bytecode'],
+        'pyv' => ['video/vnd.ms-playready.media.pyv'],
+        'pyx' => ['text/x-python'],
+        'qam' => ['application/vnd.epson.quickanime'],
+        'qbo' => ['application/vnd.intu.qbo'],
+        'qd' => ['application/x-fd-file', 'application/x-raw-floppy-disk-image'],
+        'qfx' => ['application/vnd.intu.qfx'],
+        'qif' => ['application/x-qw', 'image/x-quicktime'],
+        'qml' => ['text/x-qml'],
+        'qmlproject' => ['text/x-qml'],
+        'qmltypes' => ['text/x-qml'],
+        'qp' => ['application/x-qpress'],
+        'qps' => ['application/vnd.publishare-delta-tree'],
+        'qt' => ['video/quicktime'],
+        'qti' => ['application/x-qtiplot'],
+        'qti.gz' => ['application/x-qtiplot'],
+        'qtif' => ['image/x-quicktime'],
+        'qtl' => ['application/x-quicktime-media-link', 'application/x-quicktimeplayer'],
+        'qtvr' => ['video/quicktime'],
+        'qwd' => ['application/vnd.quark.quarkxpress'],
+        'qwt' => ['application/vnd.quark.quarkxpress'],
+        'qxb' => ['application/vnd.quark.quarkxpress'],
+        'qxd' => ['application/vnd.quark.quarkxpress'],
+        'qxl' => ['application/vnd.quark.quarkxpress'],
+        'qxt' => ['application/vnd.quark.quarkxpress'],
+        'ra' => ['audio/vnd.m-realaudio', 'audio/vnd.rn-realaudio', 'audio/x-pn-realaudio'],
+        'raf' => ['image/x-fuji-raf'],
+        'ram' => ['application/ram', 'audio/x-pn-realaudio'],
+        'raml' => ['application/raml+yaml'],
+        'rar' => ['application/x-rar-compressed', 'application/vnd.rar', 'application/x-rar'],
+        'ras' => ['image/x-cmu-raster'],
+        'raw' => ['image/x-panasonic-raw', 'image/x-panasonic-rw'],
+        'raw-disk-image' => ['application/x-raw-disk-image'],
+        'raw-disk-image.xz' => ['application/x-raw-disk-image-xz-compressed'],
+        'rax' => ['audio/vnd.m-realaudio', 'audio/vnd.rn-realaudio', 'audio/x-pn-realaudio'],
+        'rb' => ['application/x-ruby'],
+        'rcprofile' => ['application/vnd.ipunplugged.rcprofile'],
+        'rdf' => ['application/rdf+xml', 'text/rdf'],
+        'rdfs' => ['application/rdf+xml', 'text/rdf'],
+        'rdz' => ['application/vnd.data-vision.rdz'],
+        'reg' => ['text/x-ms-regedit'],
+        'rej' => ['application/x-reject', 'text/x-reject'],
+        'rep' => ['application/vnd.businessobjects'],
+        'res' => ['application/x-dtbresource+xml'],
+        'rgb' => ['image/x-rgb'],
+        'rif' => ['application/reginfo+xml'],
+        'rip' => ['audio/vnd.rip'],
+        'ris' => ['application/x-research-info-systems'],
+        'rl' => ['application/resource-lists+xml'],
+        'rlc' => ['image/vnd.fujixerox.edmics-rlc'],
+        'rld' => ['application/resource-lists-diff+xml'],
+        'rle' => ['image/rle'],
+        'rm' => ['application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'],
+        'rmi' => ['audio/midi'],
+        'rmj' => ['application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'],
+        'rmm' => ['application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'],
+        'rmp' => ['audio/x-pn-realaudio-plugin'],
+        'rms' => ['application/vnd.jcp.javame.midlet-rms', 'application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'],
+        'rmvb' => ['application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'],
+        'rmx' => ['application/vnd.rn-realmedia', 'application/vnd.rn-realmedia-vbr'],
+        'rnc' => ['application/relax-ng-compact-syntax', 'application/x-rnc'],
+        'rng' => ['application/xml', 'text/xml'],
+        'roa' => ['application/rpki-roa'],
+        'roff' => ['application/x-troff', 'text/troff', 'text/x-troff'],
+        'rp' => ['image/vnd.rn-realpix'],
+        'rp9' => ['application/vnd.cloanto.rp9'],
+        'rpm' => ['application/x-redhat-package-manager', 'application/x-rpm'],
+        'rpss' => ['application/vnd.nokia.radio-presets'],
+        'rpst' => ['application/vnd.nokia.radio-preset'],
+        'rq' => ['application/sparql-query'],
+        'rs' => ['application/rls-services+xml', 'text/rust'],
+        'rsd' => ['application/rsd+xml'],
+        'rss' => ['application/rss+xml', 'text/rss'],
+        'rt' => ['text/vnd.rn-realtext'],
+        'rtf' => ['application/rtf', 'text/rtf'],
+        'rtx' => ['text/richtext'],
+        'rv' => ['video/vnd.rn-realvideo', 'video/x-real-video'],
+        'rvx' => ['video/vnd.rn-realvideo', 'video/x-real-video'],
+        'rw2' => ['image/x-panasonic-raw2', 'image/x-panasonic-rw2'],
+        's' => ['text/x-asm'],
+        's3m' => ['audio/s3m', 'audio/x-s3m'],
+        'saf' => ['application/vnd.yamaha.smaf-audio'],
+        'sam' => ['application/x-amipro'],
+        'sami' => ['application/x-sami'],
+        'sap' => ['application/x-sap-file', 'application/x-thomson-sap-image'],
+        'sass' => ['text/x-sass'],
+        'sav' => ['application/x-spss-sav', 'application/x-spss-savefile'],
+        'sbml' => ['application/sbml+xml'],
+        'sc' => ['application/vnd.ibm.secure-container'],
+        'scala' => ['text/x-scala'],
+        'scd' => ['application/x-msschedule'],
+        'scm' => ['application/vnd.lotus-screencam', 'text/x-scheme'],
+        'scope' => ['text/x-systemd-unit'],
+        'scq' => ['application/scvp-cv-request'],
+        'scs' => ['application/scvp-cv-response'],
+        'scss' => ['text/x-scss'],
+        'scurl' => ['text/vnd.curl.scurl'],
+        'sda' => ['application/vnd.stardivision.draw'],
+        'sdc' => ['application/vnd.stardivision.calc'],
+        'sdd' => ['application/vnd.stardivision.impress'],
+        'sdkd' => ['application/vnd.solent.sdkm+xml'],
+        'sdkm' => ['application/vnd.solent.sdkm+xml'],
+        'sdp' => ['application/sdp', 'application/vnd.sdp', 'application/vnd.stardivision.impress', 'application/x-sdp'],
+        'sds' => ['application/vnd.stardivision.chart'],
+        'sdw' => ['application/vnd.stardivision.writer', 'application/vnd.stardivision.writer-global'],
+        'see' => ['application/vnd.seemail'],
+        'seed' => ['application/vnd.fdsn.seed'],
+        'sema' => ['application/vnd.sema'],
+        'semd' => ['application/vnd.semd'],
+        'semf' => ['application/vnd.semf'],
+        'ser' => ['application/java-serialized-object'],
+        'service' => ['text/x-dbus-service', 'text/x-systemd-unit'],
+        'setpay' => ['application/set-payment-initiation'],
+        'setreg' => ['application/set-registration-initiation'],
+        'sfc' => ['application/vnd.nintendo.snes.rom', 'application/x-snes-rom'],
+        'sfd-hdstx' => ['application/vnd.hydrostatix.sof-data'],
+        'sfs' => ['application/vnd.spotfire.sfs'],
+        'sfv' => ['text/x-sfv'],
+        'sg' => ['application/x-sg1000-rom'],
+        'sgb' => ['application/x-gameboy-rom'],
+        'sgf' => ['application/x-go-sgf'],
+        'sgi' => ['image/sgi', 'image/x-sgi'],
+        'sgl' => ['application/vnd.stardivision.writer', 'application/vnd.stardivision.writer-global'],
+        'sgm' => ['text/sgml'],
+        'sgml' => ['text/sgml'],
+        'sh' => ['application/x-sh', 'application/x-shellscript', 'text/x-sh'],
+        'shape' => ['application/x-dia-shape'],
+        'shar' => ['application/x-shar'],
+        'shf' => ['application/shf+xml'],
+        'shn' => ['application/x-shorten', 'audio/x-shorten'],
+        'siag' => ['application/x-siag'],
+        'sid' => ['audio/prs.sid', 'image/x-mrsid-image'],
+        'sig' => ['application/pgp-signature'],
+        'sik' => ['application/x-trash'],
+        'sil' => ['audio/silk'],
+        'silo' => ['model/mesh'],
+        'sis' => ['application/vnd.symbian.install'],
+        'sisx' => ['application/vnd.symbian.install', 'x-epoc/x-sisx-app'],
+        'sit' => ['application/x-stuffit', 'application/stuffit', 'application/x-sit'],
+        'sitx' => ['application/x-stuffitx'],
+        'siv' => ['application/sieve'],
+        'sk' => ['image/x-skencil'],
+        'sk1' => ['image/x-skencil'],
+        'skd' => ['application/vnd.koan'],
+        'skm' => ['application/vnd.koan'],
+        'skp' => ['application/vnd.koan'],
+        'skr' => ['application/pgp-keys'],
+        'skt' => ['application/vnd.koan'],
+        'sldm' => ['application/vnd.ms-powerpoint.slide.macroenabled.12'],
+        'sldx' => ['application/vnd.openxmlformats-officedocument.presentationml.slide'],
+        'slice' => ['text/x-systemd-unit'],
+        'slk' => ['text/spreadsheet'],
+        'slt' => ['application/vnd.epson.salt'],
+        'sm' => ['application/vnd.stepmania.stepchart'],
+        'smaf' => ['application/vnd.smaf', 'application/x-smaf'],
+        'smc' => ['application/vnd.nintendo.snes.rom', 'application/x-snes-rom'],
+        'smd' => ['application/vnd.stardivision.mail', 'application/x-genesis-rom'],
+        'smf' => ['application/vnd.stardivision.math'],
+        'smi' => ['application/smil', 'application/smil+xml', 'application/x-sami'],
+        'smil' => ['application/smil', 'application/smil+xml'],
+        'sml' => ['application/smil', 'application/smil+xml'],
+        'sms' => ['application/x-sms-rom'],
+        'smv' => ['video/x-smv'],
+        'smzip' => ['application/vnd.stepmania.package'],
+        'snap' => ['application/vnd.snap'],
+        'snd' => ['audio/basic'],
+        'snf' => ['application/x-font-snf'],
+        'so' => ['application/octet-stream', 'application/x-sharedlib'],
+        'socket' => ['text/x-systemd-unit'],
+        'spc' => ['application/x-pkcs7-certificates'],
+        'spd' => ['application/x-font-speedo'],
+        'spec' => ['text/x-rpm-spec'],
+        'spf' => ['application/vnd.yamaha.smaf-phrase'],
+        'spl' => ['application/futuresplash', 'application/vnd.adobe.flash.movie', 'application/x-futuresplash', 'application/x-shockwave-flash'],
+        'spm' => ['application/x-source-rpm'],
+        'spot' => ['text/vnd.in3d.spot'],
+        'spp' => ['application/scvp-vp-response'],
+        'spq' => ['application/scvp-vp-request'],
+        'spx' => ['audio/ogg', 'audio/x-speex'],
+        'sql' => ['application/sql', 'application/x-sql', 'text/x-sql'],
+        'sqlite2' => ['application/x-sqlite2'],
+        'sqlite3' => ['application/vnd.sqlite3', 'application/x-sqlite3'],
+        'sqsh' => ['application/vnd.squashfs'],
+        'sr2' => ['image/x-sony-sr2'],
+        'src' => ['application/x-wais-source'],
+        'src.rpm' => ['application/x-source-rpm'],
+        'srf' => ['image/x-sony-srf'],
+        'srt' => ['application/x-srt', 'application/x-subrip'],
+        'sru' => ['application/sru+xml'],
+        'srx' => ['application/sparql-results+xml'],
+        'ss' => ['text/x-scheme'],
+        'ssa' => ['text/x-ssa'],
+        'ssdl' => ['application/ssdl+xml'],
+        'sse' => ['application/vnd.kodak-descriptor'],
+        'ssf' => ['application/vnd.epson.ssf'],
+        'ssml' => ['application/ssml+xml'],
+        'st' => ['application/vnd.sailingtracker.track'],
+        'stc' => ['application/vnd.sun.xml.calc.template'],
+        'std' => ['application/vnd.sun.xml.draw.template'],
+        'stf' => ['application/vnd.wt.stf'],
+        'sti' => ['application/vnd.sun.xml.impress.template'],
+        'stk' => ['application/hyperstudio'],
+        'stl' => ['application/vnd.ms-pki.stl', 'model/stl', 'model/x.stl-ascii', 'model/x.stl-binary'],
+        'stm' => ['audio/x-stm'],
+        'str' => ['application/vnd.pg.format'],
+        'stw' => ['application/vnd.sun.xml.writer.template'],
+        'sty' => ['application/x-tex', 'text/x-tex'],
+        'sub' => ['image/vnd.dvb.subtitle', 'text/vnd.dvb.subtitle', 'text/x-microdvd', 'text/x-mpsub', 'text/x-subviewer'],
+        'sun' => ['image/x-sun-raster'],
+        'sus' => ['application/vnd.sus-calendar'],
+        'susp' => ['application/vnd.sus-calendar'],
+        'sv' => ['text/x-svsrc'],
+        'sv4cpio' => ['application/x-sv4cpio'],
+        'sv4crc' => ['application/x-sv4crc'],
+        'svc' => ['application/vnd.dvb.service'],
+        'svd' => ['application/vnd.svd'],
+        'svg' => ['image/svg+xml'],
+        'svgz' => ['image/svg+xml', 'image/svg+xml-compressed'],
+        'svh' => ['text/x-svhdr'],
+        'swa' => ['application/x-director'],
+        'swap' => ['text/x-systemd-unit'],
+        'swf' => ['application/futuresplash', 'application/vnd.adobe.flash.movie', 'application/x-shockwave-flash'],
+        'swi' => ['application/vnd.aristanetworks.swi'],
+        'swm' => ['application/x-ms-wim'],
+        'sxc' => ['application/vnd.sun.xml.calc'],
+        'sxd' => ['application/vnd.sun.xml.draw'],
+        'sxg' => ['application/vnd.sun.xml.writer.global'],
+        'sxi' => ['application/vnd.sun.xml.impress'],
+        'sxm' => ['application/vnd.sun.xml.math'],
+        'sxw' => ['application/vnd.sun.xml.writer'],
+        'sylk' => ['text/spreadsheet'],
+        't' => ['application/x-perl', 'application/x-troff', 'text/troff', 'text/x-perl', 'text/x-troff'],
+        't2t' => ['text/x-txt2tags'],
+        't3' => ['application/x-t3vm-image'],
+        'taglet' => ['application/vnd.mynfc'],
+        'tao' => ['application/vnd.tao.intent-module-archive'],
+        'tar' => ['application/x-tar', 'application/x-gtar'],
+        'tar.Z' => ['application/x-tarz'],
+        'tar.bz' => ['application/x-bzip-compressed-tar'],
+        'tar.bz2' => ['application/x-bzip-compressed-tar'],
+        'tar.gz' => ['application/x-compressed-tar'],
+        'tar.lrz' => ['application/x-lrzip-compressed-tar'],
+        'tar.lz' => ['application/x-lzip-compressed-tar'],
+        'tar.lz4' => ['application/x-lz4-compressed-tar'],
+        'tar.lzma' => ['application/x-lzma-compressed-tar'],
+        'tar.lzo' => ['application/x-tzo'],
+        'tar.xz' => ['application/x-xz-compressed-tar'],
+        'target' => ['text/x-systemd-unit'],
+        'taz' => ['application/x-tarz'],
+        'tb2' => ['application/x-bzip-compressed-tar'],
+        'tbz' => ['application/x-bzip-compressed-tar'],
+        'tbz2' => ['application/x-bzip-compressed-tar'],
+        'tcap' => ['application/vnd.3gpp2.tcap'],
+        'tcl' => ['application/x-tcl', 'text/x-tcl'],
+        'teacher' => ['application/vnd.smart.teacher'],
+        'tei' => ['application/tei+xml'],
+        'teicorpus' => ['application/tei+xml'],
+        'tex' => ['application/x-tex', 'text/x-tex'],
+        'texi' => ['application/x-texinfo', 'text/x-texinfo'],
+        'texinfo' => ['application/x-texinfo', 'text/x-texinfo'],
+        'text' => ['text/plain'],
+        'tfi' => ['application/thraud+xml'],
+        'tfm' => ['application/x-tex-tfm'],
+        'tga' => ['image/x-icb', 'image/x-tga'],
+        'tgz' => ['application/x-compressed-tar'],
+        'theme' => ['application/x-theme'],
+        'themepack' => ['application/x-windows-themepack'],
+        'thmx' => ['application/vnd.ms-officetheme'],
+        'tif' => ['image/tiff'],
+        'tiff' => ['image/tiff'],
+        'timer' => ['text/x-systemd-unit'],
+        'tk' => ['text/x-tcl'],
+        'tlrz' => ['application/x-lrzip-compressed-tar'],
+        'tlz' => ['application/x-lzma-compressed-tar'],
+        'tmo' => ['application/vnd.tmobile-livetv'],
+        'tnef' => ['application/ms-tnef', 'application/vnd.ms-tnef'],
+        'tnf' => ['application/ms-tnef', 'application/vnd.ms-tnef'],
+        'toc' => ['application/x-cdrdao-toc'],
+        'torrent' => ['application/x-bittorrent'],
+        'tpic' => ['image/x-icb', 'image/x-tga'],
+        'tpl' => ['application/vnd.groove-tool-template'],
+        'tpt' => ['application/vnd.trid.tpt'],
+        'tr' => ['application/x-troff', 'text/troff', 'text/x-troff'],
+        'tra' => ['application/vnd.trueapp'],
+        'trig' => ['application/trig', 'application/x-trig'],
+        'trm' => ['application/x-msterminal'],
+        'ts' => ['application/x-linguist', 'text/vnd.qt.linguist', 'text/vnd.trolltech.linguist', 'video/mp2t'],
+        'tsd' => ['application/timestamped-data'],
+        'tsv' => ['text/tab-separated-values'],
+        'tta' => ['audio/tta', 'audio/x-tta'],
+        'ttc' => ['font/collection'],
+        'ttf' => ['application/x-font-truetype', 'application/x-font-ttf', 'font/ttf'],
+        'ttl' => ['text/turtle'],
+        'ttx' => ['application/x-font-ttx'],
+        'twd' => ['application/vnd.simtech-mindmapper'],
+        'twds' => ['application/vnd.simtech-mindmapper'],
+        'twig' => ['text/x-twig'],
+        'txd' => ['application/vnd.genomatix.tuxedo'],
+        'txf' => ['application/vnd.mobius.txf'],
+        'txt' => ['text/plain'],
+        'txz' => ['application/x-xz-compressed-tar'],
+        'tzo' => ['application/x-tzo'],
+        'u32' => ['application/x-authorware-bin'],
+        'udeb' => ['application/vnd.debian.binary-package', 'application/x-deb', 'application/x-debian-package'],
+        'ufd' => ['application/vnd.ufdl'],
+        'ufdl' => ['application/vnd.ufdl'],
+        'ufraw' => ['application/x-ufraw'],
+        'ui' => ['application/x-designer', 'application/x-gtk-builder'],
+        'uil' => ['text/x-uil'],
+        'ult' => ['audio/x-mod'],
+        'ulx' => ['application/x-glulx'],
+        'umj' => ['application/vnd.umajin'],
+        'unf' => ['application/x-nes-rom'],
+        'uni' => ['audio/x-mod'],
+        'unif' => ['application/x-nes-rom'],
+        'unityweb' => ['application/vnd.unity'],
+        'uoml' => ['application/vnd.uoml+xml'],
+        'uri' => ['text/uri-list'],
+        'uris' => ['text/uri-list'],
+        'url' => ['application/x-mswinurl'],
+        'urls' => ['text/uri-list'],
+        'ustar' => ['application/x-ustar'],
+        'utz' => ['application/vnd.uiq.theme'],
+        'uu' => ['text/x-uuencode'],
+        'uue' => ['text/x-uuencode', 'zz-application/zz-winassoc-uu'],
+        'uva' => ['audio/vnd.dece.audio'],
+        'uvd' => ['application/vnd.dece.data'],
+        'uvf' => ['application/vnd.dece.data'],
+        'uvg' => ['image/vnd.dece.graphic'],
+        'uvh' => ['video/vnd.dece.hd'],
+        'uvi' => ['image/vnd.dece.graphic'],
+        'uvm' => ['video/vnd.dece.mobile'],
+        'uvp' => ['video/vnd.dece.pd'],
+        'uvs' => ['video/vnd.dece.sd'],
+        'uvt' => ['application/vnd.dece.ttml+xml'],
+        'uvu' => ['video/vnd.uvvu.mp4'],
+        'uvv' => ['video/vnd.dece.video'],
+        'uvva' => ['audio/vnd.dece.audio'],
+        'uvvd' => ['application/vnd.dece.data'],
+        'uvvf' => ['application/vnd.dece.data'],
+        'uvvg' => ['image/vnd.dece.graphic'],
+        'uvvh' => ['video/vnd.dece.hd'],
+        'uvvi' => ['image/vnd.dece.graphic'],
+        'uvvm' => ['video/vnd.dece.mobile'],
+        'uvvp' => ['video/vnd.dece.pd'],
+        'uvvs' => ['video/vnd.dece.sd'],
+        'uvvt' => ['application/vnd.dece.ttml+xml'],
+        'uvvu' => ['video/vnd.uvvu.mp4'],
+        'uvvv' => ['video/vnd.dece.video'],
+        'uvvx' => ['application/vnd.dece.unspecified'],
+        'uvvz' => ['application/vnd.dece.zip'],
+        'uvx' => ['application/vnd.dece.unspecified'],
+        'uvz' => ['application/vnd.dece.zip'],
+        'v' => ['text/x-verilog'],
+        'v64' => ['application/x-n64-rom'],
+        'vala' => ['text/x-vala'],
+        'vapi' => ['text/x-vala'],
+        'vb' => ['application/x-virtual-boy-rom'],
+        'vcard' => ['text/directory', 'text/vcard', 'text/x-vcard'],
+        'vcd' => ['application/x-cdlink'],
+        'vcf' => ['text/x-vcard', 'text/directory', 'text/vcard'],
+        'vcg' => ['application/vnd.groove-vcard'],
+        'vcs' => ['application/ics', 'text/calendar', 'text/x-vcalendar'],
+        'vct' => ['text/directory', 'text/vcard', 'text/x-vcard'],
+        'vcx' => ['application/vnd.vcx'],
+        'vda' => ['image/x-icb', 'image/x-tga'],
+        'vhd' => ['text/x-vhdl'],
+        'vhdl' => ['text/x-vhdl'],
+        'vis' => ['application/vnd.visionary'],
+        'viv' => ['video/vivo', 'video/vnd.vivo'],
+        'vivo' => ['video/vivo', 'video/vnd.vivo'],
+        'vlc' => ['application/m3u', 'audio/m3u', 'audio/mpegurl', 'audio/x-m3u', 'audio/x-mp3-playlist', 'audio/x-mpegurl'],
+        'vob' => ['video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2', 'video/x-ms-vob'],
+        'voc' => ['audio/x-voc'],
+        'vor' => ['application/vnd.stardivision.writer', 'application/vnd.stardivision.writer-global'],
+        'vox' => ['application/x-authorware-bin'],
+        'vrm' => ['model/vrml'],
+        'vrml' => ['model/vrml'],
+        'vsd' => ['application/vnd.visio'],
+        'vsdm' => ['application/vnd.ms-visio.drawing.macroenabled.main+xml'],
+        'vsdx' => ['application/vnd.ms-visio.drawing.main+xml'],
+        'vsf' => ['application/vnd.vsf'],
+        'vss' => ['application/vnd.visio'],
+        'vssm' => ['application/vnd.ms-visio.stencil.macroenabled.main+xml'],
+        'vssx' => ['application/vnd.ms-visio.stencil.main+xml'],
+        'vst' => ['application/vnd.visio', 'image/x-icb', 'image/x-tga'],
+        'vstm' => ['application/vnd.ms-visio.template.macroenabled.main+xml'],
+        'vstx' => ['application/vnd.ms-visio.template.main+xml'],
+        'vsw' => ['application/vnd.visio'],
+        'vtt' => ['text/vtt'],
+        'vtu' => ['model/vnd.vtu'],
+        'vxml' => ['application/voicexml+xml'],
+        'w3d' => ['application/x-director'],
+        'wad' => ['application/x-doom', 'application/x-doom-wad', 'application/x-wii-wad'],
+        'wav' => ['audio/wav', 'audio/vnd.wave', 'audio/x-wav'],
+        'wax' => ['application/x-ms-asx', 'audio/x-ms-asx', 'audio/x-ms-wax', 'video/x-ms-wax', 'video/x-ms-wmx', 'video/x-ms-wvx'],
+        'wb1' => ['application/x-quattropro'],
+        'wb2' => ['application/x-quattropro'],
+        'wb3' => ['application/x-quattropro'],
+        'wbmp' => ['image/vnd.wap.wbmp'],
+        'wbs' => ['application/vnd.criticaltools.wbs+xml'],
+        'wbxml' => ['application/vnd.wap.wbxml'],
+        'wcm' => ['application/vnd.ms-works'],
+        'wdb' => ['application/vnd.ms-works'],
+        'wdp' => ['image/vnd.ms-photo'],
+        'weba' => ['audio/webm'],
+        'webm' => ['video/webm'],
+        'webp' => ['image/webp'],
+        'wg' => ['application/vnd.pmi.widget'],
+        'wgt' => ['application/widget'],
+        'wim' => ['application/x-ms-wim'],
+        'wk1' => ['application/lotus123', 'application/vnd.lotus-1-2-3', 'application/wk1', 'application/x-123', 'application/x-lotus123', 'zz-application/zz-winassoc-123'],
+        'wk3' => ['application/lotus123', 'application/vnd.lotus-1-2-3', 'application/wk1', 'application/x-123', 'application/x-lotus123', 'zz-application/zz-winassoc-123'],
+        'wk4' => ['application/lotus123', 'application/vnd.lotus-1-2-3', 'application/wk1', 'application/x-123', 'application/x-lotus123', 'zz-application/zz-winassoc-123'],
+        'wkdownload' => ['application/x-partial-download'],
+        'wks' => ['application/lotus123', 'application/vnd.lotus-1-2-3', 'application/vnd.ms-works', 'application/wk1', 'application/x-123', 'application/x-lotus123', 'zz-application/zz-winassoc-123'],
+        'wm' => ['video/x-ms-wm'],
+        'wma' => ['audio/x-ms-wma', 'audio/wma'],
+        'wmd' => ['application/x-ms-wmd'],
+        'wmf' => ['application/wmf', 'application/x-msmetafile', 'application/x-wmf', 'image/wmf', 'image/x-win-metafile', 'image/x-wmf'],
+        'wml' => ['text/vnd.wap.wml'],
+        'wmlc' => ['application/vnd.wap.wmlc'],
+        'wmls' => ['text/vnd.wap.wmlscript'],
+        'wmlsc' => ['application/vnd.wap.wmlscriptc'],
+        'wmv' => ['audio/x-ms-wmv', 'video/x-ms-wmv'],
+        'wmx' => ['application/x-ms-asx', 'audio/x-ms-asx', 'video/x-ms-wax', 'video/x-ms-wmx', 'video/x-ms-wvx'],
+        'wmz' => ['application/x-ms-wmz', 'application/x-msmetafile'],
+        'woff' => ['application/font-woff', 'application/x-font-woff', 'font/woff'],
+        'woff2' => ['font/woff', 'font/woff2'],
+        'wp' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'],
+        'wp4' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'],
+        'wp5' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'],
+        'wp6' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'],
+        'wpd' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'],
+        'wpg' => ['application/x-wpg'],
+        'wpl' => ['application/vnd.ms-wpl'],
+        'wpp' => ['application/vnd.wordperfect', 'application/wordperfect', 'application/x-wordperfect'],
+        'wps' => ['application/vnd.ms-works'],
+        'wqd' => ['application/vnd.wqd'],
+        'wri' => ['application/x-mswrite'],
+        'wrl' => ['model/vrml'],
+        'ws' => ['application/x-wonderswan-rom'],
+        'wsc' => ['application/x-wonderswan-color-rom'],
+        'wsdl' => ['application/wsdl+xml'],
+        'wsgi' => ['text/x-python'],
+        'wspolicy' => ['application/wspolicy+xml'],
+        'wtb' => ['application/vnd.webturbo'],
+        'wv' => ['audio/x-wavpack'],
+        'wvc' => ['audio/x-wavpack-correction'],
+        'wvp' => ['audio/x-wavpack'],
+        'wvx' => ['application/x-ms-asx', 'audio/x-ms-asx', 'video/x-ms-wax', 'video/x-ms-wmx', 'video/x-ms-wvx'],
+        'wwf' => ['application/wwf', 'application/x-wwf'],
+        'x32' => ['application/x-authorware-bin'],
+        'x3d' => ['model/x3d+xml'],
+        'x3db' => ['model/x3d+binary'],
+        'x3dbz' => ['model/x3d+binary'],
+        'x3dv' => ['model/x3d+vrml'],
+        'x3dvz' => ['model/x3d+vrml'],
+        'x3dz' => ['model/x3d+xml'],
+        'x3f' => ['image/x-sigma-x3f'],
+        'xac' => ['application/x-gnucash'],
+        'xaml' => ['application/xaml+xml'],
+        'xap' => ['application/x-silverlight-app'],
+        'xar' => ['application/vnd.xara', 'application/x-xar'],
+        'xbap' => ['application/x-ms-xbap'],
+        'xbd' => ['application/vnd.fujixerox.docuworks.binder'],
+        'xbel' => ['application/x-xbel'],
+        'xbl' => ['application/xml', 'text/xml'],
+        'xbm' => ['image/x-xbitmap'],
+        'xcf' => ['image/x-xcf'],
+        'xcf.bz2' => ['image/x-compressed-xcf'],
+        'xcf.gz' => ['image/x-compressed-xcf'],
+        'xdf' => ['application/xcap-diff+xml'],
+        'xdgapp' => ['application/vnd.flatpak', 'application/vnd.xdgapp'],
+        'xdm' => ['application/vnd.syncml.dm+xml'],
+        'xdp' => ['application/vnd.adobe.xdp+xml'],
+        'xdssc' => ['application/dssc+xml'],
+        'xdw' => ['application/vnd.fujixerox.docuworks'],
+        'xenc' => ['application/xenc+xml'],
+        'xer' => ['application/patch-ops-error+xml'],
+        'xfdf' => ['application/vnd.adobe.xfdf'],
+        'xfdl' => ['application/vnd.xfdl'],
+        'xhe' => ['audio/usac'],
+        'xht' => ['application/xhtml+xml'],
+        'xhtml' => ['application/xhtml+xml'],
+        'xhvml' => ['application/xv+xml'],
+        'xi' => ['audio/x-xi'],
+        'xif' => ['image/vnd.xiff'],
+        'xla' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'],
+        'xlam' => ['application/vnd.ms-excel.addin.macroenabled.12'],
+        'xlc' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'],
+        'xld' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'],
+        'xlf' => ['application/x-xliff', 'application/x-xliff+xml', 'application/xliff+xml'],
+        'xliff' => ['application/x-xliff', 'application/xliff+xml'],
+        'xll' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'],
+        'xlm' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'],
+        'xlr' => ['application/vnd.ms-works'],
+        'xls' => ['application/vnd.ms-excel', 'application/msexcel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'],
+        'xlsb' => ['application/vnd.ms-excel.sheet.binary.macroenabled.12'],
+        'xlsm' => ['application/vnd.ms-excel.sheet.macroenabled.12'],
+        'xlsx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
+        'xlt' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'],
+        'xltm' => ['application/vnd.ms-excel.template.macroenabled.12'],
+        'xltx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.template'],
+        'xlw' => ['application/msexcel', 'application/vnd.ms-excel', 'application/x-msexcel', 'zz-application/zz-winassoc-xls'],
+        'xm' => ['audio/x-xm', 'audio/xm'],
+        'xmf' => ['audio/mobile-xmf', 'audio/x-xmf', 'audio/xmf'],
+        'xmi' => ['text/x-xmi'],
+        'xml' => ['application/xml', 'text/xml'],
+        'xo' => ['application/vnd.olpc-sugar'],
+        'xop' => ['application/xop+xml'],
+        'xpi' => ['application/x-xpinstall'],
+        'xpl' => ['application/xproc+xml'],
+        'xpm' => ['image/x-xpixmap', 'image/x-xpm'],
+        'xpr' => ['application/vnd.is-xpr'],
+        'xps' => ['application/oxps', 'application/vnd.ms-xpsdocument', 'application/xps'],
+        'xpw' => ['application/vnd.intercon.formnet'],
+        'xpx' => ['application/vnd.intercon.formnet'],
+        'xsd' => ['application/xml', 'text/xml'],
+        'xsl' => ['application/xml', 'application/xslt+xml'],
+        'xslfo' => ['text/x-xslfo'],
+        'xslt' => ['application/xslt+xml'],
+        'xsm' => ['application/vnd.syncml+xml'],
+        'xspf' => ['application/x-xspf+xml', 'application/xspf+xml'],
+        'xul' => ['application/vnd.mozilla.xul+xml'],
+        'xvm' => ['application/xv+xml'],
+        'xvml' => ['application/xv+xml'],
+        'xwd' => ['image/x-xwindowdump'],
+        'xyz' => ['chemical/x-xyz'],
+        'xz' => ['application/x-xz'],
+        'yaml' => ['application/x-yaml', 'text/x-yaml', 'text/yaml'],
+        'yang' => ['application/yang'],
+        'yin' => ['application/yin+xml'],
+        'yml' => ['application/x-yaml', 'text/x-yaml', 'text/yaml'],
+        'yt' => ['application/vnd.youtube.yt'],
+        'z1' => ['application/x-zmachine'],
+        'z2' => ['application/x-zmachine'],
+        'z3' => ['application/x-zmachine'],
+        'z4' => ['application/x-zmachine'],
+        'z5' => ['application/x-zmachine'],
+        'z6' => ['application/x-zmachine'],
+        'z64' => ['application/x-n64-rom'],
+        'z7' => ['application/x-zmachine'],
+        'z8' => ['application/x-zmachine'],
+        'zabw' => ['application/x-abiword'],
+        'zaz' => ['application/vnd.zzazz.deck+xml'],
+        'zip' => ['application/zip', 'application/x-zip', 'application/x-zip-compressed'],
+        'zir' => ['application/vnd.zul'],
+        'zirz' => ['application/vnd.zul'],
+        'zmm' => ['application/vnd.handheld-entertainment+xml'],
+        'zoo' => ['application/x-zoo'],
+        'zsav' => ['application/x-spss-sav', 'application/x-spss-savefile'],
+        'zz' => ['application/zlib'],
+        '123' => ['application/lotus123', 'application/vnd.lotus-1-2-3', 'application/wk1', 'application/x-123', 'application/x-lotus123', 'zz-application/zz-winassoc-123'],
+        '602' => ['application/x-t602'],
+        '669' => ['audio/x-mod'],
+    ];
+}
diff --git a/vendor/symfony/mime/MimeTypesInterface.php b/vendor/symfony/mime/MimeTypesInterface.php
new file mode 100644
index 0000000..bdf2042
--- /dev/null
+++ b/vendor/symfony/mime/MimeTypesInterface.php
@@ -0,0 +1,34 @@
+<?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\Mime;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+interface MimeTypesInterface extends MimeTypeGuesserInterface
+{
+    /**
+     * Gets the extensions for the given MIME type.
+     *
+     * @return string[] an array of extensions (first one is the preferred one)
+     */
+    public function getExtensions(string $mimeType): array;
+
+    /**
+     * Gets the MIME types for the given extension.
+     *
+     * @return string[] an array of MIME types (first one is the preferred one)
+     */
+    public function getMimeTypes(string $ext): array;
+}
diff --git a/vendor/symfony/mime/NamedAddress.php b/vendor/symfony/mime/NamedAddress.php
new file mode 100644
index 0000000..b13fd73
--- /dev/null
+++ b/vendor/symfony/mime/NamedAddress.php
@@ -0,0 +1,44 @@
+<?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\Mime;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+final class NamedAddress extends Address
+{
+    private $name;
+
+    public function __construct(string $address, string $name)
+    {
+        parent::__construct($address);
+
+        $this->name = trim(str_replace(["\n", "\r"], '', $name));
+    }
+
+    public function getName(): string
+    {
+        return $this->name;
+    }
+
+    public function getEncodedNamedAddress(): string
+    {
+        return ($n = $this->getName()) ? $n.' <'.$this->getEncodedAddress().'>' : $this->getEncodedAddress();
+    }
+
+    public function toString(): string
+    {
+        return $this->getEncodedNamedAddress();
+    }
+}
diff --git a/vendor/symfony/mime/Part/AbstractMultipartPart.php b/vendor/symfony/mime/Part/AbstractMultipartPart.php
new file mode 100644
index 0000000..34a94d2
--- /dev/null
+++ b/vendor/symfony/mime/Part/AbstractMultipartPart.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\Mime\Part;
+
+use Symfony\Component\Mime\Header\Headers;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+abstract class AbstractMultipartPart extends AbstractPart
+{
+    private $boundary;
+    private $parts = [];
+
+    public function __construct(AbstractPart ...$parts)
+    {
+        parent::__construct();
+
+        foreach ($parts as $part) {
+            $this->parts[] = $part;
+        }
+    }
+
+    /**
+     * @return AbstractPart[]
+     */
+    public function getParts(): array
+    {
+        return $this->parts;
+    }
+
+    public function getMediaType(): string
+    {
+        return 'multipart';
+    }
+
+    public function getPreparedHeaders(): Headers
+    {
+        $headers = parent::getPreparedHeaders();
+        $headers->setHeaderParameter('Content-Type', 'boundary', $this->getBoundary());
+
+        return $headers;
+    }
+
+    public function bodyToString(): string
+    {
+        $parts = $this->getParts();
+        $string = '';
+        foreach ($parts as $part) {
+            $string .= '--'.$this->getBoundary()."\r\n".$part->toString()."\r\n";
+        }
+        $string .= '--'.$this->getBoundary()."--\r\n";
+
+        return $string;
+    }
+
+    public function bodyToIterable(): iterable
+    {
+        $parts = $this->getParts();
+        foreach ($parts as $part) {
+            yield '--'.$this->getBoundary()."\r\n";
+            yield from $part->toIterable();
+            yield "\r\n";
+        }
+        yield '--'.$this->getBoundary()."--\r\n";
+    }
+
+    private function getBoundary(): string
+    {
+        if (null === $this->boundary) {
+            $this->boundary = '_=_symfony_'.time().'_'.bin2hex(random_bytes(16)).'_=_';
+        }
+
+        return $this->boundary;
+    }
+}
diff --git a/vendor/symfony/mime/Part/AbstractPart.php b/vendor/symfony/mime/Part/AbstractPart.php
new file mode 100644
index 0000000..29eaa1e
--- /dev/null
+++ b/vendor/symfony/mime/Part/AbstractPart.php
@@ -0,0 +1,62 @@
+<?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\Mime\Part;
+
+use Symfony\Component\Mime\Header\Headers;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+abstract class AbstractPart
+{
+    private $headers;
+
+    public function __construct()
+    {
+        $this->headers = new Headers();
+    }
+
+    public function getHeaders(): Headers
+    {
+        return $this->headers;
+    }
+
+    public function getPreparedHeaders(): Headers
+    {
+        $headers = clone $this->headers;
+        $headers->setHeaderBody('Parameterized', 'Content-Type', $this->getMediaType().'/'.$this->getMediaSubtype());
+
+        return $headers;
+    }
+
+    public function toString(): string
+    {
+        return $this->getPreparedHeaders()->toString()."\r\n".$this->bodyToString();
+    }
+
+    public function toIterable(): iterable
+    {
+        yield $this->getPreparedHeaders()->toString();
+        yield "\r\n";
+        yield from $this->bodyToIterable();
+    }
+
+    abstract public function bodyToString(): string;
+
+    abstract public function bodyToIterable(): iterable;
+
+    abstract public function getMediaType(): string;
+
+    abstract public function getMediaSubtype(): string;
+}
diff --git a/vendor/symfony/mime/Part/DataPart.php b/vendor/symfony/mime/Part/DataPart.php
new file mode 100644
index 0000000..1cfb1e6
--- /dev/null
+++ b/vendor/symfony/mime/Part/DataPart.php
@@ -0,0 +1,150 @@
+<?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\Mime\Part;
+
+use Symfony\Component\Mime\Exception\InvalidArgumentException;
+use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\MimeTypes;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+class DataPart extends TextPart
+{
+    private static $mimeTypes;
+
+    private $filename;
+    private $mediaType;
+    private $cid;
+    private $handle;
+
+    /**
+     * @param resource|string $body
+     */
+    public function __construct($body, string $filename = null, string $contentType = null, string $encoding = null)
+    {
+        if (null === $contentType) {
+            $contentType = 'application/octet-stream';
+        }
+        list($this->mediaType, $subtype) = explode('/', $contentType);
+
+        parent::__construct($body, null, $subtype, $encoding);
+
+        $this->filename = $filename;
+        $this->setName($filename);
+        $this->setDisposition('attachment');
+    }
+
+    public static function fromPath(string $path, string $name = null, string $contentType = null): self
+    {
+        // FIXME: if file is not readable, exception?
+
+        if (null === $contentType) {
+            $ext = strtolower(substr($path, strrpos($path, '.') + 1));
+            if (null === self::$mimeTypes) {
+                self::$mimeTypes = new MimeTypes();
+            }
+            $contentType = self::$mimeTypes->getMimeTypes($ext)[0] ?? 'application/octet-stream';
+        }
+
+        if (false === $handle = @fopen($path, 'r', false)) {
+            throw new InvalidArgumentException(sprintf('Unable to open path "%s"', $path));
+        }
+        $p = new self($handle, $name ?: basename($path), $contentType);
+        $p->handle = $handle;
+
+        return $p;
+    }
+
+    /**
+     * @return $this
+     */
+    public function asInline()
+    {
+        return $this->setDisposition('inline');
+    }
+
+    public function getContentId(): string
+    {
+        return $this->cid ?: $this->cid = $this->generateContentId();
+    }
+
+    public function hasContentId(): bool
+    {
+        return null !== $this->cid;
+    }
+
+    public function getMediaType(): string
+    {
+        return $this->mediaType;
+    }
+
+    public function getPreparedHeaders(): Headers
+    {
+        $headers = parent::getPreparedHeaders();
+
+        if (null !== $this->cid) {
+            $headers->setHeaderBody('Id', 'Content-ID', $this->cid);
+        }
+
+        if (null !== $this->filename) {
+            $headers->setHeaderParameter('Content-Disposition', 'filename', $this->filename);
+        }
+
+        return $headers;
+    }
+
+    private function generateContentId(): string
+    {
+        return bin2hex(random_bytes(16)).'@symfony';
+    }
+
+    public function __destruct()
+    {
+        if (null !== $this->handle && \is_resource($this->handle)) {
+            fclose($this->handle);
+        }
+    }
+
+    public function __sleep()
+    {
+        // converts the body to a string
+        parent::__sleep();
+
+        $this->_parent = [];
+        foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
+            $r = new \ReflectionProperty(TextPart::class, $name);
+            $r->setAccessible(true);
+            $this->_parent[$name] = $r->getValue($this);
+        }
+        $this->_headers = $this->getHeaders();
+
+        return ['_headers', '_parent', 'filename', 'mediaType'];
+    }
+
+    public function __wakeup()
+    {
+        $r = new \ReflectionProperty(AbstractPart::class, 'headers');
+        $r->setAccessible(true);
+        $r->setValue($this, $this->_headers);
+        unset($this->_headers);
+
+        foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
+            $r = new \ReflectionProperty(TextPart::class, $name);
+            $r->setAccessible(true);
+            $r->setValue($this, $this->_parent[$name]);
+        }
+        unset($this->_parent);
+    }
+}
diff --git a/vendor/symfony/mime/Part/MessagePart.php b/vendor/symfony/mime/Part/MessagePart.php
new file mode 100644
index 0000000..64a5340
--- /dev/null
+++ b/vendor/symfony/mime/Part/MessagePart.php
@@ -0,0 +1,64 @@
+<?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\Mime\Part;
+
+use Symfony\Component\Mime\Message;
+use Symfony\Component\Mime\RawMessage;
+
+/**
+ * @final
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+class MessagePart extends DataPart
+{
+    private $message;
+
+    public function __construct(RawMessage $message)
+    {
+        if ($message instanceof Message) {
+            $name = $message->getHeaders()->getHeaderBody('Subject').'.eml';
+        } else {
+            $name = 'email.eml';
+        }
+        parent::__construct('', $name);
+
+        $this->message = $message;
+    }
+
+    public function getMediaType(): string
+    {
+        return 'message';
+    }
+
+    public function getMediaSubtype(): string
+    {
+        return 'rfc822';
+    }
+
+    public function getBody(): string
+    {
+        return $this->message->toString();
+    }
+
+    public function bodyToString(): string
+    {
+        return $this->getBody();
+    }
+
+    public function bodyToIterable(): iterable
+    {
+        return $this->message->toIterable();
+    }
+}
diff --git a/vendor/symfony/mime/Part/Multipart/AlternativePart.php b/vendor/symfony/mime/Part/Multipart/AlternativePart.php
new file mode 100644
index 0000000..ad316a4
--- /dev/null
+++ b/vendor/symfony/mime/Part/Multipart/AlternativePart.php
@@ -0,0 +1,27 @@
+<?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\Mime\Part\Multipart;
+
+use Symfony\Component\Mime\Part\AbstractMultipartPart;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+final class AlternativePart extends AbstractMultipartPart
+{
+    public function getMediaSubtype(): string
+    {
+        return 'alternative';
+    }
+}
diff --git a/vendor/symfony/mime/Part/Multipart/DigestPart.php b/vendor/symfony/mime/Part/Multipart/DigestPart.php
new file mode 100644
index 0000000..6199e5b
--- /dev/null
+++ b/vendor/symfony/mime/Part/Multipart/DigestPart.php
@@ -0,0 +1,33 @@
+<?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\Mime\Part\Multipart;
+
+use Symfony\Component\Mime\Part\AbstractMultipartPart;
+use Symfony\Component\Mime\Part\MessagePart;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+final class DigestPart extends AbstractMultipartPart
+{
+    public function __construct(MessagePart ...$parts)
+    {
+        parent::__construct(...$parts);
+    }
+
+    public function getMediaSubtype(): string
+    {
+        return 'digest';
+    }
+}
diff --git a/vendor/symfony/mime/Part/Multipart/FormDataPart.php b/vendor/symfony/mime/Part/Multipart/FormDataPart.php
new file mode 100644
index 0000000..75d69a8
--- /dev/null
+++ b/vendor/symfony/mime/Part/Multipart/FormDataPart.php
@@ -0,0 +1,96 @@
+<?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\Mime\Part\Multipart;
+
+use Symfony\Component\Mime\Exception\InvalidArgumentException;
+use Symfony\Component\Mime\Part\AbstractMultipartPart;
+use Symfony\Component\Mime\Part\DataPart;
+use Symfony\Component\Mime\Part\TextPart;
+
+/**
+ * Implements RFC 7578.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+final class FormDataPart extends AbstractMultipartPart
+{
+    private $fields = [];
+
+    /**
+     * @param (string|array|DataPart)[] $fields
+     */
+    public function __construct(array $fields = [])
+    {
+        parent::__construct();
+
+        foreach ($fields as $name => $value) {
+            if (!\is_string($value) && !\is_array($value) && !$value instanceof TextPart) {
+                throw new InvalidArgumentException(sprintf('A form field value can only be a string, an array, or an instance of TextPart ("%s" given).', \is_object($value) ? \get_class($value) : \gettype($value)));
+            }
+
+            $this->fields[$name] = $value;
+        }
+        // HTTP does not support \r\n in header values
+        $this->getHeaders()->setMaxLineLength(PHP_INT_MAX);
+    }
+
+    public function getMediaSubtype(): string
+    {
+        return 'form-data';
+    }
+
+    public function getParts(): array
+    {
+        return $this->prepareFields($this->fields);
+    }
+
+    private function prepareFields(array $fields): array
+    {
+        $values = [];
+        array_walk_recursive($fields, function ($item, $key) use (&$values) {
+            if (!\is_array($item)) {
+                $values[] = $this->preparePart($key, $item);
+            }
+        });
+
+        return $values;
+    }
+
+    private function preparePart($name, $value): TextPart
+    {
+        if (\is_string($value)) {
+            return $this->configurePart($name, new TextPart($value, 'utf-8', 'plain', '8bit'));
+        }
+
+        return $this->configurePart($name, $value);
+    }
+
+    private function configurePart(string $name, TextPart $part): TextPart
+    {
+        static $r;
+
+        if (null === $r) {
+            $r = new \ReflectionProperty(TextPart::class, 'encoding');
+            $r->setAccessible(true);
+        }
+
+        $part->setDisposition('form-data');
+        $part->setName($name);
+        // HTTP does not support \r\n in header values
+        $part->getHeaders()->setMaxLineLength(PHP_INT_MAX);
+        $r->setValue($part, '8bit');
+
+        return $part;
+    }
+}
diff --git a/vendor/symfony/mime/Part/Multipart/MixedPart.php b/vendor/symfony/mime/Part/Multipart/MixedPart.php
new file mode 100644
index 0000000..eaa869f
--- /dev/null
+++ b/vendor/symfony/mime/Part/Multipart/MixedPart.php
@@ -0,0 +1,27 @@
+<?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\Mime\Part\Multipart;
+
+use Symfony\Component\Mime\Part\AbstractMultipartPart;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+final class MixedPart extends AbstractMultipartPart
+{
+    public function getMediaSubtype(): string
+    {
+        return 'mixed';
+    }
+}
diff --git a/vendor/symfony/mime/Part/Multipart/RelatedPart.php b/vendor/symfony/mime/Part/Multipart/RelatedPart.php
new file mode 100644
index 0000000..2d55630
--- /dev/null
+++ b/vendor/symfony/mime/Part/Multipart/RelatedPart.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\Mime\Part\Multipart;
+
+use Symfony\Component\Mime\Part\AbstractMultipartPart;
+use Symfony\Component\Mime\Part\AbstractPart;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+final class RelatedPart extends AbstractMultipartPart
+{
+    private $mainPart;
+
+    public function __construct(AbstractPart $mainPart, AbstractPart $part, AbstractPart ...$parts)
+    {
+        $this->mainPart = $mainPart;
+        $this->prepareParts($part, ...$parts);
+
+        parent::__construct($part, ...$parts);
+    }
+
+    public function getParts(): array
+    {
+        return array_merge([$this->mainPart], parent::getParts());
+    }
+
+    public function getMediaSubtype(): string
+    {
+        return 'related';
+    }
+
+    private function generateContentId(): string
+    {
+        return bin2hex(random_bytes(16)).'@symfony';
+    }
+
+    private function prepareParts(AbstractPart ...$parts): void
+    {
+        foreach ($parts as $part) {
+            if (!$part->getHeaders()->has('Content-ID')) {
+                $part->getHeaders()->setHeaderBody('Id', 'Content-ID', $this->generateContentId());
+            }
+        }
+    }
+}
diff --git a/vendor/symfony/mime/Part/TextPart.php b/vendor/symfony/mime/Part/TextPart.php
new file mode 100644
index 0000000..6a04185
--- /dev/null
+++ b/vendor/symfony/mime/Part/TextPart.php
@@ -0,0 +1,190 @@
+<?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\Mime\Part;
+
+use Symfony\Component\Mime\Encoder\Base64ContentEncoder;
+use Symfony\Component\Mime\Encoder\ContentEncoderInterface;
+use Symfony\Component\Mime\Encoder\EightBitContentEncoder;
+use Symfony\Component\Mime\Encoder\QpContentEncoder;
+use Symfony\Component\Mime\Exception\InvalidArgumentException;
+use Symfony\Component\Mime\Header\Headers;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+class TextPart extends AbstractPart
+{
+    private static $encoders = [];
+
+    private $body;
+    private $charset;
+    private $subtype;
+    private $disposition;
+    private $name;
+    private $encoding;
+
+    /**
+     * @param resource|string $body
+     */
+    public function __construct($body, ?string $charset = 'utf-8', $subtype = 'plain', string $encoding = null)
+    {
+        parent::__construct();
+
+        if (!\is_string($body) && !\is_resource($body)) {
+            throw new \TypeError(sprintf('The body of "%s" must be a string or a resource (got "%s").', self::class, \is_object($body) ? \get_class($body) : \gettype($body)));
+        }
+
+        $this->body = $body;
+        $this->charset = $charset;
+        $this->subtype = $subtype;
+
+        if (null === $encoding) {
+            $this->encoding = $this->chooseEncoding();
+        } else {
+            if ('quoted-printable' !== $encoding && 'base64' !== $encoding && '8bit' !== $encoding) {
+                throw new InvalidArgumentException(sprintf('The encoding must be one of "quoted-printable", "base64", or "8bit" ("%s" given).', $encoding));
+            }
+            $this->encoding = $encoding;
+        }
+    }
+
+    public function getMediaType(): string
+    {
+        return 'text';
+    }
+
+    public function getMediaSubtype(): string
+    {
+        return $this->subtype;
+    }
+
+    /**
+     * @param string $disposition one of attachment, inline, or form-data
+     *
+     * @return $this
+     */
+    public function setDisposition(string $disposition)
+    {
+        $this->disposition = $disposition;
+
+        return $this;
+    }
+
+    /**
+     * Sets the name of the file (used by FormDataPart).
+     *
+     * @return $this
+     */
+    public function setName($name)
+    {
+        $this->name = $name;
+
+        return $this;
+    }
+
+    public function getBody(): string
+    {
+        if (!\is_resource($this->body)) {
+            return $this->body;
+        }
+
+        if (stream_get_meta_data($this->body)['seekable'] ?? false) {
+            rewind($this->body);
+        }
+
+        return stream_get_contents($this->body) ?: '';
+    }
+
+    public function bodyToString(): string
+    {
+        return $this->getEncoder()->encodeString($this->getBody(), $this->charset);
+    }
+
+    public function bodyToIterable(): iterable
+    {
+        if (\is_resource($this->body)) {
+            if (stream_get_meta_data($this->body)['seekable'] ?? false) {
+                rewind($this->body);
+            }
+            yield from $this->getEncoder()->encodeByteStream($this->body);
+        } else {
+            yield $this->getEncoder()->encodeString($this->body);
+        }
+    }
+
+    public function getPreparedHeaders(): Headers
+    {
+        $headers = parent::getPreparedHeaders();
+
+        $headers->setHeaderBody('Parameterized', 'Content-Type', $this->getMediaType().'/'.$this->getMediaSubtype());
+        if ($this->charset) {
+            $headers->setHeaderParameter('Content-Type', 'charset', $this->charset);
+        }
+        if ($this->name) {
+            $headers->setHeaderParameter('Content-Type', 'name', $this->name);
+        }
+        $headers->setHeaderBody('Text', 'Content-Transfer-Encoding', $this->encoding);
+
+        if (!$headers->has('Content-Disposition') && null !== $this->disposition) {
+            $headers->setHeaderBody('Parameterized', 'Content-Disposition', $this->disposition);
+            if ($this->name) {
+                $headers->setHeaderParameter('Content-Disposition', 'name', $this->name);
+            }
+        }
+
+        return $headers;
+    }
+
+    private function getEncoder(): ContentEncoderInterface
+    {
+        if ('8bit' === $this->encoding) {
+            return self::$encoders[$this->encoding] ?? (self::$encoders[$this->encoding] = new EightBitContentEncoder());
+        }
+
+        if ('quoted-printable' === $this->encoding) {
+            return self::$encoders[$this->encoding] ?? (self::$encoders[$this->encoding] = new QpContentEncoder());
+        }
+
+        return self::$encoders[$this->encoding] ?? (self::$encoders[$this->encoding] = new Base64ContentEncoder());
+    }
+
+    private function chooseEncoding(): string
+    {
+        if (null === $this->charset) {
+            return 'base64';
+        }
+
+        return 'quoted-printable';
+    }
+
+    public function __sleep()
+    {
+        // convert resources to strings for serialization
+        if (\is_resource($this->body)) {
+            $this->body = $this->getBody();
+        }
+
+        $this->_headers = $this->getHeaders();
+
+        return ['_headers', 'body', 'charset', 'subtype', 'disposition', 'name', 'encoding'];
+    }
+
+    public function __wakeup()
+    {
+        $r = new \ReflectionProperty(AbstractPart::class, 'headers');
+        $r->setAccessible(true);
+        $r->setValue($this, $this->_headers);
+        unset($this->_headers);
+    }
+}
diff --git a/vendor/symfony/mime/README.md b/vendor/symfony/mime/README.md
new file mode 100644
index 0000000..3288246
--- /dev/null
+++ b/vendor/symfony/mime/README.md
@@ -0,0 +1,18 @@
+MIME Component
+==============
+
+The MIME component allows manipulating MIME messages.
+
+**This Component is experimental**.
+[Experimental features](https://symfony.com/doc/current/contributing/code/experimental.html)
+are not covered by Symfony's
+[Backward Compatibility Promise](https://symfony.com/doc/current/contributing/code/bc.html).
+
+Resources
+---------
+
+  * [Documentation](https://symfony.com/doc/current/components/mime.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)
diff --git a/vendor/symfony/mime/RawMessage.php b/vendor/symfony/mime/RawMessage.php
new file mode 100644
index 0000000..40a2795
--- /dev/null
+++ b/vendor/symfony/mime/RawMessage.php
@@ -0,0 +1,81 @@
+<?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\Mime;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @experimental in 4.3
+ */
+class RawMessage implements \Serializable
+{
+    private $message;
+
+    /**
+     * @param iterable|string $message
+     */
+    public function __construct($message)
+    {
+        $this->message = $message;
+    }
+
+    public function toString(): string
+    {
+        if (\is_string($this->message)) {
+            return $this->message;
+        }
+
+        return $this->message = implode('', iterator_to_array($this->message, false));
+    }
+
+    public function toIterable(): iterable
+    {
+        if (\is_string($this->message)) {
+            yield $this->message;
+
+            return;
+        }
+
+        $message = '';
+        foreach ($this->message as $chunk) {
+            $message .= $chunk;
+            yield $chunk;
+        }
+        $this->message = $message;
+    }
+
+    /**
+     * @internal
+     */
+    final public function serialize()
+    {
+        return serialize($this->__serialize());
+    }
+
+    /**
+     * @internal
+     */
+    final public function unserialize($serialized)
+    {
+        $this->__unserialize(unserialize($serialized));
+    }
+
+    public function __serialize(): array
+    {
+        return [$this->message];
+    }
+
+    public function __unserialize(array $data): void
+    {
+        [$this->message] = $data;
+    }
+}
diff --git a/vendor/symfony/mime/Resources/bin/update_mime_types.php b/vendor/symfony/mime/Resources/bin/update_mime_types.php
new file mode 100644
index 0000000..74a9449
--- /dev/null
+++ b/vendor/symfony/mime/Resources/bin/update_mime_types.php
@@ -0,0 +1,166 @@
+<?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.
+ */
+
+// load new map
+$data = file_get_contents('https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types');
+$new = [];
+foreach (explode("\n", $data) as $line) {
+    if (!$line || '#' == $line[0]) {
+        continue;
+    }
+    $mimeType = substr($line, 0, strpos($line, "\t"));
+    $extensions = explode(' ', substr($line, strrpos($line, "\t") + 1));
+    $new[$mimeType] = $extensions;
+}
+
+$xml = simplexml_load_string(file_get_contents('https://raw.github.com/minad/mimemagic/master/script/freedesktop.org.xml'));
+foreach ($xml as $node) {
+    $exts = [];
+    foreach ($node->glob as $glob) {
+        $pattern = (string) $glob['pattern'];
+        if ('*' != $pattern[0] || '.' != $pattern[1]) {
+            continue;
+        }
+
+        $exts[] = substr($pattern, 2);
+    }
+
+    if (!$exts) {
+        continue;
+    }
+
+    $mt = strtolower((string) $node['type']);
+    $new[$mt] = array_merge($new[$mt] ?? [], $exts);
+    foreach ($node->alias as $alias) {
+        $mt = strtolower((string) $alias['type']);
+        $new[$mt] = array_merge($new[$mt] ?? [], $exts);
+    }
+}
+
+// load current map
+$data = file_get_contents($output = __DIR__.'/../../MimeTypes.php');
+$current = [];
+$pre = '';
+$post = '';
+foreach (explode("\n", $data) as $line) {
+    if (!preg_match("{^        '([^']+/[^']+)' => \['(.+)'\],$}", $line, $matches)) {
+        if (!$current) {
+            $pre .= $line."\n";
+        } else {
+            $post .= $line."\n";
+        }
+        continue;
+    }
+    $current[$matches[1]] = explode("', '", $matches[2]);
+}
+
+// we merge the 2 maps (we never remove old mime types)
+$map = array_replace_recursive($current, $new);
+ksort($map);
+
+$data = $pre;
+foreach ($map as $mimeType => $exts) {
+    $data .= sprintf("        '%s' => ['%s'],\n", $mimeType, implode("', '", array_unique($exts)));
+}
+$data .= $post;
+
+// reverse map
+// we prefill the extensions with some preferences for content-types
+$exts = [
+    'aif' => ['audio/x-aiff'],
+    'aiff' => ['audio/x-aiff'],
+    'aps' => ['application/postscript'],
+    'avi' => ['video/avi'],
+    'bmp' => ['image/bmp'],
+    'bz2' => ['application/x-bz2'],
+    'css' => ['text/css'],
+    'csv' => ['text/csv'],
+    'dmg' => ['application/x-apple-diskimage'],
+    'doc' => ['application/msword'],
+    'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
+    'eml' => ['message/rfc822'],
+    'exe' => ['application/x-ms-dos-executable'],
+    'flv' => ['video/x-flv'],
+    'gif' => ['image/gif'],
+    'gz' => ['application/x-gzip'],
+    'hqx' => ['application/stuffit'],
+    'htm' => ['text/html'],
+    'html' => ['text/html'],
+    'jar' => ['application/x-java-archive'],
+    'jpeg' => ['image/jpeg'],
+    'jpg' => ['image/jpeg'],
+    'js' => ['text/javascript'],
+    'm3u' => ['audio/x-mpegurl'],
+    'm4a' => ['audio/mp4'],
+    'mdb' => ['application/x-msaccess'],
+    'mid' => ['audio/midi'],
+    'midi' => ['audio/midi'],
+    'mov' => ['video/quicktime'],
+    'mp3' => ['audio/mpeg'],
+    'mp4' => ['video/mp4'],
+    'mpeg' => ['video/mpeg'],
+    'mpg' => ['video/mpeg'],
+    'ogg' => ['audio/ogg'],
+    'pdf' => ['application/pdf'],
+    'php' => ['application/x-php'],
+    'php3' => ['application/x-php'],
+    'php4' => ['application/x-php'],
+    'php5' => ['application/x-php'],
+    'png' => ['image/png'],
+    'ppt' => ['application/vnd.ms-powerpoint'],
+    'pptx' => ['application/vnd.openxmlformats-officedocument.presentationml.presentation'],
+    'ps' => ['application/postscript'],
+    'rar' => ['application/x-rar-compressed'],
+    'rtf' => ['application/rtf'],
+    'sit' => ['application/x-stuffit'],
+    'svg' => ['image/svg+xml'],
+    'tar' => ['application/x-tar'],
+    'tif' => ['image/tiff'],
+    'tiff' => ['image/tiff'],
+    'ttf' => ['application/x-font-truetype'],
+    'txt' => ['text/plain'],
+    'vcf' => ['text/x-vcard'],
+    'wav' => ['audio/wav'],
+    'wma' => ['audio/x-ms-wma'],
+    'wmv' => ['audio/x-ms-wmv'],
+    'xls' => ['application/vnd.ms-excel'],
+    'xlsx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
+    'xml' => ['application/xml'],
+    'zip' => ['application/zip'],
+];
+foreach ($map as $mimeType => $extensions) {
+    foreach ($extensions as $extension) {
+        $exts[$extension][] = $mimeType;
+    }
+}
+ksort($exts);
+
+$updated = '';
+$state = 0;
+foreach (explode("\n", $data) as $line) {
+    if (!preg_match("{^        '([^'/]+)' => \['(.+)'\],$}", $line, $matches)) {
+        if (1 === $state) {
+            $state = 2;
+            foreach ($exts as $ext => $mimeTypes) {
+                $updated .= sprintf("        '%s' => ['%s'],\n", $ext, implode("', '", array_unique($mimeTypes)));
+            }
+        }
+        $updated .= $line."\n";
+        continue;
+    }
+    $state = 1;
+}
+
+$updated = preg_replace('{Updated from upstream on .+?\.}', 'Updated from upstream on '.date('Y-m-d'), $updated, -1);
+
+file_put_contents($output, rtrim($updated, "\n")."\n");
+
+echo "Done.\n";
diff --git a/vendor/symfony/mime/Tests/AbstractMimeTypeGuesserTest.php b/vendor/symfony/mime/Tests/AbstractMimeTypeGuesserTest.php
new file mode 100644
index 0000000..70e419c
--- /dev/null
+++ b/vendor/symfony/mime/Tests/AbstractMimeTypeGuesserTest.php
@@ -0,0 +1,117 @@
+<?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\Mime\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\MimeTypeGuesserInterface;
+
+abstract class AbstractMimeTypeGuesserTest extends TestCase
+{
+    public static function tearDownAfterClass(): void
+    {
+        $path = __DIR__.'/Fixtures/mimetypes/to_delete';
+        if (file_exists($path)) {
+            @chmod($path, 0666);
+            @unlink($path);
+        }
+    }
+
+    abstract protected function getGuesser(): MimeTypeGuesserInterface;
+
+    public function testGuessWithLeadingDash()
+    {
+        if (!$this->getGuesser()->isGuesserSupported()) {
+            $this->markTestSkipped('Guesser is not supported');
+        }
+
+        $cwd = getcwd();
+        chdir(__DIR__.'/Fixtures/mimetypes');
+        try {
+            $this->assertEquals('image/gif', $this->getGuesser()->guessMimeType('-test'));
+        } finally {
+            chdir($cwd);
+        }
+    }
+
+    public function testGuessImageWithoutExtension()
+    {
+        if (!$this->getGuesser()->isGuesserSupported()) {
+            $this->markTestSkipped('Guesser is not supported');
+        }
+
+        $this->assertEquals('image/gif', $this->getGuesser()->guessMimeType(__DIR__.'/Fixtures/mimetypes/test'));
+    }
+
+    public function testGuessImageWithDirectory()
+    {
+        if (!$this->getGuesser()->isGuesserSupported()) {
+            $this->markTestSkipped('Guesser is not supported');
+        }
+
+        $this->expectException('\InvalidArgumentException');
+        $this->getGuesser()->guessMimeType(__DIR__.'/Fixtures/mimetypes/directory');
+    }
+
+    public function testGuessImageWithKnownExtension()
+    {
+        if (!$this->getGuesser()->isGuesserSupported()) {
+            $this->markTestSkipped('Guesser is not supported');
+        }
+
+        $this->assertEquals('image/gif', $this->getGuesser()->guessMimeType(__DIR__.'/Fixtures/mimetypes/test.gif'));
+    }
+
+    public function testGuessFileWithUnknownExtension()
+    {
+        if (!$this->getGuesser()->isGuesserSupported()) {
+            $this->markTestSkipped('Guesser is not supported');
+        }
+
+        $this->assertEquals('application/octet-stream', $this->getGuesser()->guessMimeType(__DIR__.'/Fixtures/mimetypes/.unknownextension'));
+    }
+
+    public function testGuessWithIncorrectPath()
+    {
+        if (!$this->getGuesser()->isGuesserSupported()) {
+            $this->markTestSkipped('Guesser is not supported');
+        }
+
+        $this->expectException('\InvalidArgumentException');
+        $this->getGuesser()->guessMimeType(__DIR__.'/Fixtures/mimetypes/not_here');
+    }
+
+    public function testGuessWithNonReadablePath()
+    {
+        if (!$this->getGuesser()->isGuesserSupported()) {
+            $this->markTestSkipped('Guesser is not supported');
+        }
+
+        if ('\\' === \DIRECTORY_SEPARATOR) {
+            $this->markTestSkipped('Can not verify chmod operations on Windows');
+        }
+
+        if (!getenv('USER') || 'root' === getenv('USER')) {
+            $this->markTestSkipped('This test will fail if run under superuser');
+        }
+
+        $path = __DIR__.'/Fixtures/mimetypes/to_delete';
+        touch($path);
+        @chmod($path, 0333);
+
+        if ('0333' == substr(sprintf('%o', fileperms($path)), -4)) {
+            $this->expectException('\InvalidArgumentException');
+            $this->getGuesser()->guessMimeType($path);
+        } else {
+            $this->markTestSkipped('Can not verify chmod operations, change of file permissions failed');
+        }
+    }
+}
diff --git a/vendor/symfony/mime/Tests/AddressTest.php b/vendor/symfony/mime/Tests/AddressTest.php
new file mode 100644
index 0000000..dd7d3ce
--- /dev/null
+++ b/vendor/symfony/mime/Tests/AddressTest.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\Mime\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\NamedAddress;
+
+class AddressTest extends TestCase
+{
+    public function testConstructor()
+    {
+        $a = new Address('fabien@symfonï.com');
+        $this->assertEquals('fabien@symfonï.com', $a->getAddress());
+        $this->assertEquals('fabien@xn--symfon-nwa.com', $a->toString());
+        $this->assertEquals('fabien@xn--symfon-nwa.com', $a->getEncodedAddress());
+    }
+
+    public function testConstructorWithInvalidAddress()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        new Address('fab   pot@symfony.com');
+    }
+
+    public function testCreate()
+    {
+        $this->assertSame($a = new Address('fabien@symfony.com'), Address::create($a));
+        $this->assertSame($b = new NamedAddress('helene@symfony.com', 'Helene'), Address::create($b));
+        $this->assertEquals($a, Address::create('fabien@symfony.com'));
+    }
+
+    public function testCreateWrongArg()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        Address::create(new \stdClass());
+    }
+
+    public function testCreateArray()
+    {
+        $fabien = new Address('fabien@symfony.com');
+        $helene = new NamedAddress('helene@symfony.com', 'Helene');
+        $this->assertSame([$fabien, $helene], Address::createArray([$fabien, $helene]));
+
+        $this->assertEquals([$fabien], Address::createArray(['fabien@symfony.com']));
+    }
+
+    public function testCreateArrayWrongArg()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        Address::createArray([new \stdClass()]);
+    }
+}
diff --git a/vendor/symfony/mime/Tests/CharacterStreamTest.php b/vendor/symfony/mime/Tests/CharacterStreamTest.php
new file mode 100644
index 0000000..62f4dc6
--- /dev/null
+++ b/vendor/symfony/mime/Tests/CharacterStreamTest.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\Mime\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\CharacterStream;
+
+class CharacterStreamTest extends TestCase
+{
+    public function testReadCharactersAreInTact()
+    {
+        $stream = new CharacterStream(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));
+        $stream->write(pack('C*',
+            0xD0, 0xBB,
+            0xD1, 0x8E,
+            0xD0, 0xB1,
+            0xD1, 0x8B,
+            0xD1, 0x85
+        ));
+        $this->assertSame(pack('C*', 0xD0, 0x94), $stream->read(1));
+        $this->assertSame(pack('C*', 0xD0, 0xB6, 0xD0, 0xBE), $stream->read(2));
+        $this->assertSame(pack('C*', 0xD0, 0xBB), $stream->read(1));
+        $this->assertSame(pack('C*', 0xD1, 0x8E, 0xD0, 0xB1, 0xD1, 0x8B), $stream->read(3));
+        $this->assertSame(pack('C*', 0xD1, 0x85), $stream->read(1));
+        $this->assertNull($stream->read(1));
+    }
+
+    public function testCharactersCanBeReadAsByteArrays()
+    {
+        $stream = new CharacterStream(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));
+        $stream->write(pack('C*',
+            0xD0, 0xBB,
+            0xD1, 0x8E,
+            0xD0, 0xB1,
+            0xD1, 0x8B,
+            0xD1, 0x85
+        ));
+        $this->assertEquals([0xD0, 0x94], $stream->readBytes(1));
+        $this->assertEquals([0xD0, 0xB6, 0xD0, 0xBE], $stream->readBytes(2));
+        $this->assertEquals([0xD0, 0xBB], $stream->readBytes(1));
+        $this->assertEquals([0xD1, 0x8E, 0xD0, 0xB1, 0xD1, 0x8B], $stream->readBytes(3));
+        $this->assertEquals([0xD1, 0x85], $stream->readBytes(1));
+        $this->assertNull($stream->readBytes(1));
+    }
+
+    public function testRequestingLargeCharCountPastEndOfStream()
+    {
+        $stream = new CharacterStream(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));
+        $this->assertSame(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE), $stream->read(100));
+        $this->assertNull($stream->read(1));
+    }
+
+    public function testRequestingByteArrayCountPastEndOfStream()
+    {
+        $stream = new CharacterStream(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));
+        $this->assertEquals([0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE], $stream->readBytes(100));
+        $this->assertNull($stream->readBytes(1));
+    }
+
+    public function testPointerOffsetCanBeSet()
+    {
+        $stream = new CharacterStream(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));
+        $this->assertSame(pack('C*', 0xD0, 0x94), $stream->read(1));
+        $stream->setPointer(0);
+        $this->assertSame(pack('C*', 0xD0, 0x94), $stream->read(1));
+        $stream->setPointer(2);
+        $this->assertSame(pack('C*', 0xD0, 0xBE), $stream->read(1));
+    }
+
+    public function testAlgorithmWithFixedWidthCharsets()
+    {
+        $stream = new CharacterStream(pack('C*', 0xD1, 0x8D, 0xD0, 0xBB, 0xD0, 0xB0));
+        $this->assertSame(pack('C*', 0xD1, 0x8D), $stream->read(1));
+        $this->assertSame(pack('C*', 0xD0, 0xBB), $stream->read(1));
+        $this->assertSame(pack('C*', 0xD0, 0xB0), $stream->read(1));
+        $this->assertNull($stream->read(1));
+    }
+}
diff --git a/vendor/symfony/mime/Tests/DependencyInjection/AddMimeTypeGuesserPassTest.php b/vendor/symfony/mime/Tests/DependencyInjection/AddMimeTypeGuesserPassTest.php
new file mode 100644
index 0000000..cf40a46
--- /dev/null
+++ b/vendor/symfony/mime/Tests/DependencyInjection/AddMimeTypeGuesserPassTest.php
@@ -0,0 +1,42 @@
+<?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\Mime\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\Mime\DependencyInjection\AddMimeTypeGuesserPass;
+use Symfony\Component\Mime\FileinfoMimeTypeGuesser;
+use Symfony\Component\Mime\MimeTypes;
+
+class AddMimeTypeGuesserPassTest extends TestCase
+{
+    public function testTags()
+    {
+        $container = new ContainerBuilder();
+        $container->addCompilerPass(new AddMimeTypeGuesserPass());
+
+        $definition = new Definition(FileinfoMimeTypeGuesser::class);
+        $definition->addArgument('/path/to/magic/file');
+        $definition->addTag('mime.mime_type_guesser');
+        $container->setDefinition('some_mime_type_guesser', $definition->setPublic(true));
+        $container->register('mime_types', MimeTypes::class)->setPublic(true);
+        $container->compile();
+
+        $router = $container->getDefinition('mime_types');
+        $calls = $router->getMethodCalls();
+        $this->assertCount(1, $calls);
+        $this->assertEquals('registerGuesser', $calls[0][0]);
+        $this->assertEquals(new Reference('some_mime_type_guesser'), $calls[0][1][0]);
+    }
+}
diff --git a/vendor/symfony/mime/Tests/EmailTest.php b/vendor/symfony/mime/Tests/EmailTest.php
new file mode 100644
index 0000000..1822da1
--- /dev/null
+++ b/vendor/symfony/mime/Tests/EmailTest.php
@@ -0,0 +1,387 @@
+<?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\Mime\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Email;
+use Symfony\Component\Mime\NamedAddress;
+use Symfony\Component\Mime\Part\DataPart;
+use Symfony\Component\Mime\Part\Multipart\AlternativePart;
+use Symfony\Component\Mime\Part\Multipart\MixedPart;
+use Symfony\Component\Mime\Part\Multipart\RelatedPart;
+use Symfony\Component\Mime\Part\TextPart;
+
+class EmailTest extends TestCase
+{
+    public function testSubject()
+    {
+        $e = new Email();
+        $e->subject('Subject');
+        $this->assertEquals('Subject', $e->getSubject());
+    }
+
+    public function testDate()
+    {
+        $e = new Email();
+        $e->date($d = new \DateTimeImmutable());
+        $this->assertSame($d, $e->getDate());
+    }
+
+    public function testReturnPath()
+    {
+        $e = new Email();
+        $e->returnPath('fabien@symfony.com');
+        $this->assertEquals(new Address('fabien@symfony.com'), $e->getReturnPath());
+    }
+
+    public function testSender()
+    {
+        $e = new Email();
+        $e->sender('fabien@symfony.com');
+        $this->assertEquals(new Address('fabien@symfony.com'), $e->getSender());
+
+        $e->sender($fabien = new Address('fabien@symfony.com'));
+        $this->assertSame($fabien, $e->getSender());
+    }
+
+    public function testFrom()
+    {
+        $e = new Email();
+        $helene = new Address('helene@symfony.com');
+        $thomas = new NamedAddress('thomas@symfony.com', 'Thomas');
+        $caramel = new Address('caramel@symfony.com');
+
+        $this->assertSame($e, $e->from('fabien@symfony.com', $helene, $thomas));
+        $v = $e->getFrom();
+        $this->assertCount(3, $v);
+        $this->assertEquals(new Address('fabien@symfony.com'), $v[0]);
+        $this->assertSame($helene, $v[1]);
+        $this->assertSame($thomas, $v[2]);
+
+        $this->assertSame($e, $e->addFrom('lucas@symfony.com', $caramel));
+        $v = $e->getFrom();
+        $this->assertCount(5, $v);
+        $this->assertEquals(new Address('fabien@symfony.com'), $v[0]);
+        $this->assertSame($helene, $v[1]);
+        $this->assertSame($thomas, $v[2]);
+        $this->assertEquals(new Address('lucas@symfony.com'), $v[3]);
+        $this->assertSame($caramel, $v[4]);
+
+        $e = new Email();
+        $e->addFrom('lucas@symfony.com', $caramel);
+        $this->assertCount(2, $e->getFrom());
+
+        $e = new Email();
+        $e->from('lucas@symfony.com');
+        $e->from($caramel);
+        $this->assertSame([$caramel], $e->getFrom());
+    }
+
+    public function testReplyTo()
+    {
+        $e = new Email();
+        $helene = new Address('helene@symfony.com');
+        $thomas = new NamedAddress('thomas@symfony.com', 'Thomas');
+        $caramel = new Address('caramel@symfony.com');
+
+        $this->assertSame($e, $e->replyTo('fabien@symfony.com', $helene, $thomas));
+        $v = $e->getReplyTo();
+        $this->assertCount(3, $v);
+        $this->assertEquals(new Address('fabien@symfony.com'), $v[0]);
+        $this->assertSame($helene, $v[1]);
+        $this->assertSame($thomas, $v[2]);
+
+        $this->assertSame($e, $e->addReplyTo('lucas@symfony.com', $caramel));
+        $v = $e->getReplyTo();
+        $this->assertCount(5, $v);
+        $this->assertEquals(new Address('fabien@symfony.com'), $v[0]);
+        $this->assertSame($helene, $v[1]);
+        $this->assertSame($thomas, $v[2]);
+        $this->assertEquals(new Address('lucas@symfony.com'), $v[3]);
+        $this->assertSame($caramel, $v[4]);
+
+        $e = new Email();
+        $e->addReplyTo('lucas@symfony.com', $caramel);
+        $this->assertCount(2, $e->getReplyTo());
+
+        $e = new Email();
+        $e->replyTo('lucas@symfony.com');
+        $e->replyTo($caramel);
+        $this->assertSame([$caramel], $e->getReplyTo());
+    }
+
+    public function testTo()
+    {
+        $e = new Email();
+        $helene = new Address('helene@symfony.com');
+        $thomas = new NamedAddress('thomas@symfony.com', 'Thomas');
+        $caramel = new Address('caramel@symfony.com');
+
+        $this->assertSame($e, $e->to('fabien@symfony.com', $helene, $thomas));
+        $v = $e->getTo();
+        $this->assertCount(3, $v);
+        $this->assertEquals(new Address('fabien@symfony.com'), $v[0]);
+        $this->assertSame($helene, $v[1]);
+        $this->assertSame($thomas, $v[2]);
+
+        $this->assertSame($e, $e->addTo('lucas@symfony.com', $caramel));
+        $v = $e->getTo();
+        $this->assertCount(5, $v);
+        $this->assertEquals(new Address('fabien@symfony.com'), $v[0]);
+        $this->assertSame($helene, $v[1]);
+        $this->assertSame($thomas, $v[2]);
+        $this->assertEquals(new Address('lucas@symfony.com'), $v[3]);
+        $this->assertSame($caramel, $v[4]);
+
+        $e = new Email();
+        $e->addTo('lucas@symfony.com', $caramel);
+        $this->assertCount(2, $e->getTo());
+
+        $e = new Email();
+        $e->to('lucas@symfony.com');
+        $e->to($caramel);
+        $this->assertSame([$caramel], $e->getTo());
+    }
+
+    public function testCc()
+    {
+        $e = new Email();
+        $helene = new Address('helene@symfony.com');
+        $thomas = new NamedAddress('thomas@symfony.com', 'Thomas');
+        $caramel = new Address('caramel@symfony.com');
+
+        $this->assertSame($e, $e->cc('fabien@symfony.com', $helene, $thomas));
+        $v = $e->getCc();
+        $this->assertCount(3, $v);
+        $this->assertEquals(new Address('fabien@symfony.com'), $v[0]);
+        $this->assertSame($helene, $v[1]);
+        $this->assertSame($thomas, $v[2]);
+
+        $this->assertSame($e, $e->addCc('lucas@symfony.com', $caramel));
+        $v = $e->getCc();
+        $this->assertCount(5, $v);
+        $this->assertEquals(new Address('fabien@symfony.com'), $v[0]);
+        $this->assertSame($helene, $v[1]);
+        $this->assertSame($thomas, $v[2]);
+        $this->assertEquals(new Address('lucas@symfony.com'), $v[3]);
+        $this->assertSame($caramel, $v[4]);
+
+        $e = new Email();
+        $e->addCc('lucas@symfony.com', $caramel);
+        $this->assertCount(2, $e->getCc());
+
+        $e = new Email();
+        $e->cc('lucas@symfony.com');
+        $e->cc($caramel);
+        $this->assertSame([$caramel], $e->getCc());
+    }
+
+    public function testBcc()
+    {
+        $e = new Email();
+        $helene = new Address('helene@symfony.com');
+        $thomas = new NamedAddress('thomas@symfony.com', 'Thomas');
+        $caramel = new Address('caramel@symfony.com');
+
+        $this->assertSame($e, $e->bcc('fabien@symfony.com', $helene, $thomas));
+        $v = $e->getBcc();
+        $this->assertCount(3, $v);
+        $this->assertEquals(new Address('fabien@symfony.com'), $v[0]);
+        $this->assertSame($helene, $v[1]);
+        $this->assertSame($thomas, $v[2]);
+
+        $this->assertSame($e, $e->addBcc('lucas@symfony.com', $caramel));
+        $v = $e->getBcc();
+        $this->assertCount(5, $v);
+        $this->assertEquals(new Address('fabien@symfony.com'), $v[0]);
+        $this->assertSame($helene, $v[1]);
+        $this->assertSame($thomas, $v[2]);
+        $this->assertEquals(new Address('lucas@symfony.com'), $v[3]);
+        $this->assertSame($caramel, $v[4]);
+
+        $e = new Email();
+        $e->addBcc('lucas@symfony.com', $caramel);
+        $this->assertCount(2, $e->getBcc());
+
+        $e = new Email();
+        $e->bcc('lucas@symfony.com');
+        $e->bcc($caramel);
+        $this->assertSame([$caramel], $e->getBcc());
+    }
+
+    public function testPriority()
+    {
+        $e = new Email();
+        $this->assertEquals(3, $e->getPriority());
+
+        $e->priority(1);
+        $this->assertEquals(1, $e->getPriority());
+        $e->priority(10);
+        $this->assertEquals(5, $e->getPriority());
+        $e->priority(-10);
+        $this->assertEquals(1, $e->getPriority());
+    }
+
+    public function testGenerateBodyThrowsWhenEmptyBody()
+    {
+        $this->expectException(\LogicException::class);
+        (new Email())->getBody();
+    }
+
+    public function testGetBody()
+    {
+        $e = new Email();
+        $e->setBody($text = new TextPart('text content'));
+        $this->assertEquals($text, $e->getBody());
+    }
+
+    public function testGenerateBody()
+    {
+        $text = new TextPart('text content');
+        $html = new TextPart('html content', 'utf-8', 'html');
+        $att = new DataPart($file = fopen(__DIR__.'/Fixtures/mimetypes/test', 'r'));
+        $img = new DataPart($image = fopen(__DIR__.'/Fixtures/mimetypes/test.gif', 'r'), 'test.gif');
+
+        $e = new Email();
+        $e->text('text content');
+        $this->assertEquals($text, $e->getBody());
+        $this->assertEquals('text content', $e->getTextBody());
+
+        $e = new Email();
+        $e->html('html content');
+        $this->assertEquals($html, $e->getBody());
+        $this->assertEquals('html content', $e->getHtmlBody());
+
+        $e = new Email();
+        $e->html('html content');
+        $e->text('text content');
+        $this->assertEquals(new AlternativePart($text, $html), $e->getBody());
+
+        $e = new Email();
+        $e->html('html content', 'iso-8859-1');
+        $e->text('text content', 'iso-8859-1');
+        $this->assertEquals('iso-8859-1', $e->getTextCharset());
+        $this->assertEquals('iso-8859-1', $e->getHtmlCharset());
+        $this->assertEquals(new AlternativePart(new TextPart('text content', 'iso-8859-1'), new TextPart('html content', 'iso-8859-1', 'html')), $e->getBody());
+
+        $e = new Email();
+        $e->attach($file);
+        $e->text('text content');
+        $this->assertEquals(new MixedPart($text, $att), $e->getBody());
+
+        $e = new Email();
+        $e->attach($file);
+        $e->html('html content');
+        $this->assertEquals(new MixedPart($html, $att), $e->getBody());
+
+        $e = new Email();
+        $e->attach($file);
+        $this->assertEquals(new MixedPart($att), $e->getBody());
+
+        $e = new Email();
+        $e->html('html content');
+        $e->text('text content');
+        $e->attach($file);
+        $this->assertEquals(new MixedPart(new AlternativePart($text, $html), $att), $e->getBody());
+
+        $e = new Email();
+        $e->html('html content');
+        $e->text('text content');
+        $e->attach($file);
+        $e->attach($image, 'test.gif');
+        $this->assertEquals(new MixedPart(new AlternativePart($text, $html), $att, $img), $e->getBody());
+
+        $e = new Email();
+        $e->text('text content');
+        $e->attach($file);
+        $e->attach($image, 'test.gif');
+        $this->assertEquals(new MixedPart($text, $att, $img), $e->getBody());
+
+        $e = new Email();
+        $e->html($content = 'html content <img src="test.gif">');
+        $e->text('text content');
+        $e->attach($file);
+        $e->attach($image, 'test.gif');
+        $fullhtml = new TextPart($content, 'utf-8', 'html');
+        $this->assertEquals(new MixedPart(new AlternativePart($text, $fullhtml), $att, $img), $e->getBody());
+
+        $e = new Email();
+        $e->html($content = 'html content <img src="cid:test.gif">');
+        $e->text('text content');
+        $e->attach($file);
+        $e->attach($image, 'test.gif');
+        $body = $e->getBody();
+        $this->assertInstanceOf(MixedPart::class, $body);
+        $this->assertCount(2, $related = $body->getParts());
+        $this->assertInstanceOf(RelatedPart::class, $related[0]);
+        $this->assertEquals($att, $related[1]);
+        $this->assertCount(2, $parts = $related[0]->getParts());
+        $this->assertInstanceOf(AlternativePart::class, $parts[0]);
+        $generatedHtml = $parts[0]->getParts()[1];
+        $this->assertStringContainsString('cid:'.$parts[1]->getContentId(), $generatedHtml->getBody());
+
+        $content = 'html content <img src="cid:test.gif">';
+        $r = fopen('php://memory', 'r+', false);
+        fwrite($r, $content);
+        rewind($r);
+
+        $e = new Email();
+        $e->html($r);
+        // embedding the same image twice results in one image only in the email
+        $e->embed($image, 'test.gif');
+        $e->embed($image, 'test.gif');
+        $body = $e->getBody();
+        $this->assertInstanceOf(RelatedPart::class, $body);
+        // 2 parts only, not 3 (text + embedded image once)
+        $this->assertCount(2, $parts = $body->getParts());
+        $this->assertStringMatchesFormat('html content <img src=3D"cid:%s@symfony">', $parts[0]->bodyToString());
+    }
+
+    public function testAttachments()
+    {
+        $contents = file_get_contents($name = __DIR__.'/Fixtures/mimetypes/test', 'r');
+        $att = new DataPart($file = fopen($name, 'r'), 'test');
+        $inline = (new DataPart($contents, 'test'))->asInline();
+        $e = new Email();
+        $e->attach($file, 'test');
+        $e->embed($contents, 'test');
+        $this->assertEquals([$att, $inline], $e->getAttachments());
+
+        $att = DataPart::fromPath($name, 'test');
+        $inline = DataPart::fromPath($name, 'test')->asInline();
+        $e = new Email();
+        $e->attachFromPath($name);
+        $e->embedFromPath($name);
+        $this->assertEquals([$att->bodyToString(), $inline->bodyToString()], array_map(function (DataPart $a) { return $a->bodyToString(); }, $e->getAttachments()));
+        $this->assertEquals([$att->getPreparedHeaders(), $inline->getPreparedHeaders()], array_map(function (DataPart $a) { return $a->getPreparedHeaders(); }, $e->getAttachments()));
+    }
+
+    public function testSerialize()
+    {
+        $r = fopen('php://memory', 'r+', false);
+        fwrite($r, 'Text content');
+        rewind($r);
+
+        $e = new Email();
+        $e->from('fabien@symfony.com');
+        $e->text($r);
+        $e->html($r);
+        $name = __DIR__.'/Fixtures/mimetypes/test';
+        $file = fopen($name, 'r');
+        $e->attach($file, 'test');
+        $expected = clone $e;
+        $n = unserialize(serialize($e));
+        $this->assertEquals($expected->getHeaders(), $n->getHeaders());
+        $this->assertEquals($e->getBody(), $n->getBody());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Encoder/Base64EncoderTest.php b/vendor/symfony/mime/Tests/Encoder/Base64EncoderTest.php
new file mode 100644
index 0000000..a90fbb7
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Encoder/Base64EncoderTest.php
@@ -0,0 +1,158 @@
+<?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\Mime\Tests\Encoder;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Encoder\Base64Encoder;
+
+class Base64EncoderTest extends TestCase
+{
+    /*
+    There's really no point in testing the entire base64 encoding to the
+    level QP encoding has been tested.  base64_encode() has been in PHP for
+    years.
+    */
+
+    public function testInputOutputRatioIs3to4Bytes()
+    {
+        /*
+        RFC 2045, 6.8
+
+         The encoding process represents 24-bit groups of input bits as output
+         strings of 4 encoded characters.  Proceeding from left to right, a
+         24-bit input group is formed by concatenating 3 8bit input groups.
+         These 24 bits are then treated as 4 concatenated 6-bit groups, each
+         of which is translated into a single digit in the base64 alphabet.
+         */
+
+        $encoder = new Base64Encoder();
+        $this->assertEquals('MTIz', $encoder->encodeString('123'), '3 bytes of input should yield 4 bytes of output');
+        $this->assertEquals('MTIzNDU2', $encoder->encodeString('123456'), '6 bytes in input should yield 8 bytes of output');
+        $this->assertEquals('MTIzNDU2Nzg5', $encoder->encodeString('123456789'), '%s: 9 bytes in input should yield 12 bytes of output');
+    }
+
+    public function testPadLength()
+    {
+        /*
+        RFC 2045, 6.8
+
+       Special processing is performed if fewer than 24 bits are available
+       at the end of the data being encoded.  A full encoding quantum is
+       always completed at the end of a body.  When fewer than 24 input bits
+       are available in an input group, zero bits are added (on the right)
+       to form an integral number of 6-bit groups.  Padding at the end of
+       the data is performed using the "=" character.  Since all base64
+       input is an integral number of octets, only the following cases can
+       arise: (1) the final quantum of encoding input is an integral
+       multiple of 24 bits; here, the final unit of encoded output will be
+       an integral multiple of 4 characters with no "=" padding, (2) the
+       final quantum of encoding input is exactly 8 bits; here, the final
+       unit of encoded output will be two characters followed by two "="
+       padding characters, or (3) the final quantum of encoding input is
+       exactly 16 bits; here, the final unit of encoded output will be three
+       characters followed by one "=" padding character.
+       */
+
+        $encoder = new Base64Encoder();
+        for ($i = 0; $i < 30; ++$i) {
+            $input = pack('C', random_int(0, 255));
+            $this->assertRegExp('~^[a-zA-Z0-9/\+]{2}==$~', $encoder->encodeString($input), 'A single byte should have 2 bytes of padding');
+        }
+
+        for ($i = 0; $i < 30; ++$i) {
+            $input = pack('C*', random_int(0, 255), random_int(0, 255));
+            $this->assertRegExp('~^[a-zA-Z0-9/\+]{3}=$~', $encoder->encodeString($input), 'Two bytes should have 1 byte of padding');
+        }
+
+        for ($i = 0; $i < 30; ++$i) {
+            $input = pack('C*', random_int(0, 255), random_int(0, 255), random_int(0, 255));
+            $this->assertRegExp('~^[a-zA-Z0-9/\+]{4}$~', $encoder->encodeString($input), 'Three bytes should have no padding');
+        }
+    }
+
+    public function testMaximumLineLengthIs76Characters()
+    {
+        /*
+         The encoded output stream must be represented in lines of no more
+         than 76 characters each.  All line breaks or other characters not
+         found in Table 1 must be ignored by decoding software.
+         */
+
+        $input =
+        'abcdefghijklmnopqrstuvwxyz'.
+        'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
+        '1234567890'.
+        'abcdefghijklmnopqrstuvwxyz'.
+        'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
+        '1234567890'.
+        'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+        $output =
+        'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk'.//38
+        'NERUZHSElKS0xNTk9QUVJTVFVWV1hZWjEyMzQ1'."\r\n".//76 *
+        'Njc4OTBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3'.//38
+        'h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFla'."\r\n".//76 *
+        'MTIzNDU2Nzg5MEFCQ0RFRkdISUpLTE1OT1BRUl'.//38
+        'NUVVZXWFla';                                       //48
+
+        $encoder = new Base64Encoder();
+        $this->assertEquals($output, $encoder->encodeString($input), 'Lines should be no more than 76 characters');
+    }
+
+    public function testMaximumLineLengthCanBeSpecified()
+    {
+        $input =
+        'abcdefghijklmnopqrstuvwxyz'.
+        'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
+        '1234567890'.
+        'abcdefghijklmnopqrstuvwxyz'.
+        'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
+        '1234567890'.
+        'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+        $output =
+        'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk'.//38
+        'NERUZHSElKS0'."\r\n".//50 *
+        'xNTk9QUVJTVFVWV1hZWjEyMzQ1Njc4OTBhYmNk'.//38
+        'ZWZnaGlqa2xt'."\r\n".//50 *
+        'bm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1'.//38
+        'BRUlNUVVZXWF'."\r\n".//50 *
+        'laMTIzNDU2Nzg5MEFCQ0RFRkdISUpLTE1OT1BR'.//38
+        'UlNUVVZXWFla';                                     //50 *
+
+        $encoder = new Base64Encoder();
+        $this->assertEquals($output, $encoder->encodeString($input, 'utf-8', 0, 50), 'Lines should be no more than 100 characters');
+    }
+
+    public function testFirstLineLengthCanBeDifferent()
+    {
+        $input =
+        'abcdefghijklmnopqrstuvwxyz'.
+        'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
+        '1234567890'.
+        'abcdefghijklmnopqrstuvwxyz'.
+        'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
+        '1234567890'.
+        'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+        $output =
+        'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk'.//38
+        'NERUZHSElKS0xNTk9QU'."\r\n".//57 *
+        'VJTVFVWV1hZWjEyMzQ1Njc4OTBhYmNkZWZnaGl'.//38
+        'qa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLT'."\r\n".//76 *
+        'E1OT1BRUlNUVVZXWFlaMTIzNDU2Nzg5MEFCQ0R'.//38
+        'FRkdISUpLTE1OT1BRUlNUVVZXWFla';                    //67
+
+        $encoder = new Base64Encoder();
+        $this->assertEquals($output, $encoder->encodeString($input, 'utf-8', 19), 'First line offset is 19 so first line should be 57 chars long');
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Encoder/Base64MimeHeaderEncoderTest.php b/vendor/symfony/mime/Tests/Encoder/Base64MimeHeaderEncoderTest.php
new file mode 100644
index 0000000..34bfe0d
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Encoder/Base64MimeHeaderEncoderTest.php
@@ -0,0 +1,23 @@
+<?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\Mime\Tests\Encoder;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Encoder\Base64MimeHeaderEncoder;
+
+class Base64MimeHeaderEncoderTest extends TestCase
+{
+    public function testNameIsB()
+    {
+        $this->assertEquals('B', (new Base64MimeHeaderEncoder())->getName());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Encoder/QpEncoderTest.php b/vendor/symfony/mime/Tests/Encoder/QpEncoderTest.php
new file mode 100644
index 0000000..bf08b2f
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Encoder/QpEncoderTest.php
@@ -0,0 +1,213 @@
+<?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\Mime\Tests\Encoder;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Encoder\QpEncoder;
+
+class QpEncoderTest extends TestCase
+{
+    /* -- RFC 2045, 6.7 --
+    (1)   (General 8bit representation) Any octet, except a CR or
+                    LF that is part of a CRLF line break of the canonical
+                    (standard) form of the data being encoded, may be
+                    represented by an "=" followed by a two digit
+                    hexadecimal representation of the octet's value.  The
+                    digits of the hexadecimal alphabet, for this purpose,
+                    are "0123456789ABCDEF".  Uppercase letters must be
+                    used; lowercase letters are not allowed.  Thus, for
+                    example, the decimal value 12 (US-ASCII form feed) can
+                    be represented by "=0C", and the decimal value 61 (US-
+                    ASCII EQUAL SIGN) can be represented by "=3D".  This
+                    rule must be followed except when the following rules
+                    allow an alternative encoding.
+                    */
+
+    public function testPermittedCharactersAreNotEncoded()
+    {
+        /* -- RFC 2045, 6.7 --
+        (2)   (Literal representation) Octets with decimal values of
+                    33 through 60 inclusive, and 62 through 126, inclusive,
+                    MAY be represented as the US-ASCII characters which
+                    correspond to those octets (EXCLAMATION POINT through
+                    LESS THAN, and GREATER THAN through TILDE,
+                    respectively).
+                    */
+
+        $encoder = new QpEncoder();
+        foreach (array_merge(range(33, 60), range(62, 126)) as $ordinal) {
+            $char = \chr($ordinal);
+            $this->assertSame($char, $encoder->encodeString($char));
+        }
+    }
+
+    public function testWhiteSpaceAtLineEndingIsEncoded()
+    {
+        /* -- RFC 2045, 6.7 --
+        (3)   (White Space) Octets with values of 9 and 32 MAY be
+                    represented as US-ASCII TAB (HT) and SPACE characters,
+                    respectively, but MUST NOT be so represented at the end
+                    of an encoded line.  Any TAB (HT) or SPACE characters
+                    on an encoded line MUST thus be followed on that line
+                    by a printable character.  In particular, an "=" at the
+                    end of an encoded line, indicating a soft line break
+                    (see rule #5) may follow one or more TAB (HT) or SPACE
+                    characters.  It follows that an octet with decimal
+                    value 9 or 32 appearing at the end of an encoded line
+                    must be represented according to Rule #1.  This rule is
+                    necessary because some MTAs (Message Transport Agents,
+                    programs which transport messages from one user to
+                    another, or perform a portion of such transfers) are
+                    known to pad lines of text with SPACEs, and others are
+                    known to remove "white space" characters from the end
+                    of a line.  Therefore, when decoding a Quoted-Printable
+                    body, any trailing white space on a line must be
+                    deleted, as it will necessarily have been added by
+                    intermediate transport agents.
+                    */
+
+        $encoder = new QpEncoder();
+
+        $HT = \chr(0x09); // 9
+        $SPACE = \chr(0x20); // 32
+
+        // HT
+        $string = 'a'.$HT.$HT."\r\n".'b';
+        $this->assertEquals('a'.$HT.'=09'."\r\n".'b', $encoder->encodeString($string));
+
+        // SPACE
+        $string = 'a'.$SPACE.$SPACE."\r\n".'b';
+        $this->assertEquals('a'.$SPACE.'=20'."\r\n".'b', $encoder->encodeString($string));
+    }
+
+    public function testCRLFIsLeftAlone()
+    {
+        /*
+        (4)   (Line Breaks) A line break in a text body, represented
+                    as a CRLF sequence in the text canonical form, must be
+                    represented by a (RFC 822) line break, which is also a
+                    CRLF sequence, in the Quoted-Printable encoding.  Since
+                    the canonical representation of media types other than
+                    text do not generally include the representation of
+                    line breaks as CRLF sequences, no hard line breaks
+                    (i.e. line breaks that are intended to be meaningful
+                    and to be displayed to the user) can occur in the
+                    quoted-printable encoding of such types.  Sequences
+                    like "=0D", "=0A", "=0A=0D" and "=0D=0A" will routinely
+                    appear in non-text data represented in quoted-
+                    printable, of course.
+
+                    Note that many implementations may elect to encode the
+                    local representation of various content types directly
+                    rather than converting to canonical form first,
+                    encoding, and then converting back to local
+                    representation.  In particular, this may apply to plain
+                    text material on systems that use newline conventions
+                    other than a CRLF terminator sequence.  Such an
+                    implementation optimization is permissible, but only
+                    when the combined canonicalization-encoding step is
+                    equivalent to performing the three steps separately.
+                    */
+
+        $encoder = new QpEncoder();
+        $string = 'a'."\r\n".'b'."\r\n".'c'."\r\n";
+        $this->assertEquals($string, $encoder->encodeString($string));
+    }
+
+    public function testLinesLongerThan76CharactersAreSoftBroken()
+    {
+        /*
+        (5)   (Soft Line Breaks) The Quoted-Printable encoding
+                    REQUIRES that encoded lines be no more than 76
+                    characters long.  If longer lines are to be encoded
+                    with the Quoted-Printable encoding, "soft" line breaks
+                    must be used.  An equal sign as the last character on a
+                    encoded line indicates such a non-significant ("soft")
+                    line break in the encoded text.
+                    */
+
+        $encoder = new QpEncoder();
+        $input = str_repeat('a', 140);
+        $output = '';
+        for ($i = 0; $i < 140; ++$i) {
+            // we read 4 chars at a time (max is 75)
+            if (18 * 4 /* 72 */ == $i) {
+                $output .= "=\r\n";
+            }
+            $output .= 'a';
+        }
+        $this->assertEquals($output, $encoder->encodeString($input));
+    }
+
+    public function testMaxLineLengthCanBeSpecified()
+    {
+        $encoder = new QpEncoder();
+        $input = str_repeat('a', 100);
+        $output = '';
+        for ($i = 0; $i < 100; ++$i) {
+            // we read 4 chars at a time (max is 53)
+            if (13 * 4 /* 52 */ == $i) {
+                $output .= "=\r\n";
+            }
+            $output .= 'a';
+        }
+        $this->assertEquals($output, $encoder->encodeString($input, 'utf-8', 0, 54));
+    }
+
+    public function testBytesBelowPermittedRangeAreEncoded()
+    {
+        // According to Rule (1 & 2)
+        $encoder = new QpEncoder();
+        foreach (range(0, 32) as $ordinal) {
+            $char = \chr($ordinal);
+            $this->assertEquals(sprintf('=%02X', $ordinal), $encoder->encodeString($char));
+        }
+    }
+
+    public function testDecimalByte61IsEncoded()
+    {
+        // According to Rule (1 & 2)
+        $encoder = new QpEncoder();
+        $this->assertEquals('=3D', $encoder->encodeString('='));
+    }
+
+    public function testBytesAbovePermittedRangeAreEncoded()
+    {
+        // According to Rule (1 & 2)
+        $encoder = new QpEncoder();
+        foreach (range(127, 255) as $ordinal) {
+            $this->assertSame(sprintf('=%02X', $ordinal), $encoder->encodeString(\chr($ordinal), 'iso-8859-1'));
+        }
+    }
+
+    public function testFirstLineLengthCanBeDifferent()
+    {
+        $encoder = new QpEncoder();
+        $input = str_repeat('a', 140);
+        $output = '';
+        for ($i = 0; $i < 140; ++$i) {
+            // we read 4 chars at a time (max is 54 for the first line and 75 for the second one)
+            if (13 * 4 == $i || 13 * 4 + 18 * 4 == $i) {
+                $output .= "=\r\n";
+            }
+            $output .= 'a';
+        }
+        $this->assertEquals($output, $encoder->encodeString($input, 'utf-8', 22), 'First line should start at offset 22 so can only have max length 54');
+    }
+
+    public function testTextIsPreWrapped()
+    {
+        $encoder = new QpEncoder();
+        $input = str_repeat('a', 70)."\r\n".str_repeat('a', 70)."\r\n".str_repeat('a', 70);
+        $this->assertEquals($input, $encoder->encodeString($input));
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Encoder/QpMimeHeaderEncoderTest.php b/vendor/symfony/mime/Tests/Encoder/QpMimeHeaderEncoderTest.php
new file mode 100644
index 0000000..f075452
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Encoder/QpMimeHeaderEncoderTest.php
@@ -0,0 +1,139 @@
+<?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\Mime\Tests\Encoder;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Encoder\QpMimeHeaderEncoder;
+
+class QpMimeHeaderEncoderTest extends TestCase
+{
+    public function testNameIsQ()
+    {
+        $encoder = new QpMimeHeaderEncoder();
+        $this->assertEquals('Q', $encoder->getName());
+    }
+
+    public function testSpaceAndTabNeverAppear()
+    {
+        /* -- RFC 2047, 4.
+         Only a subset of the printable ASCII characters may be used in
+        'encoded-text'.  Space and tab characters are not allowed, so that
+        the beginning and end of an 'encoded-word' are obvious.
+        */
+
+        $encoder = new QpMimeHeaderEncoder();
+        $this->assertNotRegExp('~[ \t]~', $encoder->encodeString("a \t b"), 'encoded-words in headers cannot contain LWSP as per RFC 2047.');
+    }
+
+    public function testSpaceIsRepresentedByUnderscore()
+    {
+        /* -- RFC 2047, 4.2.
+        (2) The 8-bit hexadecimal value 20 (e.g., ISO-8859-1 SPACE) may be
+        represented as "_" (underscore, ASCII 95.).  (This character may
+        not pass through some internetwork mail gateways, but its use
+        will greatly enhance readability of "Q" encoded data with mail
+        readers that do not support this encoding.)  Note that the "_"
+        always represents hexadecimal 20, even if the SPACE character
+        occupies a different code position in the character set in use.
+        */
+        $encoder = new QpMimeHeaderEncoder();
+        $this->assertEquals('a_b', $encoder->encodeString('a b'), 'Spaces can be represented by more readable underscores as per RFC 2047.');
+    }
+
+    public function testEqualsAndQuestionAndUnderscoreAreEncoded()
+    {
+        /* -- RFC 2047, 4.2.
+        (3) 8-bit values which correspond to printable ASCII characters other
+        than "=", "?", and "_" (underscore), MAY be represented as those
+        characters.  (But see section 5 for restrictions.)  In
+        particular, SPACE and TAB MUST NOT be represented as themselves
+        within encoded words.
+        */
+        $encoder = new QpMimeHeaderEncoder();
+        $this->assertEquals('=3D=3F=5F', $encoder->encodeString('=?_'), 'Chars =, ? and _ (underscore) may not appear as per RFC 2047.');
+    }
+
+    public function testParensAndQuotesAreEncoded()
+    {
+        /* -- RFC 2047, 5 (2).
+         A "Q"-encoded 'encoded-word' which appears in a 'comment' MUST NOT
+        contain the characters "(", ")" or "
+        */
+
+        $encoder = new QpMimeHeaderEncoder();
+        $this->assertEquals('=28=22=29', $encoder->encodeString('(")'), 'Chars (, " (DQUOTE) and ) may not appear as per RFC 2047.');
+    }
+
+    public function testOnlyCharactersAllowedInPhrasesAreUsed()
+    {
+        /* -- RFC 2047, 5.
+        (3) As a replacement for a 'word' entity within a 'phrase', for example,
+        one that precedes an address in a From, To, or Cc header.  The ABNF
+        definition for 'phrase' from RFC 822 thus becomes:
+
+        phrase = 1*( encoded-word / word )
+
+        In this case the set of characters that may be used in a "Q"-encoded
+        'encoded-word' is restricted to: <upper and lower case ASCII
+        letters, decimal digits, "!", "*", "+", "-", "/", "=", and "_"
+        (underscore, ASCII 95.)>.  An 'encoded-word' that appears within a
+        'phrase' MUST be separated from any adjacent 'word', 'text' or
+        'special' by 'linear-white-space'.
+        */
+
+        $allowedBytes = array_merge(
+            range(\ord('a'), \ord('z')), range(\ord('A'), \ord('Z')),
+            range(\ord('0'), \ord('9')),
+            [\ord('!'), \ord('*'), \ord('+'), \ord('-'), \ord('/')]
+        );
+        $encoder = new QpMimeHeaderEncoder();
+        foreach (range(0x00, 0xFF) as $byte) {
+            $char = pack('C', $byte);
+            $encodedChar = $encoder->encodeString($char, 'iso-8859-1');
+            if (\in_array($byte, $allowedBytes)) {
+                $this->assertEquals($char, $encodedChar, 'Character '.$char.' should not be encoded.');
+            } elseif (0x20 == $byte) {
+                // special case
+                $this->assertEquals('_', $encodedChar, 'Space character should be replaced.');
+            } else {
+                $this->assertEquals(sprintf('=%02X', $byte), $encodedChar, 'Byte '.$byte.' should be encoded.');
+            }
+        }
+    }
+
+    public function testEqualsNeverAppearsAtEndOfLine()
+    {
+        /* -- RFC 2047, 5 (3).
+        The 'encoded-text' in an 'encoded-word' must be self-contained;
+        'encoded-text' MUST NOT be continued from one 'encoded-word' to
+        another.  This implies that the 'encoded-text' portion of a "B"
+        'encoded-word' will be a multiple of 4 characters long; for a "Q"
+        'encoded-word', any "=" character that appears in the 'encoded-text'
+        portion will be followed by two hexadecimal characters.
+        */
+
+        $input = str_repeat('a', 140);
+
+        $output = '';
+        $seq = 0;
+        for (; $seq < 140; ++$seq) {
+            // compute the end of line (multiple of 4 chars)
+            if (18 * 4 === $seq) {
+                $output .= "\r\n"; // =\r\n
+            }
+            $output .= 'a';
+        }
+
+        $encoder = new QpMimeHeaderEncoder();
+        $this->assertEquals($output, $encoder->encodeString($input));
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Encoder/Rfc2231EncoderTest.php b/vendor/symfony/mime/Tests/Encoder/Rfc2231EncoderTest.php
new file mode 100644
index 0000000..edcb15f
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Encoder/Rfc2231EncoderTest.php
@@ -0,0 +1,129 @@
+<?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\Mime\Tests\Encoder;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Encoder\Rfc2231Encoder;
+
+class Rfc2231EncoderTest extends TestCase
+{
+    private $rfc2045Token = '/^[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+$/D';
+
+    /* --
+    This algorithm is described in RFC 2231, but is barely touched upon except
+    for mentioning bytes can be represented as their octet values (e.g. %20 for
+    the SPACE character).
+
+    The tests here focus on how to use that representation to always generate text
+    which matches RFC 2045's definition of "token".
+    */
+
+    public function testEncodingAsciiCharactersProducesValidToken()
+    {
+        $string = '';
+        foreach (range(0x00, 0x7F) as $octet) {
+            $char = pack('C', $octet);
+            $string .= $char;
+        }
+        $encoder = new Rfc2231Encoder();
+        $encoded = $encoder->encodeString($string);
+
+        foreach (explode("\r\n", $encoded) as $line) {
+            $this->assertRegExp($this->rfc2045Token, $line, 'Encoder should always return a valid RFC 2045 token.');
+        }
+    }
+
+    public function testEncodingNonAsciiCharactersProducesValidToken()
+    {
+        $string = '';
+        foreach (range(0x80, 0xFF) as $octet) {
+            $char = pack('C', $octet);
+            $string .= $char;
+        }
+        $encoder = new Rfc2231Encoder();
+        $encoded = $encoder->encodeString($string);
+
+        foreach (explode("\r\n", $encoded) as $line) {
+            $this->assertRegExp($this->rfc2045Token, $line, 'Encoder should always return a valid RFC 2045 token.');
+        }
+    }
+
+    public function testMaximumLineLengthCanBeSet()
+    {
+        $string = '';
+        for ($x = 0; $x < 200; ++$x) {
+            $char = 'a';
+            $string .= $char;
+        }
+        $encoder = new Rfc2231Encoder();
+        $encoded = $encoder->encodeString($string, 'utf-8', 0, 75);
+
+        // 72 here and not 75 as we read 4 chars at a time
+        $this->assertEquals(
+            str_repeat('a', 72)."\r\n".
+            str_repeat('a', 72)."\r\n".
+            str_repeat('a', 56),
+            $encoded,
+            'Lines should be wrapped at each 72 characters'
+        );
+    }
+
+    public function testFirstLineCanHaveShorterLength()
+    {
+        $string = '';
+        for ($x = 0; $x < 200; ++$x) {
+            $char = 'a';
+            $string .= $char;
+        }
+        $encoder = new Rfc2231Encoder();
+        $encoded = $encoder->encodeString($string, 'utf-8', 24, 72);
+
+        $this->assertEquals(
+            str_repeat('a', 48)."\r\n".
+            str_repeat('a', 72)."\r\n".
+            str_repeat('a', 72)."\r\n".
+            str_repeat('a', 8),
+            $encoded,
+            'First line should be 24 bytes shorter than the others.'
+        );
+    }
+
+    public function testEncodingAndDecodingSamples()
+    {
+        $dir = realpath(__DIR__.'/../Fixtures/samples/charsets');
+        $sampleFp = opendir($dir);
+        while (false !== $encoding = readdir($sampleFp)) {
+            if ('.' == substr($encoding, 0, 1)) {
+                continue;
+            }
+
+            $encoder = new Rfc2231Encoder();
+            if (is_dir($dir.'/'.$encoding)) {
+                $fileFp = opendir($dir.'/'.$encoding);
+                while (false !== $sampleFile = readdir($fileFp)) {
+                    if ('.' == substr($sampleFile, 0, 1)) {
+                        continue;
+                    }
+
+                    $text = file_get_contents($dir.'/'.$encoding.'/'.$sampleFile);
+                    $encodedText = $encoder->encodeString($text, $encoding);
+                    $this->assertEquals(
+                        urldecode(implode('', explode("\r\n", $encodedText))), $text,
+                        'Encoded string should decode back to original string for sample '.$dir.'/'.$encoding.'/'.$sampleFile
+                    );
+                }
+                closedir($fileFp);
+            }
+        }
+        closedir($sampleFp);
+    }
+}
diff --git a/vendor/symfony/mime/Tests/FileBinaryMimeTypeGuesserTest.php b/vendor/symfony/mime/Tests/FileBinaryMimeTypeGuesserTest.php
new file mode 100644
index 0000000..0742732
--- /dev/null
+++ b/vendor/symfony/mime/Tests/FileBinaryMimeTypeGuesserTest.php
@@ -0,0 +1,23 @@
+<?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\Mime\Tests;
+
+use Symfony\Component\Mime\FileBinaryMimeTypeGuesser;
+use Symfony\Component\Mime\MimeTypeGuesserInterface;
+
+class FileBinaryMimeTypeGuesserTest extends AbstractMimeTypeGuesserTest
+{
+    protected function getGuesser(): MimeTypeGuesserInterface
+    {
+        return new FileBinaryMimeTypeGuesser();
+    }
+}
diff --git a/vendor/symfony/mime/Tests/FileinfoMimeTypeGuesserTest.php b/vendor/symfony/mime/Tests/FileinfoMimeTypeGuesserTest.php
new file mode 100644
index 0000000..d2e6930
--- /dev/null
+++ b/vendor/symfony/mime/Tests/FileinfoMimeTypeGuesserTest.php
@@ -0,0 +1,26 @@
+<?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\Mime\Tests;
+
+use Symfony\Component\Mime\FileinfoMimeTypeGuesser;
+use Symfony\Component\Mime\MimeTypeGuesserInterface;
+
+/**
+ * @requires extension fileinfo
+ */
+class FileinfoMimeTypeGuesserTest extends AbstractMimeTypeGuesserTest
+{
+    protected function getGuesser(): MimeTypeGuesserInterface
+    {
+        return new FileinfoMimeTypeGuesser();
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Fixtures/mimetypes/-test b/vendor/symfony/mime/Tests/Fixtures/mimetypes/-test
new file mode 100644
index 0000000..b636f4b
Binary files /dev/null and b/vendor/symfony/mime/Tests/Fixtures/mimetypes/-test differ
diff --git a/vendor/symfony/mime/Tests/Fixtures/mimetypes/.unknownextension b/vendor/symfony/mime/Tests/Fixtures/mimetypes/.unknownextension
new file mode 100644
index 0000000..4d1ae35
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Fixtures/mimetypes/.unknownextension
@@ -0,0 +1 @@
+f
\ No newline at end of file
diff --git a/vendor/symfony/mime/Tests/Fixtures/mimetypes/directory/.empty b/vendor/symfony/mime/Tests/Fixtures/mimetypes/directory/.empty
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Fixtures/mimetypes/directory/.empty
diff --git a/vendor/symfony/mime/Tests/Fixtures/mimetypes/other-file.example b/vendor/symfony/mime/Tests/Fixtures/mimetypes/other-file.example
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Fixtures/mimetypes/other-file.example
diff --git a/vendor/symfony/mime/Tests/Fixtures/mimetypes/test b/vendor/symfony/mime/Tests/Fixtures/mimetypes/test
new file mode 100644
index 0000000..b636f4b
Binary files /dev/null and b/vendor/symfony/mime/Tests/Fixtures/mimetypes/test differ
diff --git a/vendor/symfony/mime/Tests/Fixtures/mimetypes/test.gif b/vendor/symfony/mime/Tests/Fixtures/mimetypes/test.gif
new file mode 100644
index 0000000..b636f4b
Binary files /dev/null and b/vendor/symfony/mime/Tests/Fixtures/mimetypes/test.gif differ
diff --git a/vendor/symfony/mime/Tests/Fixtures/samples/charsets/iso-2022-jp/one.txt b/vendor/symfony/mime/Tests/Fixtures/samples/charsets/iso-2022-jp/one.txt
new file mode 100644
index 0000000..99ce65b
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Fixtures/samples/charsets/iso-2022-jp/one.txt
@@ -0,0 +1,11 @@
+ISO-2022-JPは、インターネット上(特に電子メール)などで使われる日本の文字用の文字符号化方式。ISO/IEC 2022のエスケープシーケンスを利用して文字集合を切り替える7ビットのコードであることを特徴とする (アナウンス機能のエスケープシーケンスは省略される)。俗に「JISコード」と呼ばれることもある。
+
+概要
+日本語表記への利用が想定されている文字コードであり、日本語の利用されるネットワークにおいて、日本の規格を応用したものである。また文字集合としては、日本語で用いられる漢字、ひらがな、カタカナはもちろん、ラテン文字、ギリシア文字、キリル文字なども含んでおり、学術や産業の分野での利用も考慮たものとなっている。規格名に、ISOの日本語の言語コードであるjaではなく、国・地域名コードのJPが示されているゆえんである。
+文字集合としてJIS X 0201のC0集合(制御文字)、JIS X 0201のラテン文字集合、ISO 646の国際基準版図形文字、JIS X 0208の1978年版(JIS C 6226-1978)と1983年および1990年版が利用できる。JIS X 0201の片仮名文字集合は利用できない。1986年以降、日本の電子メールで用いられてきたJUNETコードを、村井純・Mark Crispin・Erik van der Poelが1993年にRFC化したもの(RFC 1468)。後にJIS X 0208:1997の附属書2としてJISに規定された。MIMEにおける文字符号化方式の識別用の名前として IANA に登録されている。
+なお、符号化の仕様についてはISO/IEC 2022#ISO-2022-JPも参照。
+
+ISO-2022-JPと非標準的拡張使用
+「JISコード」(または「ISO-2022-JP」)というコード名の規定下では、その仕様通りの使用が求められる。しかし、Windows OS上では、実際にはCP932コード (MicrosoftによるShift JISを拡張した亜種。ISO-2022-JP規定外文字が追加されている。)による独自拡張(の文字)を断りなく使うアプリケーションが多い。この例としてInternet ExplorerやOutlook Expressがある。また、EmEditor、秀丸エディタやThunderbirdのようなMicrosoft社以外のWindowsアプリケーションでも同様の場合がある。この場合、ISO-2022-JPの範囲外の文字を使ってしまうと、異なる製品間では未定義不明文字として認識されるか、もしくは文字化けを起こす原因となる。そのため、Windows用の電子メールクライアントであっても独自拡張の文字を使用すると警告を出したり、あえて使えないように制限しているものも存在する。さらにはISO-2022-JPの範囲内であってもCP932は非標準文字(FULLWIDTH TILDE等)を持つので文字化けの原因になり得る。
+また、符号化方式名をISO-2022-JPとしているのに、文字集合としてはJIS X 0212 (いわゆる補助漢字) やJIS X 0201の片仮名文字集合 (いわゆる半角カナ) をも符号化している例があるが、ISO-2022-JPではこれらの文字を許容していない。これらの符号化は独自拡張の実装であり、中にはISO/IEC 2022の仕様に準拠すらしていないものもある[2]。従って受信側の電子メールクライアントがこれらの独自拡張に対応していない場合、その文字あるいはその文字を含む行、時にはテキスト全体が文字化けすることがある。
+
diff --git a/vendor/symfony/mime/Tests/Fixtures/samples/charsets/iso-8859-1/one.txt b/vendor/symfony/mime/Tests/Fixtures/samples/charsets/iso-8859-1/one.txt
new file mode 100644
index 0000000..3101178
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Fixtures/samples/charsets/iso-8859-1/one.txt
@@ -0,0 +1,19 @@
+Op mat eraus hinnen beschte, rou zënne schaddreg ké. Ké sin Eisen Kaffi prächteg, den haut esou Fielse wa, Well zielen d'Welt am dir. Aus grousse rëschten d'Stroos do, as dat Kléder gewëss d'Kàchen. Schied gehéiert d'Vioule net hu, rou ke zënter Säiten d'Hierz. Ze eise Fletschen mat, gei as gréng d'Lëtzebuerger. Wäit räich no mat.
+
+Säiten d'Liewen aus en. Un gëtt bléit lossen wee, da wéi alle weisen Kolrettchen. Et deser d'Pan d'Kirmes vun, en wuel Benn rëschten méi. En get drem ménger beschte, da wär Stad welle. Nun Dach d'Pied do, mä gét ruffen gehéiert. Ze onser ugedon fir, d'Liewen Plett'len ech no, si Räis wielen bereet wat. Iwer spilt fir jo.
+
+An hin däischter Margréitchen, eng ke Frot brommt, vu den Räis néierens. Da hir Hunn Frot nozegon, rout Fläiß Himmel zum si, net gutt Kaffi Gesträich fu. Vill lait Gaart sou wa, Land Mamm Schuebersonndeg rei do. Gei geet Minutt en, gei d'Leit beschte Kolrettchen et, Mamm fergiess un hun.
+
+Et gutt Heck kommen oft, Lann rëscht rei um, Hunn rëscht schéinste ke der. En lait zielen schnéiwäiss hir, fu rou botze éiweg Minutt, rem fest gudden schaddreg en. Noper bereet Margréitchen mat op, dem denkt d'Leit d'Vioule no, oft ké Himmel Hämmel. En denkt blénken Fréijor net, Gart Schiet d'Natur no wou. No hin Ierd Frot d'Kirmes. Hire aremt un rou, ké den éiweg wielen Milliounen.
+
+Mir si Hunn Blénkeg. Ké get ston derfir d'Kàchen. Haut d'Pan fu ons, dé frou löschteg d'Meereische rei. Sou op wuel Léift. Stret schlon grousse gin hu. Mä denkt d'Leit hinnen net, ké gét haut fort rëscht.
+
+Koum d'Pan hannendrun ass ké, ké den brét Kaffi geplot. Schéi Hären d'Pied fu gét, do d'Mier néierens bei. Rëm päift Hämmel am, wee Engel beschéngt mä. Brommt klinzecht der ke, wa rout jeitzt dén. Get Zalot d'Vioule däischter da, jo fir Bänk päift duerch, bei d'Beem schéinen Plett'len jo. Den haut Faarwen ze, eng en Biereg Kirmesdag, um sin alles Faarwen d'Vioule.
+
+Eng Hunn Schied et, wat wa Frot fest gebotzt. Bei jo bleiwe ruffen Klarinett. Un Feld klinzecht gét, rifft Margréitchen rem ke. Mir dé Noper duurch gewëss, ston sech kille sin en. Gei Stret d'Wise um, Haus Gart wee as. Monn ménger an blo, wat da Gart gefällt Hämmelsbrot.
+
+Brommt geplot och ze, dat wa Räis Well Kaffi. Do get spilt prächteg, as wär kille bleiwe gewalteg. Onser frësch Margréitchen rem ke, blo en huet ugedon. Onser Hemecht wär de, hu eraus d'Sonn dat, eise deser hannendrun da och.
+
+As durch Himmel hun, no fest iw'rem schéinste mir, Hunn séngt Hierz ke zum. Séngt iw'rem d'Natur zum an. Ke wär gutt Grénge. Kënnt gudden prächteg mä rei. Dé dir Blénkeg Klarinett Kolrettchen, da fort muerges d'Kanner wou, main Feld ruffen vu wéi. Da gin esou Zalot gewalteg, gét vill Hemecht blénken dé.
+
+Haut gréng nun et, nei vu Bass gréng d'Gaassen. Fest d'Beem uechter si gin. Oft vu sinn wellen kréien. Et ass lait Zalot schéinen.
\ No newline at end of file
diff --git a/vendor/symfony/mime/Tests/Fixtures/samples/charsets/utf-8/one.txt b/vendor/symfony/mime/Tests/Fixtures/samples/charsets/utf-8/one.txt
new file mode 100644
index 0000000..26c94d5
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Fixtures/samples/charsets/utf-8/one.txt
@@ -0,0 +1,22 @@
+Код одно гринспана руководишь на. Его вы знания движение. Ты две начать
+одиночку, сказать основатель удовольствием но миф. Бы какие система тем.
+Полностью использует три мы, человек клоунов те нас, бы давать творческую
+эзотерическая шеф.
+
+Мог не помнить никакого сэкономленного, две либо какие пишите бы. Должен
+компанию кто те, этот заключалась проектировщик не ты. Глупые периоды ты
+для. Вам который хороший он. Те любых кремния концентрируются мог,
+собирать принадлежите без вы.
+
+Джоэла меньше хорошего вы миф, за тем году разработки. Даже управляющим
+руководители был не. Три коде выпускать заботиться ну. То его система
+удовольствием безостановочно, или ты главной процессорах. Мы без джоэл
+знания получат, статьи остальные мы ещё.
+
+Них русском касается поскольку по, образование должником
+систематизированный ну мои. Прийти кандидата университет но нас, для бы
+должны никакого, биг многие причин интервьюирования за.
+
+Тем до плиту почему. Вот учёт такие одного бы, об биг разным внешних
+промежуток. Вас до какому возможностей безответственный, были погодите бы
+его, по них глупые долгий количества.
diff --git a/vendor/symfony/mime/Tests/Fixtures/samples/charsets/utf-8/three.txt b/vendor/symfony/mime/Tests/Fixtures/samples/charsets/utf-8/three.txt
new file mode 100644
index 0000000..c81ccd5
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Fixtures/samples/charsets/utf-8/three.txt
@@ -0,0 +1,45 @@
+Αν ήδη διάβασε γλιτώσει μεταγλωτίσει, αυτήν θυμάμαι μου μα. Την κατάσταση χρησιμοποίησέ να! Τα διαφορά φαινόμενο διολισθήσεις πες, υψηλότερη προκαλείς περισσότερες όχι κι. Με ελέγχου γίνεται σας, μικρής δημιουργούν τη του. Τις τα γράψει εικόνες απαράδεκτη?
+
+Να ότι πρώτοι απαραίτητο. Άμεση πετάνε κακόκεφος τον ώς, να χώρου πιθανότητες του. Το μέχρι ορίστε λιγότερους σας. Πω ναί φυσικά εικόνες.
+
+Μου οι κώδικα αποκλειστικούς, λες το μάλλον συνεχώς. Νέου σημεία απίστευτα σας μα. Χρόνου μεταγλωτιστής σε νέα, τη τις πιάνει μπορούσες προγραμματιστές. Των κάνε βγαίνει εντυπωσιακό τα? Κρατάει τεσσαρών δυστυχώς της κι, ήδη υψηλότερη εξακολουθεί τα?
+
+Ώρα πετάνε μπορούσε λιγότερους αν, τα απαράδεκτη συγχωνευτεί ροή. Τη έγραψες συνηθίζουν σαν. Όλα με υλικό στήλες χειρότερα. Ανώδυνη δουλέψει επί ως, αν διαδίκτυο εσωτερικών παράγοντες από. Κεντρικό επιτυχία πες το.
+
+Πω ναι λέει τελειώσει, έξι ως έργων τελειώσει. Με αρχεία βουτήξουν ανταγωνιστής ώρα, πολύ γραφικά σελίδων τα στη. Όρο οέλεγχος δημιουργούν δε, ας θέλεις ελέγχου συντακτικό όρο! Της θυμάμαι επιδιόρθωση τα. Για μπορούσε περισσότερο αν, μέγιστη σημαίνει αποφάσισε τα του, άτομο αποτελέσει τι στα.
+
+Τι στην αφήσεις διοίκηση στη. Τα εσφαλμένη δημιουργια επιχείριση έξι! Βήμα μαγικά εκτελέσει ανά τη. Όλη αφήσεις συνεχώς εμπορικά αν, το λες κόλπα επιτυχία. Ότι οι ζώνη κειμένων. Όρο κι ρωτάει γραμμής πελάτες, τελειώσει διολισθήσεις καθυστερούσε αν εγώ? Τι πετούν διοίκηση προβλήματα ήδη.
+
+Τη γλιτώσει αποθηκευτικού μια. Πω έξι δημιουργια πιθανότητες, ως πέντε ελέγχους εκτελείται λες. Πως ερωτήσεις διοικητικό συγκεντρωμένοι οι, ας συνεχώς διοικητικό αποστηθίσει σαν. Δε πρώτες συνεχώς διολισθήσεις έχω, από τι κανένας βουτήξουν, γειτονιάς προσεκτικά ανταγωνιστής κι σαν.
+
+Δημιουργια συνηθίζουν κλπ τι? Όχι ποσοστό διακοπής κι. Κλπ φακέλους δεδομένη εξοργιστικά θα? Υποψήφιο καθορίζουν με όλη, στα πήρε προσοχή εταιρείες πω, ώς τον συνάδελφος διοικητικό δημιουργήσεις! Δούλευε επιτίθενται σας θα, με ένας παραγωγικής ένα, να ναι σημεία μέγιστη απαράδεκτη?
+
+Σας τεσσαρών συνεντεύξης τη, αρπάζεις σίγουρος μη για', επί τοπικές εντολές ακούσει θα? Ως δυστυχής μεταγλωτιστής όλη, να την είχαν σφάλμα απαραίτητο! Μην ώς άτομο διορθώσει χρησιμοποιούνταν. Δεν τα κόλπα πετάξαμε, μη που άγχος υόρκη άμεση, αφού δυστυχώς διακόψουμε όρο αν! Όλη μαγικά πετάνε επιδιορθώσεις δε, ροή φυσικά αποτελέσει πω.
+
+Άπειρα παραπάνω φαινόμενο πω ώρα, σαν πόρτες κρατήσουν συνηθίζουν ως. Κι ώρα τρέξει είχαμε εφαρμογή. Απλό σχεδιαστής μεταγλωτιστής ας επί, τις τα όταν έγραψες γραμμής? Όλα κάνεις συνάδελφος εργαζόμενοι θα, χαρτιού χαμηλός τα ροή. Ως ναι όροφο έρθει, μην πελάτες αποφάσισε μεταφραστής με, να βιαστικά εκδόσεις αναζήτησης λες. Των φταίει εκθέσεις προσπαθήσεις οι, σπίτι αποστηθίσει ας λες?
+
+Ώς που υπηρεσία απαραίτητο δημιουργείς. Μη άρα χαρά καθώς νύχτας, πω ματ μπουν είχαν. Άμεση δημιουργείς ώς ροή, γράψει γραμμής σίγουρος στα τι! Αν αφού πρώτοι εργαζόμενων ναί.
+
+Άμεση διορθώσεις με δύο? Έχουν παράδειγμα των θα, μου έρθει θυμάμαι περισσότερο το. Ότι θα αφού χρειάζονται περισσότερες. Σαν συνεχώς περίπου οι.
+
+Ώς πρώτης πετάξαμε λες, όρο κι πρώτες ζητήσεις δυστυχής. Ανά χρόνου διακοπή επιχειρηματίες ας, ώς μόλις άτομο χειρότερα όρο, κρατάει σχεδιαστής προσπαθήσεις νέο το. Πουλάς προσθέσει όλη πω, τύπου χαρακτηριστικό εγώ σε, πω πιο δούλευε αναζήτησης? Αναφορά δίνοντας σαν μη, μάθε δεδομένη εσωτερικών με ναι, αναφέρονται περιβάλλοντος ώρα αν. Και λέει απόλαυσε τα, που το όροφο προσπαθούν?
+
+Πάντα χρόνου χρήματα ναι το, σαν σωστά θυμάμαι σκεφτείς τα. Μα αποτελέσει ανεπιθύμητη την, πιο το τέτοιο ατόμου, τη των τρόπο εργαλείων επιδιόρθωσης. Περιβάλλον παραγωγικής σου κι, κλπ οι τύπου κακόκεφους αποστηθίσει, δε των πλέον τρόποι. Πιθανότητες χαρακτηριστικών σας κι, γραφικά δημιουργήσεις μια οι, πω πολλοί εξαρτάται προσεκτικά εδώ. Σταματάς παράγοντες για' ώς, στις ρωτάει το ναι! Καρέκλα ζητήσεις συνδυασμούς τη ήδη!
+
+Για μαγικά συνεχώς ακούσει το. Σταματάς προϊόντα βουτήξουν ώς ροή. Είχαν πρώτες οι ναι, μα λες αποστηθίσει ανακαλύπτεις. Όροφο άλγεβρα παραπάνω εδώ τη, πρόσληψη λαμβάνουν καταλάθος ήδη ας? Ως και εισαγωγή κρατήσουν, ένας κακόκεφους κι μας, όχι κώδικάς παίξουν πω. Πω νέα κρατάει εκφράσουν, τότε τελικών τη όχι, ας της τρέξει αλλάζοντας αποκλειστικούς.
+
+Ένας βιβλίο σε άρα, ναι ως γράψει ταξινομεί διορθώσεις! Εδώ να γεγονός συγγραφείς, ώς ήδη διακόψουμε επιχειρηματίες? Ότι πακέτων εσφαλμένη κι, θα όρο κόλπα παραγωγικής? Αν έχω κεντρικό υψηλότερη, κι δεν ίδιο πετάνε παρατηρούμενη! Που λοιπόν σημαντικό μα, προκαλείς χειροκροτήματα ως όλα, μα επί κόλπα άγχος γραμμές! Δε σου κάνεις βουτήξουν, μη έργων επενδυτής χρησιμοποίησέ στα, ως του πρώτες διάσημα σημαντικό.
+
+Βιβλίο τεράστιο προκύπτουν σαν το, σαν τρόπο επιδιόρθωση ας. Είχαν προσοχή προσπάθεια κι ματ, εδώ ως έτσι σελίδων συζήτηση. Και στην βγαίνει εσφαλμένη με, δυστυχής παράδειγμα δε μας, από σε υόρκη επιδιόρθωσης. Νέα πω νέου πιθανό, στήλες συγγραφείς μπαίνοντας μα για', το ρωτήσει κακόκεφους της? Μου σε αρέσει συγγραφής συγχωνευτεί, μη μου υόρκη ξέχασε διακοπής! Ώς επί αποφάσισε αποκλειστικούς χρησιμοποιώντας, χρήματα σελίδων ταξινομεί ναι με.
+
+Μη ανά γραμμή απόλαυσε, πω ναι μάτσο διασφαλίζεται. Τη έξι μόλις εργάστηκε δημιουργούν, έκδοση αναφορά δυσκολότερο οι νέο. Σας ως μπορούσε παράδειγμα, αν ότι δούλευε μπορούσε αποκλειστικούς, πιο λέει βουτήξουν διορθώσει ως. Έχω τελευταία κακόκεφους ας, όσο εργαζόμενων δημιουργήσεις τα.
+
+Του αν δουλέψει μπορούσε, πετούν χαμηλός εδώ ας? Κύκλο τύπους με που, δεν σε έχουν συνεχώς χειρότερα, τις τι απαράδεκτη συνηθίζουν? Θα μην τους αυτήν, τη ένα πήρε πακέτων, κι προκύπτουν περιβάλλον πως. Μα για δουλέψει απόλαυσε εφαμοργής, ώς εδώ σημαίνει μπορούσες, άμεση ακούσει προσοχή τη εδώ?
+
+Στα δώσε αθόρυβες λιγότερους οι, δε αναγκάζονται αποκλειστικούς όλα! Ας μπουν διοικητικό μια, πάντα ελέγχου διορθώσεις ώς τον. Ότι πήρε κανόνα μα. Που άτομα κάνεις δημιουργίες τα, οι μας αφού κόλπα προγραμματιστής, αφού ωραίο προκύπτουν στα ως. Θέμα χρησιμοποιήσει αν όλα, του τα άλγεβρα σελίδων. Τα ότι ανώδυνη δυστυχώς συνδυασμούς, μας οι πάντα γνωρίζουμε ανταγωνιστής, όχι τα δοκιμάσεις σχεδιαστής! Στην συνεντεύξης επιδιόρθωση πιο τα, μα από πουλάς περιβάλλον παραγωγικής.
+
+Έχουν μεταγλωτίσει σε σας, σε πάντα πρώτης μειώσει των, γράψει ρουτίνα δυσκολότερο ήδη μα? Ταξινομεί διορθώσεις να μας. Θα της προσπαθούν περιεχόμενα, δε έχω τοπικές στέλνοντάς. Ανά δε αλφα άμεση, κάποιο ρωτάει γνωρίζουμε πω στη, φράση μαγικά συνέχεια δε δύο! Αν είχαμε μειώσει ροή, μας μετράει καθυστερούσε επιδιορθώσεις μη. Χάος υόρκη κεντρικό έχω σε, ανά περίπου αναγκάζονται πω.
+
+Όσο επιστρέφουν χρονοδιαγράμματα μη. Πως ωραίο κακόκεφος διαχειριστής ως, τις να διακοπής αναζήτησης. Κάποιο ποσοστό ταξινομεί επί τη? Μάθε άμεση αλλάζοντας δύο με, μου νέου πάντα να.
+
+Πω του δυστυχώς πιθανότητες. Κι ρωτάει υψηλότερη δημιουργια ότι, πω εισαγωγή τελευταία απομόνωση ναι. Των ζητήσεις γνωρίζουμε ώς? Για' μη παραδοτέου αναφέρονται! Ύψος παραγωγικά ροή ως, φυσικά διάβασε εικόνες όσο σε? Δεν υόρκη διορθώσεις επεξεργασία θα, ως μέση σύστημα χρησιμοποιήσει τις.
\ No newline at end of file
diff --git a/vendor/symfony/mime/Tests/Fixtures/samples/charsets/utf-8/two.txt b/vendor/symfony/mime/Tests/Fixtures/samples/charsets/utf-8/two.txt
new file mode 100644
index 0000000..2443fc4
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Fixtures/samples/charsets/utf-8/two.txt
@@ -0,0 +1,3 @@
+रखति आवश्यकत प्रेरना मुख्यतह हिंदी किएलोग असक्षम कार्यलय करते विवरण किके मानसिक दिनांक पुर्व संसाध एवम् कुशलता अमितकुमार प्रोत्साहित जनित देखने उदेशीत विकसित बलवान ब्रौशर किएलोग विश्लेषण लोगो कैसे जागरुक प्रव्रुति प्रोत्साहित सदस्य आवश्यकत प्रसारन उपलब्धता अथवा हिंदी जनित दर्शाता यन्त्रालय बलवान अतित सहयोग शुरुआत सभीकुछ माहितीवानीज्य लिये खरिदे है।अभी एकत्रित सम्पर्क रिती मुश्किल प्राथमिक भेदनक्षमता विश्व उन्हे गटको द्वारा तकरीबन
+
+विश्व द्वारा व्याख्या सके। आजपर वातावरण व्याख्यान पहोच। हमारी कीसे प्राथमिक विचारशिलता पुर्व करती कम्प्युटर भेदनक्षमता लिये बलवान और्४५० यायेका वार्तालाप सुचना भारत शुरुआत लाभान्वित पढाए संस्था वर्णित मार्गदर्शन चुनने
\ No newline at end of file
diff --git a/vendor/symfony/mime/Tests/Header/DateHeaderTest.php b/vendor/symfony/mime/Tests/Header/DateHeaderTest.php
new file mode 100644
index 0000000..4fc92b9
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Header/DateHeaderTest.php
@@ -0,0 +1,80 @@
+<?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\Mime\Tests\Header;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Header\DateHeader;
+
+class DateHeaderTest extends TestCase
+{
+    /* --
+    The following tests refer to RFC 2822, section 3.6.1 and 3.3.
+    */
+
+    public function testGetDateTime()
+    {
+        $header = new DateHeader('Date', $dateTime = new \DateTimeImmutable());
+        $this->assertSame($dateTime, $header->getDateTime());
+    }
+
+    public function testDateTimeCanBeSetBySetter()
+    {
+        $header = new DateHeader('Date', new \DateTimeImmutable());
+        $header->setDateTime($dateTime = new \DateTimeImmutable());
+        $this->assertSame($dateTime, $header->getDateTime());
+    }
+
+    public function testDateTimeIsConvertedToImmutable()
+    {
+        $dateTime = new \DateTime();
+        $header = new DateHeader('Date', $dateTime);
+        $this->assertInstanceOf('DateTimeImmutable', $header->getDateTime());
+        $this->assertEquals($dateTime->getTimestamp(), $header->getDateTime()->getTimestamp());
+        $this->assertEquals($dateTime->getTimezone(), $header->getDateTime()->getTimezone());
+    }
+
+    public function testDateTimeIsImmutable()
+    {
+        $header = new DateHeader('Date', $dateTime = new \DateTime('2000-01-01 12:00:00 Europe/Berlin'));
+        $dateTime->setDate(2002, 2, 2);
+        $this->assertEquals('Sat, 01 Jan 2000 12:00:00 +0100', $header->getDateTime()->format('r'));
+        $this->assertEquals('Sat, 01 Jan 2000 12:00:00 +0100', $header->getBodyAsString());
+    }
+
+    public function testDateTimeIsConvertedToRfc2822Date()
+    {
+        $header = new DateHeader('Date', $dateTime = new \DateTimeImmutable('2000-01-01 12:00:00 Europe/Berlin'));
+        $header->setDateTime($dateTime);
+        $this->assertEquals('Sat, 01 Jan 2000 12:00:00 +0100', $header->getBodyAsString());
+    }
+
+    public function testSetBody()
+    {
+        $header = new DateHeader('Date', $dateTime = new \DateTimeImmutable());
+        $header->setBody($dateTime);
+        $this->assertEquals($dateTime->format('r'), $header->getBodyAsString());
+    }
+
+    public function testGetBody()
+    {
+        $header = new DateHeader('Date', $dateTime = new \DateTimeImmutable());
+        $header->setDateTime($dateTime);
+        $this->assertEquals($dateTime, $header->getBody());
+    }
+
+    public function testToString()
+    {
+        $header = new DateHeader('Date', $dateTime = new \DateTimeImmutable('2000-01-01 12:00:00 Europe/Berlin'));
+        $header->setDateTime($dateTime);
+        $this->assertEquals('Date: Sat, 01 Jan 2000 12:00:00 +0100', $header->toString());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Header/HeadersTest.php b/vendor/symfony/mime/Tests/Header/HeadersTest.php
new file mode 100644
index 0000000..e2eb75a
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Header/HeadersTest.php
@@ -0,0 +1,241 @@
+<?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\Mime\Tests\Header;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\Header\IdentificationHeader;
+use Symfony\Component\Mime\Header\MailboxListHeader;
+use Symfony\Component\Mime\Header\UnstructuredHeader;
+
+class HeadersTest extends TestCase
+{
+    public function testAddMailboxListHeaderDelegatesToFactory()
+    {
+        $headers = new Headers();
+        $headers->addMailboxListHeader('From', ['person@domain']);
+        $this->assertNotNull($headers->get('From'));
+    }
+
+    public function testAddDateHeaderDelegatesToFactory()
+    {
+        $dateTime = new \DateTimeImmutable();
+        $headers = new Headers();
+        $headers->addDateHeader('Date', $dateTime);
+        $this->assertNotNull($headers->get('Date'));
+    }
+
+    public function testAddTextHeaderDelegatesToFactory()
+    {
+        $headers = new Headers();
+        $headers->addTextHeader('Subject', 'some text');
+        $this->assertNotNull($headers->get('Subject'));
+    }
+
+    public function testAddParameterizedHeaderDelegatesToFactory()
+    {
+        $headers = new Headers();
+        $headers->addParameterizedHeader('Content-Type', 'text/plain', ['charset' => 'utf-8']);
+        $this->assertNotNull($headers->get('Content-Type'));
+    }
+
+    public function testAddIdHeaderDelegatesToFactory()
+    {
+        $headers = new Headers();
+        $headers->addIdHeader('Message-ID', 'some@id');
+        $this->assertNotNull($headers->get('Message-ID'));
+    }
+
+    public function testAddPathHeaderDelegatesToFactory()
+    {
+        $headers = new Headers();
+        $headers->addPathHeader('Return-Path', 'some@path');
+        $this->assertNotNull($headers->get('Return-Path'));
+    }
+
+    public function testHasReturnsFalseWhenNoHeaders()
+    {
+        $headers = new Headers();
+        $this->assertFalse($headers->has('Some-Header'));
+    }
+
+    public function testAddedMailboxListHeaderIsSeenByHas()
+    {
+        $headers = new Headers();
+        $headers->addMailboxListHeader('From', ['person@domain']);
+        $this->assertTrue($headers->has('From'));
+    }
+
+    public function testAddedDateHeaderIsSeenByHas()
+    {
+        $dateTime = new \DateTimeImmutable();
+        $headers = new Headers();
+        $headers->addDateHeader('Date', $dateTime);
+        $this->assertTrue($headers->has('Date'));
+    }
+
+    public function testAddedTextHeaderIsSeenByHas()
+    {
+        $headers = new Headers();
+        $headers->addTextHeader('Subject', 'some text');
+        $this->assertTrue($headers->has('Subject'));
+    }
+
+    public function testAddedParameterizedHeaderIsSeenByHas()
+    {
+        $headers = new Headers();
+        $headers->addParameterizedHeader('Content-Type', 'text/plain', ['charset' => 'utf-8']);
+        $this->assertTrue($headers->has('Content-Type'));
+    }
+
+    public function testAddedIdHeaderIsSeenByHas()
+    {
+        $headers = new Headers();
+        $headers->addIdHeader('Message-ID', 'some@id');
+        $this->assertTrue($headers->has('Message-ID'));
+    }
+
+    public function testAddedPathHeaderIsSeenByHas()
+    {
+        $headers = new Headers();
+        $headers->addPathHeader('Return-Path', 'some@path');
+        $this->assertTrue($headers->has('Return-Path'));
+    }
+
+    public function testNewlySetHeaderIsSeenByHas()
+    {
+        $headers = new Headers();
+        $headers->add(new UnstructuredHeader('X-Foo', 'bar'));
+        $this->assertTrue($headers->has('X-Foo'));
+    }
+
+    public function testHasCanDistinguishMultipleHeaders()
+    {
+        $headers = new Headers();
+        $headers->addTextHeader('X-Test', 'some@id');
+        $headers->addTextHeader('X-Test', 'other@id');
+        $this->assertTrue($headers->has('X-Test'));
+    }
+
+    public function testGet()
+    {
+        $header = new IdentificationHeader('Message-ID', 'some@id');
+        $headers = new Headers();
+        $headers->addIdHeader('Message-ID', 'some@id');
+        $this->assertEquals($header->toString(), $headers->get('Message-ID')->toString());
+    }
+
+    public function testGetReturnsNullIfHeaderNotSet()
+    {
+        $headers = new Headers();
+        $this->assertNull($headers->get('Message-ID'));
+    }
+
+    public function testAllReturnsAllHeadersMatchingName()
+    {
+        $header0 = new UnstructuredHeader('X-Test', 'some@id');
+        $header1 = new UnstructuredHeader('X-Test', 'other@id');
+        $header2 = new UnstructuredHeader('X-Test', 'more@id');
+        $headers = new Headers();
+        $headers->addTextHeader('X-Test', 'some@id');
+        $headers->addTextHeader('X-Test', 'other@id');
+        $headers->addTextHeader('X-Test', 'more@id');
+        $this->assertEquals([$header0, $header1, $header2], iterator_to_array($headers->all('X-Test')));
+    }
+
+    public function testAllReturnsAllHeadersIfNoArguments()
+    {
+        $header0 = new IdentificationHeader('Message-ID', 'some@id');
+        $header1 = new UnstructuredHeader('Subject', 'thing');
+        $header2 = new MailboxListHeader('To', [new Address('person@example.org')]);
+        $headers = new Headers();
+        $headers->addIdHeader('Message-ID', 'some@id');
+        $headers->addTextHeader('Subject', 'thing');
+        $headers->addMailboxListHeader('To', [new Address('person@example.org')]);
+        $this->assertEquals(['message-id' => $header0, 'subject' => $header1, 'to' => $header2], iterator_to_array($headers->all()));
+    }
+
+    public function testAllReturnsEmptyArrayIfNoneSet()
+    {
+        $headers = new Headers();
+        $this->assertEquals([], iterator_to_array($headers->all('Received')));
+    }
+
+    public function testRemoveRemovesAllHeadersWithName()
+    {
+        $headers = new Headers();
+        $headers->addIdHeader('X-Test', 'some@id');
+        $headers->addIdHeader('X-Test', 'other@id');
+        $headers->remove('X-Test');
+        $this->assertFalse($headers->has('X-Test'));
+        $this->assertFalse($headers->has('X-Test'));
+    }
+
+    public function testHasIsNotCaseSensitive()
+    {
+        $headers = new Headers();
+        $headers->addIdHeader('Message-ID', 'some@id');
+        $this->assertTrue($headers->has('message-id'));
+    }
+
+    public function testGetIsNotCaseSensitive()
+    {
+        $header = new IdentificationHeader('Message-ID', 'some@id');
+        $headers = new Headers();
+        $headers->addIdHeader('Message-ID', 'some@id');
+        $this->assertEquals($header, $headers->get('message-id'));
+    }
+
+    public function testAllIsNotCaseSensitive()
+    {
+        $header = new IdentificationHeader('Message-ID', 'some@id');
+        $headers = new Headers();
+        $headers->addIdHeader('Message-ID', 'some@id');
+        $this->assertEquals([$header], iterator_to_array($headers->all('message-id')));
+    }
+
+    public function testRemoveIsNotCaseSensitive()
+    {
+        $headers = new Headers();
+        $headers->addIdHeader('Message-ID', 'some@id');
+        $headers->remove('message-id');
+        $this->assertFalse($headers->has('Message-ID'));
+    }
+
+    public function testToStringJoinsHeadersTogether()
+    {
+        $headers = new Headers();
+        $headers->addTextHeader('Foo', 'bar');
+        $headers->addTextHeader('Zip', 'buttons');
+        $this->assertEquals("Foo: bar\r\nZip: buttons\r\n", $headers->toString());
+    }
+
+    public function testHeadersWithoutBodiesAreNotDisplayed()
+    {
+        $headers = new Headers();
+        $headers->addTextHeader('Foo', 'bar');
+        $headers->addTextHeader('Zip', '');
+        $this->assertEquals("Foo: bar\r\n", $headers->toString());
+    }
+
+    public function testToArray()
+    {
+        $headers = new Headers();
+        $headers->addIdHeader('Message-ID', 'some@id');
+        $headers->addTextHeader('Foo', str_repeat('a', 60).pack('C', 0x8F));
+        $this->assertEquals([
+            'Message-ID: <some@id>',
+            "Foo: =?utf-8?Q?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa?=\r\n =?utf-8?Q?aaaa?=",
+        ], $headers->toArray());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Header/IdentificationHeaderTest.php b/vendor/symfony/mime/Tests/Header/IdentificationHeaderTest.php
new file mode 100644
index 0000000..7d274ab
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Header/IdentificationHeaderTest.php
@@ -0,0 +1,173 @@
+<?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\Mime\Tests\Header;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Header\IdentificationHeader;
+
+class IdentificationHeaderTest extends TestCase
+{
+    public function testValueMatchesMsgIdSpec()
+    {
+        /* -- RFC 2822, 3.6.4.
+         message-id      =       "Message-ID:" msg-id CRLF
+
+         in-reply-to     =       "In-Reply-To:" 1*msg-id CRLF
+
+         references      =       "References:" 1*msg-id CRLF
+
+         msg-id          =       [CFWS] "<" id-left "@" id-right ">" [CFWS]
+
+         id-left         =       dot-atom-text / no-fold-quote / obs-id-left
+
+         id-right        =       dot-atom-text / no-fold-literal / obs-id-right
+
+         no-fold-quote   =       DQUOTE *(qtext / quoted-pair) DQUOTE
+
+         no-fold-literal =       "[" *(dtext / quoted-pair) "]"
+        */
+
+        $header = new IdentificationHeader('Message-ID', 'id-left@id-right');
+        $this->assertEquals('<id-left@id-right>', $header->getBodyAsString());
+    }
+
+    public function testIdCanBeRetrievedVerbatim()
+    {
+        $header = new IdentificationHeader('Message-ID', 'id-left@id-right');
+        $this->assertEquals('id-left@id-right', $header->getId());
+    }
+
+    public function testMultipleIdsCanBeSet()
+    {
+        $header = new IdentificationHeader('References', 'c@d');
+        $header->setIds(['a@b', 'x@y']);
+        $this->assertEquals(['a@b', 'x@y'], $header->getIds());
+    }
+
+    public function testSettingMultipleIdsProducesAListValue()
+    {
+        /* -- RFC 2822, 3.6.4.
+        The "References:" and "In-Reply-To:" field each contain one or more
+        unique message identifiers, optionally separated by CFWS.
+
+         .. SNIP ..
+
+         in-reply-to     =       "In-Reply-To:" 1*msg-id CRLF
+
+         references      =       "References:" 1*msg-id CRLF
+         */
+
+        $header = new IdentificationHeader('References', ['a@b', 'x@y']);
+        $this->assertEquals('<a@b> <x@y>', $header->getBodyAsString());
+    }
+
+    public function testIdLeftCanBeQuoted()
+    {
+        /* -- RFC 2822, 3.6.4.
+         id-left         =       dot-atom-text / no-fold-quote / obs-id-left
+         */
+
+        $header = new IdentificationHeader('References', '"ab"@c');
+        $this->assertEquals('"ab"@c', $header->getId());
+        $this->assertEquals('<"ab"@c>', $header->getBodyAsString());
+    }
+
+    public function testIdLeftCanContainAnglesAsQuotedPairs()
+    {
+        /* -- RFC 2822, 3.6.4.
+         no-fold-quote   =       DQUOTE *(qtext / quoted-pair) DQUOTE
+         */
+
+        $header = new IdentificationHeader('References', '"a\\<\\>b"@c');
+        $this->assertEquals('"a\\<\\>b"@c', $header->getId());
+        $this->assertEquals('<"a\\<\\>b"@c>', $header->getBodyAsString());
+    }
+
+    public function testIdLeftCanBeDotAtom()
+    {
+        $header = new IdentificationHeader('References', 'a.b+&%$.c@d');
+        $this->assertEquals('a.b+&%$.c@d', $header->getId());
+        $this->assertEquals('<a.b+&%$.c@d>', $header->getBodyAsString());
+    }
+
+    public function testInvalidIdLeftThrowsException()
+    {
+        $this->expectException('Exception');
+        $this->expectExceptionMessage('Email "a b c@d" does not comply with addr-spec of RFC 2822.');
+        new IdentificationHeader('References', 'a b c@d');
+    }
+
+    public function testIdRightCanBeDotAtom()
+    {
+        /* -- RFC 2822, 3.6.4.
+         id-right        =       dot-atom-text / no-fold-literal / obs-id-right
+         */
+
+        $header = new IdentificationHeader('References', 'a@b.c+&%$.d');
+        $this->assertEquals('a@b.c+&%$.d', $header->getId());
+        $this->assertEquals('<a@b.c+&%$.d>', $header->getBodyAsString());
+    }
+
+    public function testIdRightCanBeLiteral()
+    {
+        /* -- RFC 2822, 3.6.4.
+         no-fold-literal =       "[" *(dtext / quoted-pair) "]"
+        */
+
+        $header = new IdentificationHeader('References', 'a@[1.2.3.4]');
+        $this->assertEquals('a@[1.2.3.4]', $header->getId());
+        $this->assertEquals('<a@[1.2.3.4]>', $header->getBodyAsString());
+    }
+
+    public function testIdRigthIsIdnEncoded()
+    {
+        $header = new IdentificationHeader('References', 'a@ä');
+        $this->assertEquals('a@ä', $header->getId());
+        $this->assertEquals('<a@xn--4ca>', $header->getBodyAsString());
+    }
+
+    public function testInvalidIdRightThrowsException()
+    {
+        $this->expectException('Exception');
+        $this->expectExceptionMessage('Email "a@b c d" does not comply with addr-spec of RFC 2822.');
+        new IdentificationHeader('References', 'a@b c d');
+    }
+
+    public function testMissingAtSignThrowsException()
+    {
+        $this->expectException('Exception');
+        $this->expectExceptionMessage('Email "abc" does not comply with addr-spec of RFC 2822.');
+        /* -- RFC 2822, 3.6.4.
+         msg-id          =       [CFWS] "<" id-left "@" id-right ">" [CFWS]
+         */
+        new IdentificationHeader('References', 'abc');
+    }
+
+    public function testSetBody()
+    {
+        $header = new IdentificationHeader('Message-ID', 'c@d');
+        $header->setBody('a@b');
+        $this->assertEquals(['a@b'], $header->getIds());
+    }
+
+    public function testGetBody()
+    {
+        $header = new IdentificationHeader('Message-ID', 'a@b');
+        $this->assertEquals(['a@b'], $header->getBody());
+    }
+
+    public function testStringValue()
+    {
+        $header = new IdentificationHeader('References', ['a@b', 'x@y']);
+        $this->assertEquals('References: <a@b> <x@y>', $header->toString());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Header/MailboxHeaderTest.php b/vendor/symfony/mime/Tests/Header/MailboxHeaderTest.php
new file mode 100644
index 0000000..72f22ff
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Header/MailboxHeaderTest.php
@@ -0,0 +1,77 @@
+<?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\Mime\Tests\Header;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Header\MailboxHeader;
+use Symfony\Component\Mime\NamedAddress;
+
+class MailboxHeaderTest extends TestCase
+{
+    public function testConstructor()
+    {
+        $header = new MailboxHeader('Sender', $address = new Address('fabien@symfony.com'));
+        $this->assertEquals($address, $header->getAddress());
+        $this->assertEquals($address, $header->getBody());
+    }
+
+    public function testAddress()
+    {
+        $header = new MailboxHeader('Sender', new Address('fabien@symfony.com'));
+        $header->setBody($address = new Address('helene@symfony.com'));
+        $this->assertEquals($address, $header->getAddress());
+        $this->assertEquals($address, $header->getBody());
+        $header->setAddress($address = new Address('thomas@symfony.com'));
+        $this->assertEquals($address, $header->getAddress());
+        $this->assertEquals($address, $header->getBody());
+    }
+
+    public function testgetBodyAsString()
+    {
+        $header = new MailboxHeader('Sender', new Address('fabien@symfony.com'));
+        $this->assertEquals('fabien@symfony.com', $header->getBodyAsString());
+
+        $header->setAddress(new Address('fabien@sïmfony.com'));
+        $this->assertEquals('fabien@xn--smfony-iwa.com', $header->getBodyAsString());
+
+        $header = new MailboxHeader('Sender', new NamedAddress('fabien@symfony.com', 'Fabien Potencier'));
+        $this->assertEquals('Fabien Potencier <fabien@symfony.com>', $header->getBodyAsString());
+
+        $header = new MailboxHeader('Sender', new NamedAddress('fabien@symfony.com', 'Fabien Potencier, "from Symfony"'));
+        $this->assertEquals('"Fabien Potencier, \"from Symfony\"" <fabien@symfony.com>', $header->getBodyAsString());
+
+        $header = new MailboxHeader('From', new NamedAddress('fabien@symfony.com', 'Fabien Potencier, \\escaped\\'));
+        $this->assertEquals('"Fabien Potencier, \\\\escaped\\\\" <fabien@symfony.com>', $header->getBodyAsString());
+
+        $name = 'P'.pack('C', 0x8F).'tencier';
+        $header = new MailboxHeader('Sender', new NamedAddress('fabien@symfony.com', 'Fabien '.$name));
+        $header->setCharset('iso-8859-1');
+        $this->assertEquals('Fabien =?'.$header->getCharset().'?Q?P=8Ftencier?= <fabien@symfony.com>', $header->getBodyAsString());
+    }
+
+    public function testUtf8CharsInLocalPartThrows()
+    {
+        $this->expectException('Symfony\Component\Mime\Exception\AddressEncoderException');
+        $header = new MailboxHeader('Sender', new Address('fabïen@symfony.com'));
+        $header->getBodyAsString();
+    }
+
+    public function testToString()
+    {
+        $header = new MailboxHeader('Sender', new Address('fabien@symfony.com'));
+        $this->assertEquals('Sender: fabien@symfony.com', $header->toString());
+
+        $header = new MailboxHeader('Sender', new NamedAddress('fabien@symfony.com', 'Fabien Potencier'));
+        $this->assertEquals('Sender: Fabien Potencier <fabien@symfony.com>', $header->toString());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Header/MailboxListHeaderTest.php b/vendor/symfony/mime/Tests/Header/MailboxListHeaderTest.php
new file mode 100644
index 0000000..2eee1cf
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Header/MailboxListHeaderTest.php
@@ -0,0 +1,131 @@
+<?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\Mime\Tests\Header;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Header\MailboxListHeader;
+use Symfony\Component\Mime\NamedAddress;
+
+class MailboxListHeaderTest extends TestCase
+{
+    // RFC 2822, 3.6.2 for all tests
+
+    public function testMailboxIsSetForAddress()
+    {
+        $header = new MailboxListHeader('From', [new Address('chris@swiftmailer.org')]);
+        $this->assertEquals(['chris@swiftmailer.org'], $header->getAddressStrings());
+    }
+
+    public function testMailboxIsRenderedForNameAddress()
+    {
+        $header = new MailboxListHeader('From', [new NamedAddress('chris@swiftmailer.org', 'Chris Corbyn')]);
+        $this->assertEquals(['Chris Corbyn <chris@swiftmailer.org>'], $header->getAddressStrings());
+    }
+
+    public function testAddressCanBeReturnedForAddress()
+    {
+        $header = new MailboxListHeader('From', $addresses = [new Address('chris@swiftmailer.org')]);
+        $this->assertEquals($addresses, $header->getAddresses());
+    }
+
+    public function testQuotesInNameAreQuoted()
+    {
+        $header = new MailboxListHeader('From', [new NamedAddress('chris@swiftmailer.org', 'Chris Corbyn, "DHE"')]);
+        $this->assertEquals(['"Chris Corbyn, \"DHE\"" <chris@swiftmailer.org>'], $header->getAddressStrings());
+    }
+
+    public function testEscapeCharsInNameAreQuoted()
+    {
+        $header = new MailboxListHeader('From', [new NamedAddress('chris@swiftmailer.org', 'Chris Corbyn, \\escaped\\')]);
+        $this->assertEquals(['"Chris Corbyn, \\\\escaped\\\\" <chris@swiftmailer.org>'], $header->getAddressStrings());
+    }
+
+    public function testUtf8CharsInDomainAreIdnEncoded()
+    {
+        $header = new MailboxListHeader('From', [new NamedAddress('chris@swïftmailer.org', 'Chris Corbyn')]);
+        $this->assertEquals(['Chris Corbyn <chris@xn--swftmailer-78a.org>'], $header->getAddressStrings());
+    }
+
+    public function testUtf8CharsInLocalPartThrows()
+    {
+        $this->expectException('Symfony\Component\Mime\Exception\AddressEncoderException');
+        $header = new MailboxListHeader('From', [new NamedAddress('chrïs@swiftmailer.org', 'Chris Corbyn')]);
+        $header->getAddressStrings();
+    }
+
+    public function testGetMailboxesReturnsNameValuePairs()
+    {
+        $header = new MailboxListHeader('From', $addresses = [new NamedAddress('chris@swiftmailer.org', 'Chris Corbyn, DHE')]);
+        $this->assertEquals($addresses, $header->getAddresses());
+    }
+
+    public function testMultipleAddressesAsMailboxStrings()
+    {
+        $header = new MailboxListHeader('From', [new Address('chris@swiftmailer.org'), new Address('mark@swiftmailer.org')]);
+        $this->assertEquals(['chris@swiftmailer.org', 'mark@swiftmailer.org'], $header->getAddressStrings());
+    }
+
+    public function testNameIsEncodedIfNonAscii()
+    {
+        $name = 'C'.pack('C', 0x8F).'rbyn';
+        $header = new MailboxListHeader('From', [new NamedAddress('chris@swiftmailer.org', 'Chris '.$name)]);
+        $header->setCharset('iso-8859-1');
+        $addresses = $header->getAddressStrings();
+        $this->assertEquals('Chris =?'.$header->getCharset().'?Q?C=8Frbyn?= <chris@swiftmailer.org>', array_shift($addresses));
+    }
+
+    public function testEncodingLineLengthCalculations()
+    {
+        /* -- RFC 2047, 2.
+        An 'encoded-word' may not be more than 75 characters long, including
+        'charset', 'encoding', 'encoded-text', and delimiters.
+        */
+
+        $name = 'C'.pack('C', 0x8F).'rbyn';
+        $header = new MailboxListHeader('From', [new NamedAddress('chris@swiftmailer.org', 'Chris '.$name)]);
+        $header->setCharset('iso-8859-1');
+        $addresses = $header->getAddressStrings();
+        $this->assertEquals('Chris =?'.$header->getCharset().'?Q?C=8Frbyn?= <chris@swiftmailer.org>', array_shift($addresses));
+    }
+
+    public function testGetValueReturnsMailboxStringValue()
+    {
+        $header = new MailboxListHeader('From', [new NamedAddress('chris@swiftmailer.org', 'Chris Corbyn')]);
+        $this->assertEquals('Chris Corbyn <chris@swiftmailer.org>', $header->getBodyAsString());
+    }
+
+    public function testGetValueReturnsMailboxStringValueForMultipleMailboxes()
+    {
+        $header = new MailboxListHeader('From', [new NamedAddress('chris@swiftmailer.org', 'Chris Corbyn'), new NamedAddress('mark@swiftmailer.org', 'Mark Corbyn')]);
+        $this->assertEquals('Chris Corbyn <chris@swiftmailer.org>, Mark Corbyn <mark@swiftmailer.org>', $header->getBodyAsString());
+    }
+
+    public function testSetBody()
+    {
+        $header = new MailboxListHeader('From', []);
+        $header->setBody($addresses = [new Address('chris@swiftmailer.org')]);
+        $this->assertEquals($addresses, $header->getAddresses());
+    }
+
+    public function testGetBody()
+    {
+        $header = new MailboxListHeader('From', $addresses = [new Address('chris@swiftmailer.org')]);
+        $this->assertEquals($addresses, $header->getBody());
+    }
+
+    public function testToString()
+    {
+        $header = new MailboxListHeader('From', [new NamedAddress('chris@example.org', 'Chris Corbyn'), new NamedAddress('mark@example.org', 'Mark Corbyn')]);
+        $this->assertEquals('From: Chris Corbyn <chris@example.org>, Mark Corbyn <mark@example.org>', $header->toString());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Header/ParameterizedHeaderTest.php b/vendor/symfony/mime/Tests/Header/ParameterizedHeaderTest.php
new file mode 100644
index 0000000..e41d038
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Header/ParameterizedHeaderTest.php
@@ -0,0 +1,295 @@
+<?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\Mime\Tests\Header;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Header\ParameterizedHeader;
+
+class ParameterizedHeaderTest extends TestCase
+{
+    private $charset = 'utf-8';
+    private $lang = 'en-us';
+
+    public function testValueIsReturnedVerbatim()
+    {
+        $header = new ParameterizedHeader('Content-Type', 'text/plain');
+        $this->assertEquals('text/plain', $header->getValue());
+    }
+
+    public function testParametersAreAppended()
+    {
+        /* -- RFC 2045, 5.1
+        parameter := attribute "=" value
+
+        attribute := token
+                                    ; Matching of attributes
+                                    ; is ALWAYS case-insensitive.
+
+        value := token / quoted-string
+
+        token := 1*<any (US-ASCII) CHAR except SPACE, CTLs,
+                 or tspecials>
+
+        tspecials :=  "(" / ")" / "<" / ">" / "@" /
+                   "," / ";" / ":" / "\" / <">
+                   "/" / "[" / "]" / "?" / "="
+                   ; Must be in quoted-string,
+                   ; to use within parameter values
+        */
+
+        $header = new ParameterizedHeader('Content-Type', 'text/plain');
+        $header->setParameters(['charset' => 'utf-8']);
+        $this->assertEquals('text/plain; charset=utf-8', $header->getBodyAsString());
+    }
+
+    public function testSpaceInParamResultsInQuotedString()
+    {
+        $header = new ParameterizedHeader('Content-Type', 'attachment');
+        $header->setParameters(['filename' => 'my file.txt']);
+        $this->assertEquals('attachment; filename="my file.txt"', $header->getBodyAsString());
+    }
+
+    public function testLongParamsAreBrokenIntoMultipleAttributeStrings()
+    {
+        /* -- RFC 2231, 3.
+        The asterisk character ("*") followed
+        by a decimal count is employed to indicate that multiple parameters
+        are being used to encapsulate a single parameter value.  The count
+        starts at 0 and increments by 1 for each subsequent section of the
+        parameter value.  Decimal values are used and neither leading zeroes
+        nor gaps in the sequence are allowed.
+
+        The original parameter value is recovered by concatenating the
+        various sections of the parameter, in order.  For example, the
+        content-type field
+
+                Content-Type: message/external-body; access-type=URL;
+         URL*0="ftp://";
+         URL*1="cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar"
+
+        is semantically identical to
+
+                Content-Type: message/external-body; access-type=URL;
+                    URL="ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar"
+
+        Note that quotes around parameter values are part of the value
+        syntax; they are NOT part of the value itself.  Furthermore, it is
+        explicitly permitted to have a mixture of quoted and unquoted
+        continuation fields.
+        */
+
+        $value = str_repeat('a', 180);
+
+        $header = new ParameterizedHeader('Content-Disposition', 'attachment');
+        $header->setParameters(['filename' => $value]);
+        $this->assertEquals(
+            'attachment; '.
+            'filename*0*=utf-8\'\''.str_repeat('a', 60).";\r\n ".
+            'filename*1*='.str_repeat('a', 60).";\r\n ".
+            'filename*2*='.str_repeat('a', 60),
+            $header->getBodyAsString()
+        );
+    }
+
+    public function testEncodedParamDataIncludesCharsetAndLanguage()
+    {
+        /* -- RFC 2231, 4.
+        Asterisks ("*") are reused to provide the indicator that language and
+        character set information is present and encoding is being used. A
+        single quote ("'") is used to delimit the character set and language
+        information at the beginning of the parameter value. Percent signs
+        ("%") are used as the encoding flag, which agrees with RFC 2047.
+
+        Specifically, an asterisk at the end of a parameter name acts as an
+        indicator that character set and language information may appear at
+        the beginning of the parameter value. A single quote is used to
+        separate the character set, language, and actual value information in
+        the parameter value string, and an percent sign is used to flag
+        octets encoded in hexadecimal.  For example:
+
+                Content-Type: application/x-stuff;
+         title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A
+
+        Note that it is perfectly permissible to leave either the character
+        set or language field blank.  Note also that the single quote
+        delimiters MUST be present even when one of the field values is
+        omitted.
+        */
+
+        $value = str_repeat('a', 20).pack('C', 0x8F).str_repeat('a', 10);
+        $header = new ParameterizedHeader('Content-Disposition', 'attachment');
+        $header->setCharset('iso-8859-1');
+        $header->setValue('attachment');
+        $header->setParameters(['filename' => $value]);
+        $header->setLanguage($this->lang);
+        $this->assertEquals(
+            'attachment; filename*='.$header->getCharset()."'".$this->lang."'".
+            str_repeat('a', 20).'%8F'.str_repeat('a', 10),
+            $header->getBodyAsString()
+        );
+    }
+
+    public function testMultipleEncodedParamLinesAreFormattedCorrectly()
+    {
+        /* -- RFC 2231, 4.1.
+        Character set and language information may be combined with the
+        parameter continuation mechanism. For example:
+
+        Content-Type: application/x-stuff
+        title*0*=us-ascii'en'This%20is%20even%20more%20
+        title*1*=%2A%2A%2Afun%2A%2A%2A%20
+        title*2="isn't it!"
+
+           Note that:
+
+        (1)   Language and character set information only appear at
+              the beginning of a given parameter value.
+
+        (2)   Continuations do not provide a facility for using more
+              than one character set or language in the same
+              parameter value.
+
+        (3)   A value presented using multiple continuations may
+              contain a mixture of encoded and unencoded segments.
+
+        (4)   The first segment of a continuation MUST be encoded if
+              language and character set information are given.
+
+        (5)   If the first segment of a continued parameter value is
+              encoded the language and character set field delimiters
+              MUST be present even when the fields are left blank.
+           */
+
+        $value = str_repeat('a', 20).pack('C', 0x8F).str_repeat('a', 60);
+        $header = new ParameterizedHeader('Content-Disposition', 'attachment');
+        $header->setValue('attachment');
+        $header->setCharset('utf-6');
+        $header->setParameters(['filename' => $value]);
+        $header->setLanguage($this->lang);
+        $this->assertEquals(
+            'attachment; filename*0*='.$header->getCharset()."'".$this->lang."'".
+            str_repeat('a', 20).'%8F'.str_repeat('a', 23).";\r\n ".
+            'filename*1*='.str_repeat('a', 37),
+            $header->getBodyAsString()
+        );
+    }
+
+    public function testToString()
+    {
+        $header = new ParameterizedHeader('Content-Type', 'text/html');
+        $header->setParameters(['charset' => 'utf-8']);
+        $this->assertEquals('Content-Type: text/html; charset=utf-8', $header->toString());
+    }
+
+    public function testValueCanBeEncodedIfNonAscii()
+    {
+        $value = 'fo'.pack('C', 0x8F).'bar';
+        $header = new ParameterizedHeader('X-Foo', $value);
+        $header->setCharset('iso-8859-1');
+        $header->setParameters(['lookslike' => 'foobar']);
+        $this->assertEquals('X-Foo: =?'.$header->getCharset().'?Q?fo=8Fbar?=; lookslike=foobar', $header->toString());
+    }
+
+    public function testValueAndParamCanBeEncodedIfNonAscii()
+    {
+        $value = 'fo'.pack('C', 0x8F).'bar';
+        $header = new ParameterizedHeader('X-Foo', $value);
+        $header->setCharset('iso-8859-1');
+        $header->setParameters(['says' => $value]);
+        $this->assertEquals('X-Foo: =?'.$header->getCharset().'?Q?fo=8Fbar?=; says*='.$header->getCharset()."''fo%8Fbar", $header->toString());
+    }
+
+    public function testParamsAreEncodedIfNonAscii()
+    {
+        $value = 'fo'.pack('C', 0x8F).'bar';
+        $header = new ParameterizedHeader('X-Foo', 'bar');
+        $header->setCharset('iso-8859-1');
+        $header->setParameters(['says' => $value]);
+        $this->assertEquals('X-Foo: bar; says*='.$header->getCharset()."''fo%8Fbar", $header->toString());
+    }
+
+    public function testParamsAreEncodedWithLegacyEncodingEnabled()
+    {
+        $value = 'fo'.pack('C', 0x8F).'bar';
+        $header = new ParameterizedHeader('Content-Type', 'bar');
+        $header->setCharset('iso-8859-1');
+        $header->setParameters(['says' => $value]);
+        $this->assertEquals('Content-Type: bar; says="=?'.$header->getCharset().'?Q?fo=8Fbar?="', $header->toString());
+    }
+
+    public function testLanguageInformationAppearsInEncodedWords()
+    {
+        /* -- RFC 2231, 5.
+        5.  Language specification in Encoded Words
+
+        RFC 2047 provides support for non-US-ASCII character sets in RFC 822
+        message header comments, phrases, and any unstructured text field.
+        This is done by defining an encoded word construct which can appear
+        in any of these places.  Given that these are fields intended for
+        display, it is sometimes necessary to associate language information
+        with encoded words as well as just the character set.  This
+        specification extends the definition of an encoded word to allow the
+        inclusion of such information.  This is simply done by suffixing the
+        character set specification with an asterisk followed by the language
+        tag.  For example:
+
+                    From: =?US-ASCII*EN?Q?Keith_Moore?= <moore@cs.utk.edu>
+
+        -- RFC 2047, 5. Use of encoded-words in message headers
+          ...
+        + An 'encoded-word' MUST NOT be used in parameter of a MIME
+          Content-Type or Content-Disposition field, or in any structured
+          field body except within a 'comment' or 'phrase'.
+
+        -- RFC 2047, Appendix - changes since RFC 1522
+          ...
+        + clarify that encoded-words are allowed in '*text' fields in both
+          RFC822 headers and MIME body part headers, but NOT as parameter
+          values.
+        */
+
+        $value = 'fo'.pack('C', 0x8F).'bar';
+        $header = new ParameterizedHeader('X-Foo', $value);
+        $header->setCharset('iso-8859-1');
+        $header->setLanguage('en');
+        $header->setParameters(['says' => $value]);
+        $this->assertEquals('X-Foo: =?'.$header->getCharset().'*en?Q?fo=8Fbar?=; says*='.$header->getCharset()."'en'fo%8Fbar", $header->toString());
+    }
+
+    public function testSetBody()
+    {
+        $header = new ParameterizedHeader('Content-Type', 'text/html');
+        $header->setBody('text/plain');
+        $this->assertEquals('text/plain', $header->getValue());
+    }
+
+    public function testGetBody()
+    {
+        $header = new ParameterizedHeader('Content-Type', 'text/plain');
+        $this->assertEquals('text/plain', $header->getBody());
+    }
+
+    public function testSetParameter()
+    {
+        $header = new ParameterizedHeader('Content-Type', 'text/html');
+        $header->setParameters(['charset' => 'utf-8', 'delsp' => 'yes']);
+        $header->setParameter('delsp', 'no');
+        $this->assertEquals(['charset' => 'utf-8', 'delsp' => 'no'], $header->getParameters());
+    }
+
+    public function testGetParameter()
+    {
+        $header = new ParameterizedHeader('Content-Type', 'text/html');
+        $header->setParameters(['charset' => 'utf-8', 'delsp' => 'yes']);
+        $this->assertEquals('utf-8', $header->getParameter('charset'));
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Header/PathHeaderTest.php b/vendor/symfony/mime/Tests/Header/PathHeaderTest.php
new file mode 100644
index 0000000..a8386f8
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Header/PathHeaderTest.php
@@ -0,0 +1,77 @@
+<?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\Mime\Tests\Header;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Header\PathHeader;
+
+class PathHeaderTest extends TestCase
+{
+    public function testSingleAddressCanBeSetAndFetched()
+    {
+        $header = new PathHeader('Return-Path', $address = new Address('chris@swiftmailer.org'));
+        $this->assertEquals($address, $header->getAddress());
+    }
+
+    public function testAddressMustComplyWithRfc2822()
+    {
+        $this->expectException('Exception');
+        new PathHeader('Return-Path', new Address('chr is@swiftmailer.org'));
+    }
+
+    public function testValueIsAngleAddrWithValidAddress()
+    {
+        /* -- RFC 2822, 3.6.7.
+
+            return          =       "Return-Path:" path CRLF
+
+            path            =       ([CFWS] "<" ([CFWS] / addr-spec) ">" [CFWS]) /
+                                                            obs-path
+         */
+
+        $header = new PathHeader('Return-Path', new Address('chris@swiftmailer.org'));
+        $this->assertEquals('<chris@swiftmailer.org>', $header->getBodyAsString());
+    }
+
+    public function testAddressIsIdnEncoded()
+    {
+        $header = new PathHeader('Return-Path', new Address('chris@swïftmailer.org'));
+        $this->assertEquals('<chris@xn--swftmailer-78a.org>', $header->getBodyAsString());
+    }
+
+    public function testAddressMustBeEncodable()
+    {
+        $this->expectException('Symfony\Component\Mime\Exception\AddressEncoderException');
+        $header = new PathHeader('Return-Path', new Address('chrïs@swiftmailer.org'));
+        $header->getBodyAsString();
+    }
+
+    public function testSetBody()
+    {
+        $header = new PathHeader('Return-Path', new Address('foo@example.com'));
+        $header->setBody($address = new Address('foo@bar.tld'));
+        $this->assertEquals($address, $header->getAddress());
+    }
+
+    public function testGetBody()
+    {
+        $header = new PathHeader('Return-Path', $address = new Address('foo@bar.tld'));
+        $this->assertEquals($address, $header->getBody());
+    }
+
+    public function testToString()
+    {
+        $header = new PathHeader('Return-Path', new Address('chris@swiftmailer.org'));
+        $this->assertEquals('Return-Path: <chris@swiftmailer.org>', $header->toString());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Header/UnstructuredHeaderTest.php b/vendor/symfony/mime/Tests/Header/UnstructuredHeaderTest.php
new file mode 100644
index 0000000..3e065bf
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Header/UnstructuredHeaderTest.php
@@ -0,0 +1,247 @@
+<?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\Mime\Tests\Header;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Header\UnstructuredHeader;
+
+class UnstructuredHeaderTest extends TestCase
+{
+    private $charset = 'utf-8';
+
+    public function testGetNameReturnsNameVerbatim()
+    {
+        $header = new UnstructuredHeader('Subject', '');
+        $this->assertEquals('Subject', $header->getName());
+    }
+
+    public function testGetValueReturnsValueVerbatim()
+    {
+        $header = new UnstructuredHeader('Subject', 'Test');
+        $this->assertEquals('Test', $header->getValue());
+    }
+
+    public function testBasicStructureIsKeyValuePair()
+    {
+        /* -- RFC 2822, 2.2
+        Header fields are lines composed of a field name, followed by a colon
+        (":"), followed by a field body, and terminated by CRLF.
+        */
+        $header = new UnstructuredHeader('Subject', 'Test');
+        $this->assertEquals('Subject: Test', $header->toString());
+    }
+
+    public function testLongHeadersAreFoldedAtWordBoundary()
+    {
+        /* -- RFC 2822, 2.2.3
+        Each header field is logically a single line of characters comprising
+        the field name, the colon, and the field body.  For convenience
+        however, and to deal with the 998/78 character limitations per line,
+        the field body portion of a header field can be split into a multiple
+        line representation; this is called "folding".  The general rule is
+        that wherever this standard allows for folding white space (not
+        simply WSP characters), a CRLF may be inserted before any WSP.
+        */
+
+        $value = 'The quick brown fox jumped over the fence, he was a very very '.
+            'scary brown fox with a bushy tail';
+        $header = new UnstructuredHeader('X-Custom-Header', $value);
+        /*
+            X-Custom-Header: The quick brown fox jumped over the fence, he was a very very
+            scary brown fox with a bushy tail
+        */
+        $this->assertEquals(
+            'X-Custom-Header: The quick brown fox jumped over the fence, he was a'.
+            ' very'."\r\n".//Folding
+            ' very scary brown fox with a bushy tail',
+            $header->toString(), '%s: The header should have been folded at 76th char'
+        );
+    }
+
+    public function testPrintableAsciiOnlyAppearsInHeaders()
+    {
+        /* -- RFC 2822, 2.2.
+        A field name MUST be composed of printable US-ASCII characters (i.e.,
+        characters that have values between 33 and 126, inclusive), except
+        colon.  A field body may be composed of any US-ASCII characters,
+        except for CR and LF.
+        */
+
+        $nonAsciiChar = pack('C', 0x8F);
+        $header = new UnstructuredHeader('X-Test', $nonAsciiChar);
+        $this->assertRegExp('~^[^:\x00-\x20\x80-\xFF]+: [^\x80-\xFF\r\n]+$~s', $header->toString());
+    }
+
+    public function testEncodedWordsFollowGeneralStructure()
+    {
+        /* -- RFC 2047, 1.
+        Generally, an "encoded-word" is a sequence of printable ASCII
+        characters that begins with "=?", ends with "?=", and has two "?"s in
+        between.
+        */
+
+        $nonAsciiChar = pack('C', 0x8F);
+        $header = new UnstructuredHeader('X-Test', $nonAsciiChar);
+        $this->assertRegExp('~^X-Test: \=?.*?\?.*?\?.*?\?=$~s', $header->toString());
+    }
+
+    public function testEncodedWordIncludesCharsetAndEncodingMethodAndText()
+    {
+        /* -- RFC 2047, 2.
+        An 'encoded-word' is defined by the following ABNF grammar.  The
+        notation of RFC 822 is used, with the exception that white space
+        characters MUST NOT appear between components of an 'encoded-word'.
+
+        encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
+        */
+
+        $nonAsciiChar = pack('C', 0x8F);
+        $header = new UnstructuredHeader('X-Test', $nonAsciiChar);
+        $header->setCharset('iso-8859-1');
+        $this->assertEquals('X-Test: =?'.$header->getCharset().'?Q?=8F?=', $header->toString());
+    }
+
+    public function testEncodedWordsAreUsedToEncodedNonPrintableAscii()
+    {
+        // SPACE and TAB permitted
+        $nonPrintableBytes = array_merge(range(0x00, 0x08), range(0x10, 0x19), [0x7F]);
+        foreach ($nonPrintableBytes as $byte) {
+            $char = pack('C', $byte);
+            $encodedChar = sprintf('=%02X', $byte);
+            $header = new UnstructuredHeader('X-A', $char);
+            $header->setCharset('iso-8859-1');
+            $this->assertEquals('X-A: =?'.$header->getCharset().'?Q?'.$encodedChar.'?=', $header->toString(), 'Non-printable ascii should be encoded');
+        }
+    }
+
+    public function testEncodedWordsAreUsedToEncode8BitOctets()
+    {
+        foreach (range(0x80, 0xFF) as $byte) {
+            $char = pack('C', $byte);
+            $encodedChar = sprintf('=%02X', $byte);
+            $header = new UnstructuredHeader('X-A', $char);
+            $header->setCharset('iso-8859-1');
+            $this->assertEquals('X-A: =?'.$header->getCharset().'?Q?'.$encodedChar.'?=', $header->toString(), '8-bit octets should be encoded');
+        }
+    }
+
+    public function testEncodedWordsAreNoMoreThan75CharsPerLine()
+    {
+        /* -- RFC 2047, 2.
+        An 'encoded-word' may not be more than 75 characters long, including
+        'charset', 'encoding', 'encoded-text', and delimiters.
+
+        ... SNIP ...
+
+        While there is no limit to the length of a multiple-line header
+        field, each line of a header field that contains one or more
+        'encoded-word's is limited to 76 characters.
+        */
+
+        $nonAsciiChar = pack('C', 0x8F);
+
+        //Note that multi-line headers begin with LWSP which makes 75 + 1 = 76
+        //Note also that =?utf-8?q??= is 12 chars which makes 75 - 12 = 63
+
+        //* X-Test: is 8 chars
+        $header = new UnstructuredHeader('X-Test', $nonAsciiChar);
+        $header->setCharset('iso-8859-1');
+        $this->assertEquals('X-Test: =?'.$header->getCharset().'?Q?=8F?=', $header->toString());
+    }
+
+    public function testFWSPIsUsedWhenEncoderReturnsMultipleLines()
+    {
+        /* --RFC 2047, 2.
+        If it is desirable to encode more text than will fit in an 'encoded-word' of
+        75 characters, multiple 'encoded-word's (separated by CRLF SPACE) may
+        be used.
+        */
+
+        // Note that multi-line headers begin with LWSP which makes 75 + 1 = 76
+        // Note also that =?utf-8?q??= is 12 chars which makes 75 - 12 = 63
+
+        //* X-Test: is 8 chars
+        $header = new UnstructuredHeader('X-Test', pack('C', 0x8F).'line_one_here'."\r\n".'line_two_here');
+        $header->setCharset('iso-8859-1');
+        $this->assertEquals('X-Test: =?'.$header->getCharset().'?Q?=8Fline=5Fone=5Fhere?='."\r\n".' =?'.$header->getCharset().'?Q?line=5Ftwo=5Fhere?=', $header->toString());
+    }
+
+    public function testAdjacentWordsAreEncodedTogether()
+    {
+        /* -- RFC 2047, 5 (1)
+         Ordinary ASCII text and 'encoded-word's may appear together in the
+        same header field.  However, an 'encoded-word' that appears in a
+        header field defined as '*text' MUST be separated from any adjacent
+        'encoded-word' or 'text' by 'linear-white-space'.
+
+         -- RFC 2047, 2.
+         IMPORTANT: 'encoded-word's are designed to be recognized as 'atom's
+        by an RFC 822 parser.  As a consequence, unencoded white space
+        characters (such as SPACE and HTAB) are FORBIDDEN within an
+        'encoded-word'.
+        */
+
+        // It would be valid to encode all words needed, however it's probably
+        // easiest to encode the longest amount required at a time
+
+        $word = 'w'.pack('C', 0x8F).'rd';
+        $text = 'start '.$word.' '.$word.' then '.$word;
+        // 'start', ' word word', ' and end', ' word'
+
+        $header = new UnstructuredHeader('X-Test', $text);
+        $header->setCharset('iso-8859-1');
+        $this->assertEquals('X-Test: start =?'.$header->getCharset().'?Q?'.
+            'w=8Frd_w=8Frd?= then =?'.$header->getCharset().'?Q?'.
+            'w=8Frd?=', $header->toString(),
+            'Adjacent encoded words should appear grouped with WSP encoded'
+        );
+    }
+
+    public function testLanguageInformationAppearsInEncodedWords()
+    {
+        /* -- RFC 2231, 5.
+        5.  Language specification in Encoded Words
+
+        RFC 2047 provides support for non-US-ASCII character sets in RFC 822
+        message header comments, phrases, and any unstructured text field.
+        This is done by defining an encoded word construct which can appear
+        in any of these places.  Given that these are fields intended for
+        display, it is sometimes necessary to associate language information
+        with encoded words as well as just the character set.  This
+        specification extends the definition of an encoded word to allow the
+        inclusion of such information.  This is simply done by suffixing the
+        character set specification with an asterisk followed by the language
+        tag.  For example:
+
+                    From: =?US-ASCII*EN?Q?Keith_Moore?= <moore@cs.utk.edu>
+        */
+
+        $value = 'fo'.pack('C', 0x8F).'bar';
+        $header = new UnstructuredHeader('Subject', $value);
+        $header->setLanguage('en');
+        $header->setCharset('iso-8859-1');
+        $this->assertEquals('Subject: =?iso-8859-1*en?Q?fo=8Fbar?=', $header->toString());
+    }
+
+    public function testSetBody()
+    {
+        $header = new UnstructuredHeader('X-Test', '');
+        $header->setBody('test');
+        $this->assertEquals('test', $header->getValue());
+    }
+
+    public function testGetBody()
+    {
+        $header = new UnstructuredHeader('Subject', 'test');
+        $this->assertEquals('test', $header->getBody());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/MessageConverterTest.php b/vendor/symfony/mime/Tests/MessageConverterTest.php
new file mode 100644
index 0000000..6a78086
--- /dev/null
+++ b/vendor/symfony/mime/Tests/MessageConverterTest.php
@@ -0,0 +1,81 @@
+<?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\Mime\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Email;
+use Symfony\Component\Mime\Message;
+use Symfony\Component\Mime\MessageConverter;
+
+class MessageConverterTest extends TestCase
+{
+    public function testToEmail()
+    {
+        $file = file_get_contents(__DIR__.'/Fixtures/mimetypes/test.gif');
+        $email = (new Email())->from('fabien@symfony.com');
+        $this->assertSame($email, MessageConverter::toEmail($email));
+
+        $this->assertConversion((clone $email)->text('text content'));
+        $this->assertConversion((clone $email)->html('HTML content <img src="cid:test.jpg" />'));
+        $this->assertConversion((clone $email)
+            ->text('text content')
+            ->html('HTML content <img src="cid:test.jpg" />')
+        );
+        $this->assertConversion((clone $email)
+            ->text('text content')
+            ->html('HTML content <img src="cid:test.jpg" />')
+            ->embed($file, 'test.jpg', 'image/gif')
+        );
+        $this->assertConversion((clone $email)
+            ->text('text content')
+            ->html('HTML content <img src="cid:test.jpg" />')
+            ->attach($file, 'test_attached.jpg', 'image/gif')
+        );
+        $this->assertConversion((clone $email)
+            ->text('text content')
+            ->html('HTML content <img src="cid:test.jpg" />')
+            ->embed($file, 'test.jpg', 'image/gif')
+            ->attach($file, 'test_attached.jpg', 'image/gif')
+        );
+        $this->assertConversion((clone $email)
+            ->text('text content')
+            ->attach($file, 'test_attached.jpg', 'image/gif')
+        );
+        $this->assertConversion((clone $email)
+            ->html('HTML content <img src="cid:test.jpg" />')
+            ->attach($file, 'test_attached.jpg', 'image/gif')
+        );
+        $this->assertConversion((clone $email)
+            ->html('HTML content <img src="cid:test.jpg" />')
+            ->embed($file, 'test.jpg', 'image/gif')
+        );
+        $this->assertConversion((clone $email)
+            ->text('text content')
+            ->embed($file, 'test_attached.jpg', 'image/gif')
+        );
+    }
+
+    private function assertConversion(Email $expected)
+    {
+        $r = new \ReflectionMethod($expected, 'generateBody');
+        $r->setAccessible(true);
+
+        $message = new Message($expected->getHeaders(), $r->invoke($expected));
+        $converted = MessageConverter::toEmail($message);
+        if ($expected->getHtmlBody()) {
+            $this->assertStringMatchesFormat(str_replace('cid:test.jpg', 'cid:%s', $expected->getHtmlBody()), $converted->getHtmlBody());
+            $expected->html('HTML content');
+            $converted->html('HTML content');
+        }
+        $this->assertEquals($expected, $converted);
+    }
+}
diff --git a/vendor/symfony/mime/Tests/MessageTest.php b/vendor/symfony/mime/Tests/MessageTest.php
new file mode 100644
index 0000000..cc806b9
--- /dev/null
+++ b/vendor/symfony/mime/Tests/MessageTest.php
@@ -0,0 +1,151 @@
+<?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\Mime\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\Header\MailboxListHeader;
+use Symfony\Component\Mime\Header\UnstructuredHeader;
+use Symfony\Component\Mime\Message;
+use Symfony\Component\Mime\NamedAddress;
+use Symfony\Component\Mime\Part\TextPart;
+
+class MessageTest extends TestCase
+{
+    public function testConstruct()
+    {
+        $m = new Message();
+        $this->assertNull($m->getBody());
+        $this->assertEquals(new Headers(), $m->getHeaders());
+
+        $m = new Message($h = (new Headers())->addDateHeader('Date', new \DateTime()), $b = new TextPart('content'));
+        $this->assertSame($b, $m->getBody());
+        $this->assertEquals($h, $m->getHeaders());
+
+        $m = new Message();
+        $m->setBody($b);
+        $m->setHeaders($h);
+        $this->assertSame($b, $m->getBody());
+        $this->assertSame($h, $m->getHeaders());
+    }
+
+    public function testGetPreparedHeadersThrowsWhenNoFrom()
+    {
+        $this->expectException(\LogicException::class);
+        (new Message())->getPreparedHeaders();
+    }
+
+    public function testGetPreparedHeadersCloneHeaders()
+    {
+        $message = new Message();
+        $message->getHeaders()->addMailboxListHeader('From', ['fabien@symfony.com']);
+        $this->assertNotSame($message->getPreparedHeaders(), $message->getHeaders());
+    }
+
+    public function testGetPreparedHeadersSetRequiredHeaders()
+    {
+        $message = new Message();
+        $message->getHeaders()->addMailboxListHeader('From', ['fabien@symfony.com']);
+        $headers = $message->getPreparedHeaders();
+        $this->assertTrue($headers->has('MIME-Version'));
+        $this->assertTrue($headers->has('Message-ID'));
+        $this->assertTrue($headers->has('Date'));
+        $this->assertFalse($headers->has('Bcc'));
+    }
+
+    public function testGetPreparedHeaders()
+    {
+        $message = new Message();
+        $message->getHeaders()->addMailboxListHeader('From', ['fabien@symfony.com']);
+        $h = $message->getPreparedHeaders();
+        $this->assertCount(4, iterator_to_array($h->all()));
+        $this->assertEquals(new MailboxListHeader('From', [new Address('fabien@symfony.com')]), $h->get('From'));
+        $this->assertEquals(new UnstructuredHeader('MIME-Version', '1.0'), $h->get('mime-version'));
+        $this->assertTrue($h->has('Message-Id'));
+        $this->assertTrue($h->has('Date'));
+
+        $message = new Message();
+        $message->getHeaders()->addMailboxListHeader('From', ['fabien@symfony.com']);
+        $message->getHeaders()->addDateHeader('Date', $n = new \DateTimeImmutable());
+        $this->assertEquals($n, $message->getPreparedHeaders()->get('Date')->getDateTime());
+
+        $message = new Message();
+        $message->getHeaders()->addMailboxListHeader('From', ['fabien@symfony.com']);
+        $message->getHeaders()->addMailboxListHeader('Bcc', ['fabien@symfony.com']);
+        $this->assertNull($message->getPreparedHeaders()->get('Bcc'));
+    }
+
+    public function testGetPreparedHeadersWithNoFrom()
+    {
+        $this->expectException(\LogicException::class);
+        (new Message())->getPreparedHeaders();
+    }
+
+    public function testGetPreparedHeadersWithNamedFrom()
+    {
+        $message = new Message();
+        $message->getHeaders()->addMailboxListHeader('From', [new NamedAddress('fabien@symfony.com', 'Fabien')]);
+        $h = $message->getPreparedHeaders();
+        $this->assertEquals(new MailboxListHeader('From', [new NamedAddress('fabien@symfony.com', 'Fabien')]), $h->get('From'));
+        $this->assertTrue($h->has('Message-Id'));
+    }
+
+    public function testGetPreparedHeadersHasSenderWhenNeeded()
+    {
+        $message = new Message();
+        $message->getHeaders()->addMailboxListHeader('From', ['fabien@symfony.com']);
+        $this->assertNull($message->getPreparedHeaders()->get('Sender'));
+
+        $message = new Message();
+        $message->getHeaders()->addMailboxListHeader('From', ['fabien@symfony.com', 'lucas@symfony.com']);
+        $this->assertEquals('fabien@symfony.com', $message->getPreparedHeaders()->get('Sender')->getAddress()->getAddress());
+
+        $message = new Message();
+        $message->getHeaders()->addMailboxListHeader('From', ['fabien@symfony.com', 'lucas@symfony.com']);
+        $message->getHeaders()->addMailboxHeader('Sender', 'thomas@symfony.com');
+        $this->assertEquals('thomas@symfony.com', $message->getPreparedHeaders()->get('Sender')->getAddress()->getAddress());
+    }
+
+    public function testToString()
+    {
+        $message = new Message();
+        $message->getHeaders()->addMailboxListHeader('From', ['fabien@symfony.com']);
+        $expected = <<<EOF
+From: fabien@symfony.com
+MIME-Version: 1.0
+Date: %s
+Message-ID: <%s@symfony.com>
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+
+EOF;
+        $this->assertStringMatchesFormat($expected, str_replace("\r\n", "\n", $message->toString()));
+        $this->assertStringMatchesFormat($expected, str_replace("\r\n", "\n", implode('', iterator_to_array($message->toIterable(), false))));
+
+        $message = new Message(null, new TextPart('content'));
+        $message->getHeaders()->addMailboxListHeader('From', ['fabien@symfony.com']);
+        $expected = <<<EOF
+From: fabien@symfony.com
+MIME-Version: 1.0
+Date: %s
+Message-ID: <%s@symfony.com>
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+content
+EOF;
+        $this->assertStringMatchesFormat($expected, str_replace("\r\n", "\n", $message->toString()));
+        $this->assertStringMatchesFormat($expected, str_replace("\r\n", "\n", implode('', iterator_to_array($message->toIterable(), false))));
+    }
+}
diff --git a/vendor/symfony/mime/Tests/MimeTypesTest.php b/vendor/symfony/mime/Tests/MimeTypesTest.php
new file mode 100644
index 0000000..c5ff262
--- /dev/null
+++ b/vendor/symfony/mime/Tests/MimeTypesTest.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\Mime\Tests;
+
+use Symfony\Component\Mime\Exception\RuntimeException;
+use Symfony\Component\Mime\MimeTypeGuesserInterface;
+use Symfony\Component\Mime\MimeTypes;
+
+/**
+ * @requires extension fileinfo
+ */
+class MimeTypesTest extends AbstractMimeTypeGuesserTest
+{
+    protected function getGuesser(): MimeTypeGuesserInterface
+    {
+        return new MimeTypes();
+    }
+
+    public function testUnsupportedGuesser()
+    {
+        $guesser = $this->getGuesser();
+        $guesser->registerGuesser(new class() implements MimeTypeGuesserInterface {
+            public function isGuesserSupported(): bool
+            {
+                return false;
+            }
+
+            public function guessMimeType(string $mimeType): ?string
+            {
+                throw new RuntimeException('Should never be called.');
+            }
+        });
+        $this->assertEquals('image/gif', $guesser->guessMimeType(__DIR__.'/Fixtures/mimetypes/test'));
+    }
+
+    public function testGetExtensions()
+    {
+        $mt = new MimeTypes();
+        $this->assertSame(['mbox'], $mt->getExtensions('application/mbox'));
+        $this->assertSame(['ai', 'eps', 'ps'], $mt->getExtensions('application/postscript'));
+        $this->assertSame([], $mt->getExtensions('application/whatever-symfony'));
+    }
+
+    public function testGetMimeTypes()
+    {
+        $mt = new MimeTypes();
+        $this->assertSame(['application/mbox'], $mt->getMimeTypes('mbox'));
+        $this->assertContains('application/postscript', $mt->getMimeTypes('ai'));
+        $this->assertContains('application/postscript', $mt->getMimeTypes('ps'));
+        $this->assertSame([], $mt->getMimeTypes('symfony'));
+    }
+}
diff --git a/vendor/symfony/mime/Tests/NamedAddressTest.php b/vendor/symfony/mime/Tests/NamedAddressTest.php
new file mode 100644
index 0000000..b793cbb
--- /dev/null
+++ b/vendor/symfony/mime/Tests/NamedAddressTest.php
@@ -0,0 +1,42 @@
+<?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\Mime\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\NamedAddress;
+
+class NamedAddressTest extends TestCase
+{
+    public function testConstructor()
+    {
+        $a = new NamedAddress('fabien@symfonï.com', 'Fabien');
+        $this->assertEquals('Fabien', $a->getName());
+        $this->assertEquals('fabien@symfonï.com', $a->getAddress());
+        $this->assertEquals('Fabien <fabien@xn--symfon-nwa.com>', $a->toString());
+        $this->assertEquals('fabien@xn--symfon-nwa.com', $a->getEncodedAddress());
+    }
+
+    public function nameEmptyDataProvider(): array
+    {
+        return [[''], [' '], [" \r\n "]];
+    }
+
+    /**
+     * @dataProvider nameEmptyDataProvider
+     */
+    public function testNameEmpty(string $name)
+    {
+        $mail = 'mail@example.org';
+
+        $this->assertSame($mail, (new NamedAddress($mail, $name))->getEncodedNamedAddress());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Part/DataPartTest.php b/vendor/symfony/mime/Tests/Part/DataPartTest.php
new file mode 100644
index 0000000..d8a08a2
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Part/DataPartTest.php
@@ -0,0 +1,149 @@
+<?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\Mime\Tests\Part;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\Header\IdentificationHeader;
+use Symfony\Component\Mime\Header\ParameterizedHeader;
+use Symfony\Component\Mime\Header\UnstructuredHeader;
+use Symfony\Component\Mime\Part\DataPart;
+
+class DataPartTest extends TestCase
+{
+    public function testConstructor()
+    {
+        $p = new DataPart('content');
+        $this->assertEquals('content', $p->getBody());
+        $this->assertEquals(base64_encode('content'), $p->bodyToString());
+        $this->assertEquals(base64_encode('content'), implode('', iterator_to_array($p->bodyToIterable())));
+        // bodyToIterable() can be called several times
+        $this->assertEquals(base64_encode('content'), implode('', iterator_to_array($p->bodyToIterable())));
+        $this->assertEquals('application', $p->getMediaType());
+        $this->assertEquals('octet-stream', $p->getMediaSubType());
+
+        $p = new DataPart('content', null, 'text/html');
+        $this->assertEquals('text', $p->getMediaType());
+        $this->assertEquals('html', $p->getMediaSubType());
+    }
+
+    public function testConstructorWithResource()
+    {
+        $f = fopen('php://memory', 'r+', false);
+        fwrite($f, 'content');
+        rewind($f);
+        $p = new DataPart($f);
+        $this->assertEquals('content', $p->getBody());
+        $this->assertEquals(base64_encode('content'), $p->bodyToString());
+        $this->assertEquals(base64_encode('content'), implode('', iterator_to_array($p->bodyToIterable())));
+        fclose($f);
+    }
+
+    public function testConstructorWithNonStringOrResource()
+    {
+        $this->expectException(\TypeError::class);
+        new DataPart(new \stdClass());
+    }
+
+    public function testHeaders()
+    {
+        $p = new DataPart('content');
+        $this->assertEquals(new Headers(
+            new ParameterizedHeader('Content-Type', 'application/octet-stream'),
+            new UnstructuredHeader('Content-Transfer-Encoding', 'base64'),
+            new ParameterizedHeader('Content-Disposition', 'attachment')
+        ), $p->getPreparedHeaders());
+
+        $p = new DataPart('content', 'photo.jpg', 'text/html');
+        $this->assertEquals(new Headers(
+            new ParameterizedHeader('Content-Type', 'text/html', ['name' => 'photo.jpg']),
+            new UnstructuredHeader('Content-Transfer-Encoding', 'base64'),
+            new ParameterizedHeader('Content-Disposition', 'attachment', ['name' => 'photo.jpg', 'filename' => 'photo.jpg'])
+        ), $p->getPreparedHeaders());
+    }
+
+    public function testAsInline()
+    {
+        $p = new DataPart('content', 'photo.jpg', 'text/html');
+        $p->asInline();
+        $this->assertEquals(new Headers(
+            new ParameterizedHeader('Content-Type', 'text/html', ['name' => 'photo.jpg']),
+            new UnstructuredHeader('Content-Transfer-Encoding', 'base64'),
+            new ParameterizedHeader('Content-Disposition', 'inline', ['name' => 'photo.jpg', 'filename' => 'photo.jpg'])
+        ), $p->getPreparedHeaders());
+    }
+
+    public function testAsInlineWithCID()
+    {
+        $p = new DataPart('content', 'photo.jpg', 'text/html');
+        $p->asInline();
+        $cid = $p->getContentId();
+        $this->assertEquals(new Headers(
+            new ParameterizedHeader('Content-Type', 'text/html', ['name' => 'photo.jpg']),
+            new UnstructuredHeader('Content-Transfer-Encoding', 'base64'),
+            new ParameterizedHeader('Content-Disposition', 'inline', ['name' => 'photo.jpg', 'filename' => 'photo.jpg']),
+            new IdentificationHeader('Content-ID', $cid)
+        ), $p->getPreparedHeaders());
+    }
+
+    public function testFromPath()
+    {
+        $p = DataPart::fromPath($file = __DIR__.'/../Fixtures/mimetypes/test.gif');
+        $content = file_get_contents($file);
+        $this->assertEquals($content, $p->getBody());
+        $this->assertEquals(base64_encode($content), $p->bodyToString());
+        $this->assertEquals(base64_encode($content), implode('', iterator_to_array($p->bodyToIterable())));
+        $this->assertEquals('image', $p->getMediaType());
+        $this->assertEquals('gif', $p->getMediaSubType());
+        $this->assertEquals(new Headers(
+            new ParameterizedHeader('Content-Type', 'image/gif', ['name' => 'test.gif']),
+            new UnstructuredHeader('Content-Transfer-Encoding', 'base64'),
+            new ParameterizedHeader('Content-Disposition', 'attachment', ['name' => 'test.gif', 'filename' => 'test.gif'])
+        ), $p->getPreparedHeaders());
+    }
+
+    public function testFromPathWithMeta()
+    {
+        $p = DataPart::fromPath($file = __DIR__.'/../Fixtures/mimetypes/test.gif', 'photo.gif', 'image/jpeg');
+        $content = file_get_contents($file);
+        $this->assertEquals($content, $p->getBody());
+        $this->assertEquals(base64_encode($content), $p->bodyToString());
+        $this->assertEquals(base64_encode($content), implode('', iterator_to_array($p->bodyToIterable())));
+        $this->assertEquals('image', $p->getMediaType());
+        $this->assertEquals('jpeg', $p->getMediaSubType());
+        $this->assertEquals(new Headers(
+            new ParameterizedHeader('Content-Type', 'image/jpeg', ['name' => 'photo.gif']),
+            new UnstructuredHeader('Content-Transfer-Encoding', 'base64'),
+            new ParameterizedHeader('Content-Disposition', 'attachment', ['name' => 'photo.gif', 'filename' => 'photo.gif'])
+        ), $p->getPreparedHeaders());
+    }
+
+    public function testHasContentId()
+    {
+        $p = new DataPart('content');
+        $this->assertFalse($p->hasContentId());
+        $p->getContentId();
+        $this->assertTrue($p->hasContentId());
+    }
+
+    public function testSerialize()
+    {
+        $r = fopen('php://memory', 'r+', false);
+        fwrite($r, 'Text content');
+        rewind($r);
+
+        $p = new DataPart($r);
+        $p->getHeaders()->addTextHeader('foo', 'bar');
+        $expected = clone $p;
+        $this->assertEquals($expected->toString(), unserialize(serialize($p))->toString());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Part/MessagePartTest.php b/vendor/symfony/mime/Tests/Part/MessagePartTest.php
new file mode 100644
index 0000000..21a4eb0
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Part/MessagePartTest.php
@@ -0,0 +1,42 @@
+<?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\Mime\Tests\Part;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Email;
+use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\Header\ParameterizedHeader;
+use Symfony\Component\Mime\Header\UnstructuredHeader;
+use Symfony\Component\Mime\Part\MessagePart;
+
+class MessagePartTest extends TestCase
+{
+    public function testConstructor()
+    {
+        $p = new MessagePart((new Email())->from('fabien@symfony.com')->text('content'));
+        $this->assertStringContainsString('content', $p->getBody());
+        $this->assertStringContainsString('content', $p->bodyToString());
+        $this->assertStringContainsString('content', implode('', iterator_to_array($p->bodyToIterable())));
+        $this->assertEquals('message', $p->getMediaType());
+        $this->assertEquals('rfc822', $p->getMediaSubType());
+    }
+
+    public function testHeaders()
+    {
+        $p = new MessagePart((new Email())->from('fabien@symfony.com')->text('content')->subject('Subject'));
+        $this->assertEquals(new Headers(
+            new ParameterizedHeader('Content-Type', 'message/rfc822', ['name' => 'Subject.eml']),
+            new UnstructuredHeader('Content-Transfer-Encoding', 'base64'),
+            new ParameterizedHeader('Content-Disposition', 'attachment', ['name' => 'Subject.eml', 'filename' => 'Subject.eml'])
+        ), $p->getPreparedHeaders());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Part/Multipart/AlternativePartTest.php b/vendor/symfony/mime/Tests/Part/Multipart/AlternativePartTest.php
new file mode 100644
index 0000000..2dbc131
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Part/Multipart/AlternativePartTest.php
@@ -0,0 +1,25 @@
+<?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\Mime\Tests\Part\Multipart;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Part\Multipart\AlternativePart;
+
+class AlternativePartTest extends TestCase
+{
+    public function testConstructor()
+    {
+        $a = new AlternativePart();
+        $this->assertEquals('multipart', $a->getMediaType());
+        $this->assertEquals('alternative', $a->getMediaSubtype());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Part/Multipart/DigestPartTest.php b/vendor/symfony/mime/Tests/Part/Multipart/DigestPartTest.php
new file mode 100644
index 0000000..82738ef
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Part/Multipart/DigestPartTest.php
@@ -0,0 +1,28 @@
+<?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\Mime\Tests\Part\Multipart;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Message;
+use Symfony\Component\Mime\Part\MessagePart;
+use Symfony\Component\Mime\Part\Multipart\DigestPart;
+
+class DigestPartTest extends TestCase
+{
+    public function testConstructor()
+    {
+        $r = new DigestPart($a = new MessagePart(new Message()), $b = new MessagePart(new Message()));
+        $this->assertEquals('multipart', $r->getMediaType());
+        $this->assertEquals('digest', $r->getMediaSubtype());
+        $this->assertEquals([$a, $b], $r->getParts());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Part/Multipart/FormDataPartTest.php b/vendor/symfony/mime/Tests/Part/Multipart/FormDataPartTest.php
new file mode 100644
index 0000000..71a03e6
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Part/Multipart/FormDataPartTest.php
@@ -0,0 +1,66 @@
+<?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\Mime\Tests\Part\Multipart;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Part\DataPart;
+use Symfony\Component\Mime\Part\Multipart\FormDataPart;
+use Symfony\Component\Mime\Part\TextPart;
+
+class FormDataPartTest extends TestCase
+{
+    public function testConstructor()
+    {
+        $r = new \ReflectionProperty(TextPart::class, 'encoding');
+        $r->setAccessible(true);
+
+        $b = new TextPart('content');
+        $c = DataPart::fromPath($file = __DIR__.'/../../Fixtures/mimetypes/test.gif');
+        $f = new FormDataPart([
+            'foo' => $content = 'very very long content that will not be cut even if the length is way more than 76 characters, ok?',
+            'bar' => clone $b,
+            'baz' => clone $c,
+        ]);
+        $this->assertEquals('multipart', $f->getMediaType());
+        $this->assertEquals('form-data', $f->getMediaSubtype());
+        $t = new TextPart($content, 'utf-8', 'plain', '8bit');
+        $t->setDisposition('form-data');
+        $t->setName('foo');
+        $t->getHeaders()->setMaxLineLength(PHP_INT_MAX);
+        $b->setDisposition('form-data');
+        $b->setName('bar');
+        $b->getHeaders()->setMaxLineLength(PHP_INT_MAX);
+        $r->setValue($b, '8bit');
+        $c->setDisposition('form-data');
+        $c->setName('baz');
+        $c->getHeaders()->setMaxLineLength(PHP_INT_MAX);
+        $r->setValue($c, '8bit');
+        $this->assertEquals([$t, $b, $c], $f->getParts());
+    }
+
+    public function testToString()
+    {
+        $p = DataPart::fromPath($file = __DIR__.'/../../Fixtures/mimetypes/test.gif');
+        $this->assertEquals(base64_encode(file_get_contents($file)), $p->bodyToString());
+    }
+
+    public function testContentLineLength()
+    {
+        $f = new FormDataPart([
+            'foo' => new DataPart($foo = str_repeat('foo', 1000), 'foo.txt', 'text/plain'),
+            'bar' => $bar = str_repeat('bar', 1000),
+        ]);
+        $parts = $f->getParts();
+        $this->assertEquals($foo, $parts[0]->bodyToString());
+        $this->assertEquals($bar, $parts[1]->bodyToString());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Part/Multipart/MixedPartTest.php b/vendor/symfony/mime/Tests/Part/Multipart/MixedPartTest.php
new file mode 100644
index 0000000..3ff02ee
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Part/Multipart/MixedPartTest.php
@@ -0,0 +1,25 @@
+<?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\Mime\Tests\Part\Multipart;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Part\Multipart\MixedPart;
+
+class MixedPartTest extends TestCase
+{
+    public function testConstructor()
+    {
+        $a = new MixedPart();
+        $this->assertEquals('multipart', $a->getMediaType());
+        $this->assertEquals('mixed', $a->getMediaSubtype());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Part/Multipart/RelatedPartTest.php b/vendor/symfony/mime/Tests/Part/Multipart/RelatedPartTest.php
new file mode 100644
index 0000000..2a5a7be
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Part/Multipart/RelatedPartTest.php
@@ -0,0 +1,30 @@
+<?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\Mime\Tests\Part\Multipart;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Part\Multipart\RelatedPart;
+use Symfony\Component\Mime\Part\TextPart;
+
+class RelatedPartTest extends TestCase
+{
+    public function testConstructor()
+    {
+        $r = new RelatedPart($a = new TextPart('content'), $b = new TextPart('HTML content', 'utf-8', 'html'), $c = new TextPart('HTML content again', 'utf-8', 'html'));
+        $this->assertEquals('multipart', $r->getMediaType());
+        $this->assertEquals('related', $r->getMediaSubtype());
+        $this->assertEquals([$a, $b, $c], $r->getParts());
+        $this->assertFalse($a->getHeaders()->has('Content-ID'));
+        $this->assertTrue($b->getHeaders()->has('Content-ID'));
+        $this->assertTrue($c->getHeaders()->has('Content-ID'));
+    }
+}
diff --git a/vendor/symfony/mime/Tests/Part/TextPartTest.php b/vendor/symfony/mime/Tests/Part/TextPartTest.php
new file mode 100644
index 0000000..c3818b8
--- /dev/null
+++ b/vendor/symfony/mime/Tests/Part/TextPartTest.php
@@ -0,0 +1,92 @@
+<?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\Mime\Tests\Part;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\Header\ParameterizedHeader;
+use Symfony\Component\Mime\Header\UnstructuredHeader;
+use Symfony\Component\Mime\Part\TextPart;
+
+class TextPartTest extends TestCase
+{
+    public function testConstructor()
+    {
+        $p = new TextPart('content');
+        $this->assertEquals('content', $p->getBody());
+        $this->assertEquals('content', $p->bodyToString());
+        $this->assertEquals('content', implode('', iterator_to_array($p->bodyToIterable())));
+        // bodyToIterable() can be called several times
+        $this->assertEquals('content', implode('', iterator_to_array($p->bodyToIterable())));
+        $this->assertEquals('text', $p->getMediaType());
+        $this->assertEquals('plain', $p->getMediaSubType());
+
+        $p = new TextPart('content', null, 'html');
+        $this->assertEquals('html', $p->getMediaSubType());
+    }
+
+    public function testConstructorWithResource()
+    {
+        $f = fopen('php://memory', 'r+', false);
+        fwrite($f, 'content');
+        rewind($f);
+        $p = new TextPart($f);
+        $this->assertEquals('content', $p->getBody());
+        $this->assertEquals('content', $p->bodyToString());
+        $this->assertEquals('content', implode('', iterator_to_array($p->bodyToIterable())));
+        fclose($f);
+    }
+
+    public function testConstructorWithNonStringOrResource()
+    {
+        $this->expectException(\TypeError::class);
+        new TextPart(new \stdClass());
+    }
+
+    public function testHeaders()
+    {
+        $p = new TextPart('content');
+        $this->assertEquals(new Headers(
+            new ParameterizedHeader('Content-Type', 'text/plain', ['charset' => 'utf-8']),
+            new UnstructuredHeader('Content-Transfer-Encoding', 'quoted-printable')
+        ), $p->getPreparedHeaders());
+
+        $p = new TextPart('content', 'iso-8859-1');
+        $this->assertEquals(new Headers(
+            new ParameterizedHeader('Content-Type', 'text/plain', ['charset' => 'iso-8859-1']),
+            new UnstructuredHeader('Content-Transfer-Encoding', 'quoted-printable')
+        ), $p->getPreparedHeaders());
+    }
+
+    public function testEncoding()
+    {
+        $p = new TextPart('content', 'utf-8', 'plain', 'base64');
+        $this->assertEquals(base64_encode('content'), $p->bodyToString());
+        $this->assertEquals(base64_encode('content'), implode('', iterator_to_array($p->bodyToIterable())));
+        $this->assertEquals(new Headers(
+            new ParameterizedHeader('Content-Type', 'text/plain', ['charset' => 'utf-8']),
+            new UnstructuredHeader('Content-Transfer-Encoding', 'base64')
+        ), $p->getPreparedHeaders());
+    }
+
+    public function testSerialize()
+    {
+        $r = fopen('php://memory', 'r+', false);
+        fwrite($r, 'Text content');
+        rewind($r);
+
+        $p = new TextPart($r);
+        $p->getHeaders()->addTextHeader('foo', 'bar');
+        $expected = clone $p;
+        $this->assertEquals($expected->toString(), unserialize(serialize($p))->toString());
+    }
+}
diff --git a/vendor/symfony/mime/Tests/RawMessageTest.php b/vendor/symfony/mime/Tests/RawMessageTest.php
new file mode 100644
index 0000000..5d1588b
--- /dev/null
+++ b/vendor/symfony/mime/Tests/RawMessageTest.php
@@ -0,0 +1,35 @@
+<?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\Mime\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mime\RawMessage;
+
+class RawMessageTest extends TestCase
+{
+    public function testToString()
+    {
+        $message = new RawMessage('string');
+        $this->assertEquals('string', $message->toString());
+        $this->assertEquals('string', implode('', iterator_to_array($message->toIterable())));
+        // calling methods more than once work
+        $this->assertEquals('string', $message->toString());
+        $this->assertEquals('string', implode('', iterator_to_array($message->toIterable())));
+
+        $message = new RawMessage(new \ArrayObject(['some', ' ', 'string']));
+        $this->assertEquals('some string', $message->toString());
+        $this->assertEquals('some string', implode('', iterator_to_array($message->toIterable())));
+        // calling methods more than once work
+        $this->assertEquals('some string', $message->toString());
+        $this->assertEquals('some string', implode('', iterator_to_array($message->toIterable())));
+    }
+}
diff --git a/vendor/symfony/mime/composer.json b/vendor/symfony/mime/composer.json
new file mode 100644
index 0000000..0697e56
--- /dev/null
+++ b/vendor/symfony/mime/composer.json
@@ -0,0 +1,39 @@
+{
+    "name": "symfony/mime",
+    "type": "library",
+    "description": "A library to manipulate MIME messages",
+    "keywords": ["mime", "mime-type"],
+    "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.1.3",
+        "symfony/polyfill-intl-idn": "^1.10",
+        "symfony/polyfill-mbstring": "^1.0"
+    },
+    "require-dev": {
+        "egulias/email-validator": "^2.1.10",
+        "symfony/dependency-injection": "~3.4|^4.1"
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Component\\Mime\\": "" },
+        "exclude-from-classmap": [
+            "/Tests/"
+        ]
+    },
+    "minimum-stability": "dev",
+    "extra": {
+        "branch-alias": {
+            "dev-master": "4.3-dev"
+        }
+    }
+}
diff --git a/vendor/symfony/mime/phpunit.xml.dist b/vendor/symfony/mime/phpunit.xml.dist
new file mode 100644
index 0000000..a1c68d0
--- /dev/null
+++ b/vendor/symfony/mime/phpunit.xml.dist
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
+         backupGlobals="false"
+         colors="true"
+         bootstrap="vendor/autoload.php"
+         failOnRisky="true"
+         failOnWarning="true"
+>
+    <php>
+        <ini name="error_reporting" value="-1" />
+    </php>
+
+    <testsuites>
+        <testsuite name="Symfony MIME Component Test Suite">
+            <directory>./Tests/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory>./</directory>
+            <exclude>
+                <directory>./Resources</directory>
+                <directory>./Tests</directory>
+                <directory>./vendor</directory>
+            </exclude>
+        </whitelist>
+    </filter>
+</phpunit>
diff --git a/vendor/symfony/polyfill-intl-idn/Idn.php b/vendor/symfony/polyfill-intl-idn/Idn.php
new file mode 100644
index 0000000..adb718d
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/Idn.php
@@ -0,0 +1,283 @@
+<?php
+
+/*
+ * Copyright (c) 2014 TrueServer B.V.
+ *
+ * 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.
+ *
+ * Originally forked from
+ * https://github.com/true/php-punycode/blob/v2.1.1/src/Punycode.php
+ */
+
+namespace Symfony\Polyfill\Intl\Idn;
+
+/**
+ * Partial intl implementation in pure PHP.
+ *
+ * Implemented:
+ * - idn_to_ascii - Convert domain name to IDNA ASCII form
+ * - idn_to_utf8  - Convert domain name from IDNA ASCII to Unicode
+ *
+ * @author Renan Gonçalves <renan.saddam@gmail.com>
+ * @author Sebastian Kroczek <sk@xbug.de>
+ * @author Dmitry Lukashin <dmitry@lukashin.ru>
+ * @author Laurent Bassin <laurent@bassin.info>
+ *
+ * @internal
+ */
+final class Idn
+{
+    const INTL_IDNA_VARIANT_2003 = 0;
+    const INTL_IDNA_VARIANT_UTS46 = 1;
+
+    private static $encodeTable = array(
+        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+        'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+        'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+    );
+
+    private static $decodeTable = array(
+        'a' => 0, 'b' => 1, 'c' => 2, 'd' => 3, 'e' => 4, 'f' => 5,
+        'g' => 6, 'h' => 7, 'i' => 8, 'j' => 9, 'k' => 10, 'l' => 11,
+        'm' => 12, 'n' => 13, 'o' => 14, 'p' => 15, 'q' => 16, 'r' => 17,
+        's' => 18, 't' => 19, 'u' => 20, 'v' => 21, 'w' => 22, 'x' => 23,
+        'y' => 24, 'z' => 25, '0' => 26, '1' => 27, '2' => 28, '3' => 29,
+        '4' => 30, '5' => 31, '6' => 32, '7' => 33, '8' => 34, '9' => 35,
+    );
+
+    public static function idn_to_ascii($domain, $options, $variant, &$idna_info = array())
+    {
+        if (\PHP_VERSION_ID >= 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) {
+            @trigger_error('idn_to_ascii(): INTL_IDNA_VARIANT_2003 is deprecated', E_USER_DEPRECATED);
+        }
+
+        if (self::INTL_IDNA_VARIANT_UTS46 === $variant) {
+            $domain = mb_strtolower($domain, 'utf-8');
+        }
+
+        $parts = explode('.', $domain);
+
+        foreach ($parts as $i => &$part) {
+            if ('' === $part && \count($parts) > 1 + $i) {
+                return false;
+            }
+            if (false === $part = self::encodePart($part)) {
+                return false;
+            }
+        }
+
+        $output = implode('.', $parts);
+
+        $idna_info = array(
+            'result' => \strlen($output) > 255 ? false : $output,
+            'isTransitionalDifferent' => false,
+            'errors' => 0,
+        );
+
+        return $idna_info['result'];
+    }
+
+    public static function idn_to_utf8($domain, $options, $variant, &$idna_info = array())
+    {
+        if (\PHP_VERSION_ID >= 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) {
+            @trigger_error('idn_to_utf8(): INTL_IDNA_VARIANT_2003 is deprecated', E_USER_DEPRECATED);
+        }
+
+        $parts = explode('.', $domain);
+
+        foreach ($parts as &$part) {
+            $length = \strlen($part);
+            if ($length < 1 || 63 < $length) {
+                continue;
+            }
+            if (0 !== strpos($part, 'xn--')) {
+                continue;
+            }
+
+            $part = substr($part, 4);
+            $part = self::decodePart($part);
+        }
+
+        $output = implode('.', $parts);
+
+        $idna_info = array(
+            'result' => \strlen($output) > 255 ? false : $output,
+            'isTransitionalDifferent' => false,
+            'errors' => 0,
+        );
+
+        return $idna_info['result'];
+    }
+
+    private static function encodePart($input)
+    {
+        $codePoints = self::listCodePoints($input);
+
+        $n = 128;
+        $bias = 72;
+        $delta = 0;
+        $h = $b = \count($codePoints['basic']);
+
+        $output = '';
+        foreach ($codePoints['basic'] as $code) {
+            $output .= mb_chr($code, 'utf-8');
+        }
+        if ($input === $output) {
+            return $output;
+        }
+        if ($b > 0) {
+            $output .= '-';
+        }
+
+        $codePoints['nonBasic'] = array_unique($codePoints['nonBasic']);
+        sort($codePoints['nonBasic']);
+
+        $i = 0;
+        $length = mb_strlen($input, 'utf-8');
+        while ($h < $length) {
+            $m = $codePoints['nonBasic'][$i++];
+            $delta += ($m - $n) * ($h + 1);
+            $n = $m;
+
+            foreach ($codePoints['all'] as $c) {
+                if ($c < $n || $c < 128) {
+                    ++$delta;
+                }
+                if ($c === $n) {
+                    $q = $delta;
+                    for ($k = 36;; $k += 36) {
+                        $t = self::calculateThreshold($k, $bias);
+                        if ($q < $t) {
+                            break;
+                        }
+
+                        $code = $t + (($q - $t) % (36 - $t));
+                        $output .= self::$encodeTable[$code];
+
+                        $q = ($q - $t) / (36 - $t);
+                    }
+
+                    $output .= self::$encodeTable[$q];
+                    $bias = self::adapt($delta, $h + 1, ($h === $b));
+                    $delta = 0;
+                    ++$h;
+                }
+            }
+
+            ++$delta;
+            ++$n;
+        }
+
+        $output = 'xn--'.$output;
+
+        return \strlen($output) < 1 || 63 < \strlen($output) ? false : strtolower($output);
+    }
+
+    private static function listCodePoints($input)
+    {
+        $codePoints = array(
+            'all' => array(),
+            'basic' => array(),
+            'nonBasic' => array(),
+        );
+
+        $length = mb_strlen($input, 'utf-8');
+        for ($i = 0; $i < $length; ++$i) {
+            $char = mb_substr($input, $i, 1, 'utf-8');
+            $code = mb_ord($char, 'utf-8');
+            if ($code < 128) {
+                $codePoints['all'][] = $codePoints['basic'][] = $code;
+            } else {
+                $codePoints['all'][] = $codePoints['nonBasic'][] = $code;
+            }
+        }
+
+        return $codePoints;
+    }
+
+    private static function calculateThreshold($k, $bias)
+    {
+        if ($k <= $bias + 1) {
+            return 1;
+        }
+        if ($k >= $bias + 26) {
+            return 26;
+        }
+
+        return $k - $bias;
+    }
+
+    private static function adapt($delta, $numPoints, $firstTime)
+    {
+        $delta = (int) ($firstTime ? $delta / 700 : $delta / 2);
+        $delta += (int) ($delta / $numPoints);
+
+        $k = 0;
+        while ($delta > 35 * 13) {
+            $delta = (int) ($delta / 35);
+            $k = $k + 36;
+        }
+
+        return $k + (int) (36 * $delta / ($delta + 38));
+    }
+
+    private static function decodePart($input)
+    {
+        $n = 128;
+        $i = 0;
+        $bias = 72;
+        $output = '';
+
+        $pos = strrpos($input, '-');
+        if (false !== $pos) {
+            $output = substr($input, 0, $pos++);
+        } else {
+            $pos = 0;
+        }
+
+        $outputLength = \strlen($output);
+        $inputLength = \strlen($input);
+
+        while ($pos < $inputLength) {
+            $oldi = $i;
+            $w = 1;
+
+            for ($k = 36;; $k += 36) {
+                $digit = self::$decodeTable[$input[$pos++]];
+                $i += $digit * $w;
+                $t = self::calculateThreshold($k, $bias);
+
+                if ($digit < $t) {
+                    break;
+                }
+
+                $w *= 36 - $t;
+            }
+
+            $bias = self::adapt($i - $oldi, ++$outputLength, 0 === $oldi);
+            $n = $n + (int) ($i / $outputLength);
+            $i = $i % $outputLength;
+            $output = mb_substr($output, 0, $i, 'utf-8').mb_chr($n, 'utf-8').mb_substr($output, $i, $outputLength - 1, 'utf-8');
+
+            ++$i;
+        }
+
+        return $output;
+    }
+}
diff --git a/vendor/symfony/polyfill-intl-idn/LICENSE b/vendor/symfony/polyfill-intl-idn/LICENSE
new file mode 100644
index 0000000..3f853aa
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018-2019 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.
diff --git a/vendor/symfony/polyfill-intl-idn/README.md b/vendor/symfony/polyfill-intl-idn/README.md
new file mode 100644
index 0000000..5fd8c6e
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/README.md
@@ -0,0 +1,12 @@
+Symfony Polyfill / Intl: Idn
+============================
+
+This component provides `idn_to_ascii` and `idn_to_utf8` functions to users who run php versions without the intl extension.
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/vendor/symfony/polyfill-intl-idn/bootstrap.php b/vendor/symfony/polyfill-intl-idn/bootstrap.php
new file mode 100644
index 0000000..c6e3921
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/bootstrap.php
@@ -0,0 +1,59 @@
+<?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.
+ */
+
+use Symfony\Polyfill\Intl\Idn as p;
+
+if (!function_exists('idn_to_ascii')) {
+    define('U_IDNA_PROHIBITED_ERROR', 66560);
+    define('U_IDNA_ERROR_START', 66560);
+    define('U_IDNA_UNASSIGNED_ERROR', 66561);
+    define('U_IDNA_CHECK_BIDI_ERROR', 66562);
+    define('U_IDNA_STD3_ASCII_RULES_ERROR', 66563);
+    define('U_IDNA_ACE_PREFIX_ERROR', 66564);
+    define('U_IDNA_VERIFICATION_ERROR', 66565);
+    define('U_IDNA_LABEL_TOO_LONG_ERROR', 66566);
+    define('U_IDNA_ZERO_LENGTH_LABEL_ERROR', 66567);
+    define('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR', 66568);
+    define('U_IDNA_ERROR_LIMIT', 66569);
+    define('U_STRINGPREP_PROHIBITED_ERROR', 66560);
+    define('U_STRINGPREP_UNASSIGNED_ERROR', 66561);
+    define('U_STRINGPREP_CHECK_BIDI_ERROR', 66562);
+    define('IDNA_DEFAULT', 0);
+    define('IDNA_ALLOW_UNASSIGNED', 1);
+    define('IDNA_USE_STD3_RULES', 2);
+    define('IDNA_CHECK_BIDI', 4);
+    define('IDNA_CHECK_CONTEXTJ', 8);
+    define('IDNA_NONTRANSITIONAL_TO_ASCII', 16);
+    define('IDNA_NONTRANSITIONAL_TO_UNICODE', 32);
+    define('INTL_IDNA_VARIANT_2003', 0);
+    define('INTL_IDNA_VARIANT_UTS46', 1);
+    define('IDNA_ERROR_EMPTY_LABEL', 1);
+    define('IDNA_ERROR_LABEL_TOO_LONG', 2);
+    define('IDNA_ERROR_DOMAIN_NAME_TOO_LONG', 4);
+    define('IDNA_ERROR_LEADING_HYPHEN', 8);
+    define('IDNA_ERROR_TRAILING_HYPHEN', 16);
+    define('IDNA_ERROR_HYPHEN_3_4', 32);
+    define('IDNA_ERROR_LEADING_COMBINING_MARK', 64);
+    define('IDNA_ERROR_DISALLOWED', 128);
+    define('IDNA_ERROR_PUNYCODE', 256);
+    define('IDNA_ERROR_LABEL_HAS_DOT', 512);
+    define('IDNA_ERROR_INVALID_ACE_LABEL', 1024);
+    define('IDNA_ERROR_BIDI', 2048);
+    define('IDNA_ERROR_CONTEXTJ', 4096);
+
+    if (PHP_VERSION_ID < 70400) {
+        function idn_to_ascii($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_2003, &$idna_info = array()) { return p\Idn::idn_to_ascii($domain, $options, $variant, $idna_info); }
+        function idn_to_utf8($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_2003, &$idna_info = array()) { return p\Idn::idn_to_utf8($domain, $options, $variant, $idna_info); }
+    } else {
+        function idn_to_ascii($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = array()) { return p\Idn::idn_to_ascii($domain, $options, $variant, $idna_info); }
+        function idn_to_utf8($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = array()) { return p\Idn::idn_to_utf8($domain, $options, $variant, $idna_info); }
+    }
+}
diff --git a/vendor/symfony/polyfill-intl-idn/composer.json b/vendor/symfony/polyfill-intl-idn/composer.json
new file mode 100644
index 0000000..d2e3f4b
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/composer.json
@@ -0,0 +1,36 @@
+{
+    "name": "symfony/polyfill-intl-idn",
+    "type": "library",
+    "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+    "keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "idn"],
+    "homepage": "https://symfony.com",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Laurent Bassin",
+            "email": "laurent@bassin.info"
+        },
+        {
+            "name": "Symfony Community",
+            "homepage": "https://symfony.com/contributors"
+        }
+    ],
+    "require": {
+        "php": ">=5.3.3",
+        "symfony/polyfill-mbstring": "^1.3",
+        "symfony/polyfill-php72": "^1.9"
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Polyfill\\Intl\\Idn\\": "" },
+        "files": [ "bootstrap.php" ]
+    },
+    "suggest": {
+        "ext-intl": "For best performance"
+    },
+    "minimum-stability": "dev",
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.12-dev"
+        }
+    }
+}
diff --git a/vendor/symfony/polyfill-php72/LICENSE b/vendor/symfony/polyfill-php72/LICENSE
new file mode 100644
index 0000000..4cd8bdd
--- /dev/null
+++ b/vendor/symfony/polyfill-php72/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2019 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.
diff --git a/vendor/symfony/polyfill-php72/Php72.php b/vendor/symfony/polyfill-php72/Php72.php
new file mode 100644
index 0000000..d531e84
--- /dev/null
+++ b/vendor/symfony/polyfill-php72/Php72.php
@@ -0,0 +1,216 @@
+<?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\Polyfill\Php72;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
+ *
+ * @internal
+ */
+final class Php72
+{
+    private static $hashMask;
+
+    public static function utf8_encode($s)
+    {
+        $s .= $s;
+        $len = \strlen($s);
+
+        for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) {
+            switch (true) {
+                case $s[$i] < "\x80": $s[$j] = $s[$i]; break;
+                case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break;
+                default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break;
+            }
+        }
+
+        return substr($s, 0, $j);
+    }
+
+    public static function utf8_decode($s)
+    {
+        $s = (string) $s;
+        $len = \strlen($s);
+
+        for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) {
+            switch ($s[$i] & "\xF0") {
+                case "\xC0":
+                case "\xD0":
+                    $c = (\ord($s[$i] & "\x1F") << 6) | \ord($s[++$i] & "\x3F");
+                    $s[$j] = $c < 256 ? \chr($c) : '?';
+                    break;
+
+                case "\xF0":
+                    ++$i;
+                    // no break
+
+                case "\xE0":
+                    $s[$j] = '?';
+                    $i += 2;
+                    break;
+
+                default:
+                    $s[$j] = $s[$i];
+            }
+        }
+
+        return substr($s, 0, $j);
+    }
+
+    public static function php_os_family()
+    {
+        if ('\\' === \DIRECTORY_SEPARATOR) {
+            return 'Windows';
+        }
+
+        $map = array(
+            'Darwin' => 'Darwin',
+            'DragonFly' => 'BSD',
+            'FreeBSD' => 'BSD',
+            'NetBSD' => 'BSD',
+            'OpenBSD' => 'BSD',
+            'Linux' => 'Linux',
+            'SunOS' => 'Solaris',
+        );
+
+        return isset($map[PHP_OS]) ? $map[PHP_OS] : 'Unknown';
+    }
+
+    public static function spl_object_id($object)
+    {
+        if (null === self::$hashMask) {
+            self::initHashMask();
+        }
+        if (null === $hash = spl_object_hash($object)) {
+            return;
+        }
+
+        return self::$hashMask ^ hexdec(substr($hash, 16 - \PHP_INT_SIZE, \PHP_INT_SIZE));
+    }
+
+    public static function sapi_windows_vt100_support($stream, $enable = null)
+    {
+        if (!\is_resource($stream)) {
+            trigger_error('sapi_windows_vt100_support() expects parameter 1 to be resource, '.\gettype($stream).' given', E_USER_WARNING);
+
+            return false;
+        }
+
+        $meta = stream_get_meta_data($stream);
+
+        if ('STDIO' !== $meta['stream_type']) {
+            trigger_error('sapi_windows_vt100_support() was not able to analyze the specified stream', E_USER_WARNING);
+
+            return false;
+        }
+
+        // We cannot actually disable vt100 support if it is set
+        if (false === $enable || !self::stream_isatty($stream)) {
+            return false;
+        }
+
+        // The native function does not apply to stdin
+        $meta = array_map('strtolower', $meta);
+        $stdin = 'php://stdin' === $meta['uri'] || 'php://fd/0' === $meta['uri'];
+
+        return !$stdin
+            && (false !== getenv('ANSICON')
+            || 'ON' === getenv('ConEmuANSI')
+            || 'xterm' === getenv('TERM')
+            || 'Hyper' === getenv('TERM_PROGRAM'));
+    }
+
+    public static function stream_isatty($stream)
+    {
+        if (!\is_resource($stream)) {
+            trigger_error('stream_isatty() expects parameter 1 to be resource, '.\gettype($stream).' given', E_USER_WARNING);
+
+            return false;
+        }
+
+        if ('\\' === \DIRECTORY_SEPARATOR) {
+            $stat = @fstat($stream);
+            // Check if formatted mode is S_IFCHR
+            return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
+        }
+
+        return \function_exists('posix_isatty') && @posix_isatty($stream);
+    }
+
+    private static function initHashMask()
+    {
+        $obj = (object) array();
+        self::$hashMask = -1;
+
+        // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below
+        $obFuncs = array('ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush');
+        foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) {
+            if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && \in_array($frame['function'], $obFuncs)) {
+                $frame['line'] = 0;
+                break;
+            }
+        }
+        if (!empty($frame['line'])) {
+            ob_start();
+            debug_zval_dump($obj);
+            self::$hashMask = (int) substr(ob_get_clean(), 17);
+        }
+
+        self::$hashMask ^= hexdec(substr(spl_object_hash($obj), 16 - \PHP_INT_SIZE, \PHP_INT_SIZE));
+    }
+
+    public static function mb_chr($code, $encoding = null)
+    {
+        if (0x80 > $code %= 0x200000) {
+            $s = \chr($code);
+        } elseif (0x800 > $code) {
+            $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
+        } elseif (0x10000 > $code) {
+            $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
+        } else {
+            $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
+        }
+
+        if ('UTF-8' !== $encoding) {
+            $s = mb_convert_encoding($s, $encoding, 'UTF-8');
+        }
+
+        return $s;
+    }
+
+    public static function mb_ord($s, $encoding = null)
+    {
+        if (null == $encoding) {
+            $s = mb_convert_encoding($s, 'UTF-8');
+        } elseif ('UTF-8' !== $encoding) {
+            $s = mb_convert_encoding($s, 'UTF-8', $encoding);
+        }
+
+        if (1 === \strlen($s)) {
+            return \ord($s);
+        }
+
+        $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
+        if (0xF0 <= $code) {
+            return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
+        }
+        if (0xE0 <= $code) {
+            return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
+        }
+        if (0xC0 <= $code) {
+            return (($code - 0xC0) << 6) + $s[2] - 0x80;
+        }
+
+        return $code;
+    }
+}
diff --git a/vendor/symfony/polyfill-php72/README.md b/vendor/symfony/polyfill-php72/README.md
new file mode 100644
index 0000000..82c45f7
--- /dev/null
+++ b/vendor/symfony/polyfill-php72/README.md
@@ -0,0 +1,27 @@
+Symfony Polyfill / Php72
+========================
+
+This component provides functions added to PHP 7.2 core:
+
+- [`spl_object_id`](https://php.net/spl_object_id)
+- [`stream_isatty`](https://php.net/stream_isatty)
+
+On Windows only:
+
+- [`sapi_windows_vt100_support`](https://php.net/sapi_windows_vt100_support)
+
+Moved to core since 7.2 (was in the optional XML extension earlier):
+
+- [`utf8_encode`](https://php.net/utf8_encode)
+- [`utf8_decode`](https://php.net/utf8_decode)
+
+Also, it provides a constant added to PHP 7.2:
+- [`PHP_OS_FAMILY`](http://php.net/manual/en/reserved.constants.php#constant.php-os-family)
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/vendor/symfony/polyfill-php72/bootstrap.php b/vendor/symfony/polyfill-php72/bootstrap.php
new file mode 100644
index 0000000..519056d
--- /dev/null
+++ b/vendor/symfony/polyfill-php72/bootstrap.php
@@ -0,0 +1,36 @@
+<?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.
+ */
+
+use Symfony\Polyfill\Php72 as p;
+
+if (PHP_VERSION_ID < 70200) {
+    if ('\\' === DIRECTORY_SEPARATOR && !function_exists('sapi_windows_vt100_support')) {
+        function sapi_windows_vt100_support($stream, $enable = null) { return p\Php72::sapi_windows_vt100_support($stream, $enable); }
+    }
+    if (!function_exists('stream_isatty')) {
+        function stream_isatty($stream) { return p\Php72::stream_isatty($stream); }
+    }
+    if (!function_exists('utf8_encode')) {
+        function utf8_encode($s) { return p\Php72::utf8_encode($s); }
+        function utf8_decode($s) { return p\Php72::utf8_decode($s); }
+    }
+    if (!function_exists('spl_object_id')) {
+        function spl_object_id($s) { return p\Php72::spl_object_id($s); }
+    }
+    if (!defined('PHP_OS_FAMILY')) {
+        define('PHP_OS_FAMILY', p\Php72::php_os_family());
+    }
+    if (!function_exists('mb_chr')) {
+        function mb_ord($s, $enc = null) { return p\Php72::mb_ord($s, $enc); }
+        function mb_chr($code, $enc = null) { return p\Php72::mb_chr($code, $enc); }
+        function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); }
+    }
+}
diff --git a/vendor/symfony/polyfill-php72/composer.json b/vendor/symfony/polyfill-php72/composer.json
new file mode 100644
index 0000000..5cfcbb7
--- /dev/null
+++ b/vendor/symfony/polyfill-php72/composer.json
@@ -0,0 +1,31 @@
+{
+    "name": "symfony/polyfill-php72",
+    "type": "library",
+    "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+    "keywords": ["polyfill", "shim", "compatibility", "portable"],
+    "homepage": "https://symfony.com",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Nicolas Grekas",
+            "email": "p@tchwork.com"
+        },
+        {
+            "name": "Symfony Community",
+            "homepage": "https://symfony.com/contributors"
+        }
+    ],
+    "require": {
+        "php": ">=5.3.3"
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Polyfill\\Php72\\": "" },
+        "files": [ "bootstrap.php" ]
+    },
+    "minimum-stability": "dev",
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.12-dev"
+        }
+    }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/.gitignore b/vendor/symfony/psr-http-message-bridge/.gitignore
new file mode 100644
index 0000000..027924f
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/.gitignore
@@ -0,0 +1,4 @@
+vendor/
+composer.lock
+phpunit.xml
+.php_cs.cache
diff --git a/vendor/symfony/service-contracts/.gitignore b/vendor/symfony/service-contracts/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/vendor/symfony/service-contracts/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/vendor/symfony/service-contracts/LICENSE b/vendor/symfony/service-contracts/LICENSE
new file mode 100644
index 0000000..3f853aa
--- /dev/null
+++ b/vendor/symfony/service-contracts/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018-2019 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.
diff --git a/vendor/symfony/service-contracts/README.md b/vendor/symfony/service-contracts/README.md
new file mode 100644
index 0000000..d033a43
--- /dev/null
+++ b/vendor/symfony/service-contracts/README.md
@@ -0,0 +1,9 @@
+Symfony Service Contracts
+=========================
+
+A set of abstractions extracted out of the Symfony components.
+
+Can be used to build on semantics that the Symfony components proved useful - and
+that already have battle tested implementations.
+
+See https://github.com/symfony/contracts/blob/master/README.md for more information.
diff --git a/vendor/symfony/service-contracts/ResetInterface.php b/vendor/symfony/service-contracts/ResetInterface.php
new file mode 100644
index 0000000..1af1075
--- /dev/null
+++ b/vendor/symfony/service-contracts/ResetInterface.php
@@ -0,0 +1,30 @@
+<?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\Contracts\Service;
+
+/**
+ * Provides a way to reset an object to its initial state.
+ *
+ * When calling the "reset()" method on an object, it should be put back to its
+ * initial state. This usually means clearing any internal buffers and forwarding
+ * the call to internal dependencies. All properties of the object should be put
+ * back to the same state it had when it was first ready to use.
+ *
+ * This method could be called, for example, to recycle objects that are used as
+ * services, so that they can be used to handle several requests in the same
+ * process loop (note that we advise making your services stateless instead of
+ * implementing this interface when possible.)
+ */
+interface ResetInterface
+{
+    public function reset();
+}
diff --git a/vendor/symfony/service-contracts/ServiceLocatorTrait.php b/vendor/symfony/service-contracts/ServiceLocatorTrait.php
new file mode 100644
index 0000000..4ec6eb4
--- /dev/null
+++ b/vendor/symfony/service-contracts/ServiceLocatorTrait.php
@@ -0,0 +1,122 @@
+<?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\Contracts\Service;
+
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\NotFoundExceptionInterface;
+
+/**
+ * A trait to help implement ServiceProviderInterface.
+ *
+ * @author Robin Chalas <robin.chalas@gmail.com>
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+trait ServiceLocatorTrait
+{
+    private $factories;
+    private $loading = [];
+    private $providedTypes;
+
+    /**
+     * @param callable[] $factories
+     */
+    public function __construct(array $factories)
+    {
+        $this->factories = $factories;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @return bool
+     */
+    public function has($id)
+    {
+        return isset($this->factories[$id]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get($id)
+    {
+        if (!isset($this->factories[$id])) {
+            throw $this->createNotFoundException($id);
+        }
+
+        if (isset($this->loading[$id])) {
+            $ids = array_values($this->loading);
+            $ids = \array_slice($this->loading, array_search($id, $ids));
+            $ids[] = $id;
+
+            throw $this->createCircularReferenceException($id, $ids);
+        }
+
+        $this->loading[$id] = $id;
+        try {
+            return $this->factories[$id]($this);
+        } finally {
+            unset($this->loading[$id]);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getProvidedServices(): array
+    {
+        if (null === $this->providedTypes) {
+            $this->providedTypes = [];
+
+            foreach ($this->factories as $name => $factory) {
+                if (!\is_callable($factory)) {
+                    $this->providedTypes[$name] = '?';
+                } else {
+                    $type = (new \ReflectionFunction($factory))->getReturnType();
+
+                    $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').$type->getName() : '?';
+                }
+            }
+        }
+
+        return $this->providedTypes;
+    }
+
+    private function createNotFoundException(string $id): NotFoundExceptionInterface
+    {
+        if (!$alternatives = array_keys($this->factories)) {
+            $message = 'is empty...';
+        } else {
+            $last = array_pop($alternatives);
+            if ($alternatives) {
+                $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last);
+            } else {
+                $message = sprintf('only knows about the "%s" service.', $last);
+            }
+        }
+
+        if ($this->loading) {
+            $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message);
+        } else {
+            $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message);
+        }
+
+        return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface {
+        };
+    }
+
+    private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface
+    {
+        return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface {
+        };
+    }
+}
diff --git a/vendor/symfony/service-contracts/ServiceProviderInterface.php b/vendor/symfony/service-contracts/ServiceProviderInterface.php
new file mode 100644
index 0000000..c60ad0b
--- /dev/null
+++ b/vendor/symfony/service-contracts/ServiceProviderInterface.php
@@ -0,0 +1,36 @@
+<?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\Contracts\Service;
+
+use Psr\Container\ContainerInterface;
+
+/**
+ * A ServiceProviderInterface exposes the identifiers and the types of services provided by a container.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ * @author Mateusz Sip <mateusz.sip@gmail.com>
+ */
+interface ServiceProviderInterface extends ContainerInterface
+{
+    /**
+     * Returns an associative array of service types keyed by the identifiers provided by the current container.
+     *
+     * Examples:
+     *
+     *  * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface
+     *  * ['foo' => '?'] means the container provides service name "foo" of unspecified type
+     *  * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null
+     *
+     * @return string[] The provided service types, keyed by service names
+     */
+    public function getProvidedServices(): array;
+}
diff --git a/vendor/symfony/service-contracts/ServiceSubscriberInterface.php b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php
new file mode 100644
index 0000000..8bb320f
--- /dev/null
+++ b/vendor/symfony/service-contracts/ServiceSubscriberInterface.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\Contracts\Service;
+
+/**
+ * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method.
+ *
+ * The getSubscribedServices method returns an array of service types required by such instances,
+ * optionally keyed by the service names used internally. Service types that start with an interrogation
+ * mark "?" are optional, while the other ones are mandatory service dependencies.
+ *
+ * The injected service locators SHOULD NOT allow access to any other services not specified by the method.
+ *
+ * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally.
+ * This interface does not dictate any injection method for these service locators, although constructor
+ * injection is recommended.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+interface ServiceSubscriberInterface
+{
+    /**
+     * Returns an array of service types required by such instances, optionally keyed by the service names used internally.
+     *
+     * For mandatory dependencies:
+     *
+     *  * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name
+     *    internally to fetch a service which must implement Psr\Log\LoggerInterface.
+     *  * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name
+     *    internally to fetch an iterable of Psr\Log\LoggerInterface instances.
+     *  * ['Psr\Log\LoggerInterface'] is a shortcut for
+     *  * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface']
+     *
+     * otherwise:
+     *
+     *  * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency
+     *  * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency
+     *  * ['?Psr\Log\LoggerInterface'] is a shortcut for
+     *  * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface']
+     *
+     * @return array The required service types, optionally keyed by service names
+     */
+    public static function getSubscribedServices();
+}
diff --git a/vendor/symfony/service-contracts/ServiceSubscriberTrait.php b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php
new file mode 100644
index 0000000..5d9d456
--- /dev/null
+++ b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php
@@ -0,0 +1,63 @@
+<?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\Contracts\Service;
+
+use Psr\Container\ContainerInterface;
+
+/**
+ * Implementation of ServiceSubscriberInterface that determines subscribed services from
+ * private method return types. Service ids are available as "ClassName::methodName".
+ *
+ * @author Kevin Bond <kevinbond@gmail.com>
+ */
+trait ServiceSubscriberTrait
+{
+    /** @var ContainerInterface */
+    protected $container;
+
+    public static function getSubscribedServices(): array
+    {
+        static $services;
+
+        if (null !== $services) {
+            return $services;
+        }
+
+        $services = \is_callable(['parent', __FUNCTION__]) ? parent::getSubscribedServices() : [];
+
+        foreach ((new \ReflectionClass(self::class))->getMethods() as $method) {
+            if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) {
+                continue;
+            }
+
+            if (self::class === $method->getDeclaringClass()->name && ($returnType = $method->getReturnType()) && !$returnType->isBuiltin()) {
+                $services[self::class.'::'.$method->name] = '?'.$returnType->getName();
+            }
+        }
+
+        return $services;
+    }
+
+    /**
+     * @required
+     */
+    public function setContainer(ContainerInterface $container)
+    {
+        $this->container = $container;
+
+        if (\is_callable(['parent', __FUNCTION__])) {
+            return parent::setContainer($container);
+        }
+
+        return null;
+    }
+}
diff --git a/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php
new file mode 100644
index 0000000..5ed9149
--- /dev/null
+++ b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php
@@ -0,0 +1,92 @@
+<?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\Contracts\Service\Test;
+
+use PHPUnit\Framework\TestCase;
+use Psr\Container\ContainerInterface;
+use Symfony\Contracts\Service\ServiceLocatorTrait;
+
+abstract class ServiceLocatorTest extends TestCase
+{
+    protected function getServiceLocator(array $factories)
+    {
+        return new class($factories) implements ContainerInterface {
+            use ServiceLocatorTrait;
+        };
+    }
+
+    public function testHas()
+    {
+        $locator = $this->getServiceLocator([
+            'foo' => function () { return 'bar'; },
+            'bar' => function () { return 'baz'; },
+            function () { return 'dummy'; },
+        ]);
+
+        $this->assertTrue($locator->has('foo'));
+        $this->assertTrue($locator->has('bar'));
+        $this->assertFalse($locator->has('dummy'));
+    }
+
+    public function testGet()
+    {
+        $locator = $this->getServiceLocator([
+            'foo' => function () { return 'bar'; },
+            'bar' => function () { return 'baz'; },
+        ]);
+
+        $this->assertSame('bar', $locator->get('foo'));
+        $this->assertSame('baz', $locator->get('bar'));
+    }
+
+    public function testGetDoesNotMemoize()
+    {
+        $i = 0;
+        $locator = $this->getServiceLocator([
+            'foo' => function () use (&$i) {
+                ++$i;
+
+                return 'bar';
+            },
+        ]);
+
+        $this->assertSame('bar', $locator->get('foo'));
+        $this->assertSame('bar', $locator->get('foo'));
+        $this->assertSame(2, $i);
+    }
+
+    public function testThrowsOnUndefinedInternalService()
+    {
+        if (!$this->getExpectedException()) {
+            $this->expectException('Psr\Container\NotFoundExceptionInterface');
+            $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.');
+        }
+        $locator = $this->getServiceLocator([
+            'foo' => function () use (&$locator) { return $locator->get('bar'); },
+        ]);
+
+        $locator->get('foo');
+    }
+
+    public function testThrowsOnCircularReference()
+    {
+        $this->expectException('Psr\Container\ContainerExceptionInterface');
+        $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".');
+        $locator = $this->getServiceLocator([
+            'foo' => function () use (&$locator) { return $locator->get('bar'); },
+            'bar' => function () use (&$locator) { return $locator->get('baz'); },
+            'baz' => function () use (&$locator) { return $locator->get('bar'); },
+        ]);
+
+        $locator->get('foo');
+    }
+}
diff --git a/vendor/symfony/service-contracts/composer.json b/vendor/symfony/service-contracts/composer.json
new file mode 100644
index 0000000..f4209cc
--- /dev/null
+++ b/vendor/symfony/service-contracts/composer.json
@@ -0,0 +1,34 @@
+{
+    "name": "symfony/service-contracts",
+    "type": "library",
+    "description": "Generic abstractions related to writing services",
+    "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"],
+    "homepage": "https://symfony.com",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Nicolas Grekas",
+            "email": "p@tchwork.com"
+        },
+        {
+            "name": "Symfony Community",
+            "homepage": "https://symfony.com/contributors"
+        }
+    ],
+    "require": {
+        "php": "^7.1.3",
+        "psr/container": "^1.0"
+    },
+    "suggest": {
+        "symfony/service-implementation": ""
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Contracts\\Service\\": "" }
+    },
+    "minimum-stability": "dev",
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.1-dev"
+        }
+    }
+}
diff --git a/vendor/symfony/var-exporter/.gitignore b/vendor/symfony/var-exporter/.gitignore
new file mode 100644
index 0000000..5414c2c
--- /dev/null
+++ b/vendor/symfony/var-exporter/.gitignore
@@ -0,0 +1,3 @@
+composer.lock
+phpunit.xml
+vendor/
diff --git a/vendor/symfony/var-exporter/CHANGELOG.md b/vendor/symfony/var-exporter/CHANGELOG.md
new file mode 100644
index 0000000..9aa4a8b
--- /dev/null
+++ b/vendor/symfony/var-exporter/CHANGELOG.md
@@ -0,0 +1,7 @@
+CHANGELOG
+=========
+
+4.2.0
+-----
+
+ * added the component
diff --git a/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php b/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php
new file mode 100644
index 0000000..4cebe44
--- /dev/null
+++ b/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php
@@ -0,0 +1,20 @@
+<?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\VarExporter\Exception;
+
+class ClassNotFoundException extends \Exception implements ExceptionInterface
+{
+    public function __construct(string $class, \Throwable $previous = null)
+    {
+        parent::__construct(sprintf('Class "%s" not found.', $class), 0, $previous);
+    }
+}
diff --git a/vendor/symfony/var-exporter/Exception/ExceptionInterface.php b/vendor/symfony/var-exporter/Exception/ExceptionInterface.php
new file mode 100644
index 0000000..adfaed4
--- /dev/null
+++ b/vendor/symfony/var-exporter/Exception/ExceptionInterface.php
@@ -0,0 +1,16 @@
+<?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\VarExporter\Exception;
+
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php b/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php
new file mode 100644
index 0000000..7ca4884
--- /dev/null
+++ b/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php
@@ -0,0 +1,20 @@
+<?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\VarExporter\Exception;
+
+class NotInstantiableTypeException extends \Exception implements ExceptionInterface
+{
+    public function __construct(string $type)
+    {
+        parent::__construct(sprintf('Type "%s" is not instantiable.', $type));
+    }
+}
diff --git a/vendor/symfony/var-exporter/Instantiator.php b/vendor/symfony/var-exporter/Instantiator.php
new file mode 100644
index 0000000..06abbc7
--- /dev/null
+++ b/vendor/symfony/var-exporter/Instantiator.php
@@ -0,0 +1,94 @@
+<?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\VarExporter;
+
+use Symfony\Component\VarExporter\Exception\ExceptionInterface;
+use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException;
+use Symfony\Component\VarExporter\Internal\Hydrator;
+use Symfony\Component\VarExporter\Internal\Registry;
+
+/**
+ * A utility class to create objects without calling their constructor.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+final class Instantiator
+{
+    /**
+     * Creates an object and sets its properties without calling its constructor nor any other methods.
+     *
+     * For example:
+     *
+     *     // creates an empty instance of Foo
+     *     Instantiator::instantiate(Foo::class);
+     *
+     *     // creates a Foo instance and sets one of its properties
+     *     Instantiator::instantiate(Foo::class, ['propertyName' => $propertyValue]);
+     *
+     *     // creates a Foo instance and sets a private property defined on its parent Bar class
+     *     Instantiator::instantiate(Foo::class, [], [
+     *         Bar::class => ['privateBarProperty' => $propertyValue],
+     *     ]);
+     *
+     * Instances of ArrayObject, ArrayIterator and SplObjectHash can be created
+     * by using the special "\0" property name to define their internal value:
+     *
+     *     // creates an SplObjectHash where $info1 is attached to $obj1, etc.
+     *     Instantiator::instantiate(SplObjectStorage::class, ["\0" => [$obj1, $info1, $obj2, $info2...]]);
+     *
+     *     // creates an ArrayObject populated with $inputArray
+     *     Instantiator::instantiate(ArrayObject::class, ["\0" => [$inputArray]]);
+     *
+     * @param string $class             The class of the instance to create
+     * @param array  $properties        The properties to set on the instance
+     * @param array  $privateProperties The private properties to set on the instance,
+     *                                  keyed by their declaring class
+     *
+     * @return object The created instance
+     *
+     * @throws ExceptionInterface When the instance cannot be created
+     */
+    public static function instantiate(string $class, array $properties = [], array $privateProperties = [])
+    {
+        $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);
+
+        if (Registry::$cloneable[$class]) {
+            $wrappedInstance = [clone Registry::$prototypes[$class]];
+        } elseif (Registry::$instantiableWithoutConstructor[$class]) {
+            $wrappedInstance = [$reflector->newInstanceWithoutConstructor()];
+        } elseif (null === Registry::$prototypes[$class]) {
+            throw new NotInstantiableTypeException($class);
+        } elseif ($reflector->implementsInterface('Serializable') && (\PHP_VERSION_ID < 70400 || !method_exists($class, '__unserialize'))) {
+            $wrappedInstance = [unserialize('C:'.\strlen($class).':"'.$class.'":0:{}')];
+        } else {
+            $wrappedInstance = [unserialize('O:'.\strlen($class).':"'.$class.'":0:{}')];
+        }
+
+        if ($properties) {
+            $privateProperties[$class] = isset($privateProperties[$class]) ? $properties + $privateProperties[$class] : $properties;
+        }
+
+        foreach ($privateProperties as $class => $properties) {
+            if (!$properties) {
+                continue;
+            }
+            foreach ($properties as $name => $value) {
+                // because they're also used for "unserialization", hydrators
+                // deal with array of instances, so we need to wrap values
+                $properties[$name] = [$value];
+            }
+            (Hydrator::$hydrators[$class] ?? Hydrator::getHydrator($class))($properties, $wrappedInstance);
+        }
+
+        return $wrappedInstance[0];
+    }
+}
diff --git a/vendor/symfony/var-exporter/Internal/Exporter.php b/vendor/symfony/var-exporter/Internal/Exporter.php
new file mode 100644
index 0000000..06d747e
--- /dev/null
+++ b/vendor/symfony/var-exporter/Internal/Exporter.php
@@ -0,0 +1,407 @@
+<?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\VarExporter\Internal;
+
+use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+class Exporter
+{
+    /**
+     * Prepares an array of values for VarExporter.
+     *
+     * For performance this method is public and has no type-hints.
+     *
+     * @param array             &$values
+     * @param \SplObjectStorage $objectsPool
+     * @param array             &$refsPool
+     * @param int               &$objectsCount
+     * @param bool              &$valuesAreStatic
+     *
+     * @return int
+     *
+     * @throws NotInstantiableTypeException When a value cannot be serialized
+     */
+    public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic)
+    {
+        $refs = $values;
+        foreach ($values as $k => $value) {
+            if (\is_resource($value)) {
+                throw new NotInstantiableTypeException(get_resource_type($value).' resource');
+            }
+            $refs[$k] = $objectsPool;
+
+            if ($isRef = !$valueIsStatic = $values[$k] !== $objectsPool) {
+                $values[$k] = &$value; // Break hard references to make $values completely
+                unset($value);         // independent from the original structure
+                $refs[$k] = $value = $values[$k];
+                if ($value instanceof Reference && 0 > $value->id) {
+                    $valuesAreStatic = false;
+                    ++$value->count;
+                    continue;
+                }
+                $refsPool[] = [&$refs[$k], $value, &$value];
+                $refs[$k] = $values[$k] = new Reference(-\count($refsPool), $value);
+            }
+
+            if (\is_array($value)) {
+                if ($value) {
+                    $value = self::prepare($value, $objectsPool, $refsPool, $objectsCount, $valueIsStatic);
+                }
+                goto handle_value;
+            } elseif (!\is_object($value) && !$value instanceof \__PHP_Incomplete_Class) {
+                goto handle_value;
+            }
+
+            $valueIsStatic = false;
+            if (isset($objectsPool[$value])) {
+                ++$objectsCount;
+                $value = new Reference($objectsPool[$value][0]);
+                goto handle_value;
+            }
+
+            $class = \get_class($value);
+            $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);
+
+            if ($reflector->hasMethod('__serialize')) {
+                if (!$reflector->getMethod('__serialize')->isPublic()) {
+                    throw new \Error(sprintf('Call to %s method %s::__serialize()', $reflector->getMethod('__serialize')->isProtected() ? 'protected' : 'private', $class));
+                }
+
+                if (!\is_array($properties = $value->__serialize())) {
+                    throw new \Typerror($class.'::__serialize() must return an array');
+                }
+
+                goto prepare_value;
+            }
+
+            $properties = [];
+            $sleep = null;
+            $arrayValue = (array) $value;
+            $proto = Registry::$prototypes[$class];
+
+            if (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) {
+                // ArrayIterator and ArrayObject need special care because their "flags"
+                // option changes the behavior of the (array) casting operator.
+                $properties = self::getArrayObjectProperties($value, $arrayValue, $proto);
+
+                // populates Registry::$prototypes[$class] with a new instance
+                Registry::getClassReflector($class, Registry::$instantiableWithoutConstructor[$class], Registry::$cloneable[$class]);
+            } elseif ($value instanceof \SplObjectStorage && Registry::$cloneable[$class] && null !== $proto) {
+                // By implementing Serializable, SplObjectStorage breaks
+                // internal references; let's deal with it on our own.
+                foreach (clone $value as $v) {
+                    $properties[] = $v;
+                    $properties[] = $value[$v];
+                }
+                $properties = ['SplObjectStorage' => ["\0" => $properties]];
+            } elseif ($value instanceof \Serializable || $value instanceof \__PHP_Incomplete_Class) {
+                ++$objectsCount;
+                $objectsPool[$value] = [$id = \count($objectsPool), serialize($value), [], 0];
+                $value = new Reference($id);
+                goto handle_value;
+            }
+
+            if (method_exists($class, '__sleep')) {
+                if (!\is_array($sleep = $value->__sleep())) {
+                    trigger_error('serialize(): __sleep should return an array only containing the names of instance-variables to serialize', E_USER_NOTICE);
+                    $value = null;
+                    goto handle_value;
+                }
+                foreach ($sleep as $name) {
+                    if (property_exists($value, $name) && !$reflector->hasProperty($name)) {
+                        $arrayValue[$name] = $value->$name;
+                    }
+                }
+                $sleep = array_flip($sleep);
+            }
+
+            $proto = (array) $proto;
+
+            foreach ($arrayValue as $name => $v) {
+                $i = 0;
+                $n = (string) $name;
+                if ('' === $n || "\0" !== $n[0]) {
+                    $c = 'stdClass';
+                } elseif ('*' === $n[1]) {
+                    $n = substr($n, 3);
+                    $c = $reflector->getProperty($n)->class;
+                    if ('Error' === $c) {
+                        $c = 'TypeError';
+                    } elseif ('Exception' === $c) {
+                        $c = 'ErrorException';
+                    }
+                } else {
+                    $i = strpos($n, "\0", 2);
+                    $c = substr($n, 1, $i - 1);
+                    $n = substr($n, 1 + $i);
+                }
+                if (null !== $sleep) {
+                    if (!isset($sleep[$n]) || ($i && $c !== $class)) {
+                        continue;
+                    }
+                    $sleep[$n] = false;
+                }
+                if (!\array_key_exists($name, $proto) || $proto[$name] !== $v || "\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) {
+                    $properties[$c][$n] = $v;
+                }
+            }
+            if ($sleep) {
+                foreach ($sleep as $n => $v) {
+                    if (false !== $v) {
+                        trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), E_USER_NOTICE);
+                    }
+                }
+            }
+
+            prepare_value:
+            $objectsPool[$value] = [$id = \count($objectsPool)];
+            $properties = self::prepare($properties, $objectsPool, $refsPool, $objectsCount, $valueIsStatic);
+            ++$objectsCount;
+            $objectsPool[$value] = [$id, $class, $properties, method_exists($class, '__unserialize') ? -$objectsCount : (method_exists($class, '__wakeup') ? $objectsCount : 0)];
+
+            $value = new Reference($id);
+
+            handle_value:
+            if ($isRef) {
+                unset($value); // Break the hard reference created above
+            } elseif (!$valueIsStatic) {
+                $values[$k] = $value;
+            }
+            $valuesAreStatic = $valueIsStatic && $valuesAreStatic;
+        }
+
+        return $values;
+    }
+
+    public static function export($value, $indent = '')
+    {
+        switch (true) {
+            case \is_int($value) || \is_float($value): return var_export($value, true);
+            case [] === $value: return '[]';
+            case false === $value: return 'false';
+            case true === $value: return 'true';
+            case null === $value: return 'null';
+            case '' === $value: return "''";
+        }
+
+        if ($value instanceof Reference) {
+            if (0 <= $value->id) {
+                return '$o['.$value->id.']';
+            }
+            if (!$value->count) {
+                return self::export($value->value, $indent);
+            }
+            $value = -$value->id;
+
+            return '&$r['.$value.']';
+        }
+        $subIndent = $indent.'    ';
+
+        if (\is_string($value)) {
+            $code = sprintf("'%s'", addcslashes($value, "'\\"));
+
+            $code = preg_replace_callback('/([\0\r\n]++)(.)/', function ($m) use ($subIndent) {
+                $m[1] = sprintf('\'."%s".\'', str_replace(
+                    ["\0", "\r", "\n", '\n\\'],
+                    ['\0', '\r', '\n', '\n"'."\n".$subIndent.'."\\'],
+                    $m[1]
+                ));
+
+                if ("'" === $m[2]) {
+                   return substr($m[1], 0, -2);
+                }
+
+                if ('n".\'' === substr($m[1], -4)) {
+                   return substr_replace($m[1], "\n".$subIndent.".'".$m[2], -2);
+                }
+
+               return $m[1].$m[2];
+            }, $code, -1, $count);
+
+            if ($count && 0 === strpos($code, "''.")) {
+                $code = substr($code, 3);
+            }
+
+            return $code;
+        }
+
+        if (\is_array($value)) {
+            $j = -1;
+            $code = '';
+            foreach ($value as $k => $v) {
+                $code .= $subIndent;
+                if (!\is_int($k) || 1 !== $k - $j) {
+                    $code .= self::export($k, $subIndent).' => ';
+                }
+                if (\is_int($k) && $k > $j) {
+                    $j = $k;
+                }
+                $code .= self::export($v, $subIndent).",\n";
+            }
+
+            return "[\n".$code.$indent.']';
+        }
+
+        if ($value instanceof Values) {
+            $code = $subIndent."\$r = [],\n";
+            foreach ($value->values as $k => $v) {
+                $code .= $subIndent.'$r['.$k.'] = '.self::export($v, $subIndent).",\n";
+            }
+
+            return "[\n".$code.$indent.']';
+        }
+
+        if ($value instanceof Registry) {
+            return self::exportRegistry($value, $indent, $subIndent);
+        }
+
+        if ($value instanceof Hydrator) {
+            return self::exportHydrator($value, $indent, $subIndent);
+        }
+
+        throw new \UnexpectedValueException(sprintf('Cannot export value of type "%s".', \is_object($value) ? \get_class($value) : \gettype($value)));
+    }
+
+    private static function exportRegistry(Registry $value, string $indent, string $subIndent): string
+    {
+        $code = '';
+        $serializables = [];
+        $seen = [];
+        $prototypesAccess = 0;
+        $factoriesAccess = 0;
+        $r = '\\'.Registry::class;
+        $j = -1;
+
+        foreach ($value as $k => $class) {
+            if (':' === ($class[1] ?? null)) {
+                $serializables[$k] = $class;
+                continue;
+            }
+            if (!Registry::$instantiableWithoutConstructor[$class]) {
+                if (is_subclass_of($class, 'Serializable') && !method_exists($class, '__unserialize')) {
+                    $serializables[$k] = 'C:'.\strlen($class).':"'.$class.'":0:{}';
+                } else {
+                    $serializables[$k] = 'O:'.\strlen($class).':"'.$class.'":0:{}';
+                }
+                if (is_subclass_of($class, 'Throwable')) {
+                    $eol = is_subclass_of($class, 'Error') ? "\0Error\0" : "\0Exception\0";
+                    $serializables[$k] = substr_replace($serializables[$k], '1:{s:'.(5 + \strlen($eol)).':"'.$eol.'trace";a:0:{}}', -4);
+                }
+                continue;
+            }
+            $code .= $subIndent.(1 !== $k - $j ? $k.' => ' : '');
+            $j = $k;
+            $eol = ",\n";
+            $c = '['.self::export($class).']';
+
+            if ($seen[$class] ?? false) {
+                if (Registry::$cloneable[$class]) {
+                    ++$prototypesAccess;
+                    $code .= 'clone $p'.$c;
+                } else {
+                    ++$factoriesAccess;
+                    $code .= '$f'.$c.'()';
+                }
+            } else {
+                $seen[$class] = true;
+                if (Registry::$cloneable[$class]) {
+                    $code .= 'clone ('.($prototypesAccess++ ? '$p' : '($p = &'.$r.'::$prototypes)').$c.' ?? '.$r.'::p';
+                } else {
+                    $code .= '('.($factoriesAccess++ ? '$f' : '($f = &'.$r.'::$factories)').$c.' ?? '.$r.'::f';
+                    $eol = '()'.$eol;
+                }
+                $code .= '('.substr($c, 1, -1).'))';
+            }
+            $code .= $eol;
+        }
+
+        if (1 === $prototypesAccess) {
+            $code = str_replace('($p = &'.$r.'::$prototypes)', $r.'::$prototypes', $code);
+        }
+        if (1 === $factoriesAccess) {
+            $code = str_replace('($f = &'.$r.'::$factories)', $r.'::$factories', $code);
+        }
+        if ('' !== $code) {
+            $code = "\n".$code.$indent;
+        }
+
+        if ($serializables) {
+            $code = $r.'::unserialize(['.$code.'], '.self::export($serializables, $indent).')';
+        } else {
+            $code = '['.$code.']';
+        }
+
+        return '$o = '.$code;
+    }
+
+    private static function exportHydrator(Hydrator $value, string $indent, string $subIndent): string
+    {
+        $code = '';
+        foreach ($value->properties as $class => $properties) {
+            $code .= $subIndent.'    '.self::export($class).' => '.self::export($properties, $subIndent.'    ').",\n";
+        }
+
+        $code = [
+            self::export($value->registry, $subIndent),
+            self::export($value->values, $subIndent),
+            '' !== $code ? "[\n".$code.$subIndent.']' : '[]',
+            self::export($value->value, $subIndent),
+            self::export($value->wakeups, $subIndent),
+        ];
+
+        return '\\'.\get_class($value)."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')';
+    }
+
+    /**
+     * @param \ArrayIterator|\ArrayObject $value
+     * @param \ArrayIterator|\ArrayObject $proto
+     */
+    private static function getArrayObjectProperties($value, array &$arrayValue, $proto): array
+    {
+        $reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject';
+        $reflector = Registry::$reflectors[$reflector] ?? Registry::getClassReflector($reflector);
+
+        $properties = [
+            $arrayValue,
+            $reflector->getMethod('getFlags')->invoke($value),
+            $value instanceof \ArrayObject ? $reflector->getMethod('getIteratorClass')->invoke($value) : 'ArrayIterator',
+        ];
+
+        $reflector = $reflector->getMethod('setFlags');
+        $reflector->invoke($proto, \ArrayObject::STD_PROP_LIST);
+
+        if ($properties[1] & \ArrayObject::STD_PROP_LIST) {
+            $reflector->invoke($value, 0);
+            $properties[0] = (array) $value;
+        } else {
+            $reflector->invoke($value, \ArrayObject::STD_PROP_LIST);
+            $arrayValue = (array) $value;
+        }
+        $reflector->invoke($value, $properties[1]);
+
+        if ([[], 0, 'ArrayIterator'] === $properties) {
+            $properties = [];
+        } else {
+            if ('ArrayIterator' === $properties[2]) {
+                unset($properties[2]);
+            }
+            $properties = [$reflector->class => ["\0" => $properties]];
+        }
+
+        return $properties;
+    }
+}
diff --git a/vendor/symfony/var-exporter/Internal/Hydrator.php b/vendor/symfony/var-exporter/Internal/Hydrator.php
new file mode 100644
index 0000000..364d292
--- /dev/null
+++ b/vendor/symfony/var-exporter/Internal/Hydrator.php
@@ -0,0 +1,151 @@
+<?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\VarExporter\Internal;
+
+use Symfony\Component\VarExporter\Exception\ClassNotFoundException;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+class Hydrator
+{
+    public static $hydrators = [];
+
+    public $registry;
+    public $values;
+    public $properties;
+    public $value;
+    public $wakeups;
+
+    public function __construct(?Registry $registry, ?Values $values, array $properties, $value, array $wakeups)
+    {
+        $this->registry = $registry;
+        $this->values = $values;
+        $this->properties = $properties;
+        $this->value = $value;
+        $this->wakeups = $wakeups;
+    }
+
+    public static function hydrate($objects, $values, $properties, $value, $wakeups)
+    {
+        foreach ($properties as $class => $vars) {
+            (self::$hydrators[$class] ?? self::getHydrator($class))($vars, $objects);
+        }
+        foreach ($wakeups as $k => $v) {
+            if (\is_array($v)) {
+                $objects[-$k]->__unserialize($v);
+            } else {
+                $objects[$v]->__wakeup();
+            }
+        }
+
+        return $value;
+    }
+
+    public static function getHydrator($class)
+    {
+        if ('stdClass' === $class) {
+            return self::$hydrators[$class] = static function ($properties, $objects) {
+                foreach ($properties as $name => $values) {
+                    foreach ($values as $i => $v) {
+                        $objects[$i]->$name = $v;
+                    }
+                }
+            };
+        }
+
+        if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) {
+            throw new ClassNotFoundException($class);
+        }
+        $classReflector = new \ReflectionClass($class);
+
+        if (!$classReflector->isInternal()) {
+            return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, $class);
+        }
+
+        if ($classReflector->name !== $class) {
+            return self::$hydrators[$classReflector->name] ?? self::getHydrator($classReflector->name);
+        }
+
+        switch ($class) {
+            case 'ArrayIterator':
+            case 'ArrayObject':
+                $constructor = \Closure::fromCallable([$classReflector->getConstructor(), 'invokeArgs']);
+
+                return self::$hydrators[$class] = static function ($properties, $objects) use ($constructor) {
+                    foreach ($properties as $name => $values) {
+                        if ("\0" !== $name) {
+                            foreach ($values as $i => $v) {
+                                $objects[$i]->$name = $v;
+                            }
+                        }
+                    }
+                    foreach ($properties["\0"] ?? [] as $i => $v) {
+                        $constructor($objects[$i], $v);
+                    }
+                };
+
+            case 'ErrorException':
+                return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \ErrorException {
+                });
+
+            case 'TypeError':
+                return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \Error {
+                });
+
+            case 'SplObjectStorage':
+                return self::$hydrators[$class] = static function ($properties, $objects) {
+                    foreach ($properties as $name => $values) {
+                        if ("\0" === $name) {
+                            foreach ($values as $i => $v) {
+                                for ($j = 0; $j < \count($v); ++$j) {
+                                    $objects[$i]->attach($v[$j], $v[++$j]);
+                                }
+                            }
+                            continue;
+                        }
+                        foreach ($values as $i => $v) {
+                            $objects[$i]->$name = $v;
+                        }
+                    }
+                };
+        }
+
+        $propertySetters = [];
+        foreach ($classReflector->getProperties() as $propertyReflector) {
+            if (!$propertyReflector->isStatic()) {
+                $propertyReflector->setAccessible(true);
+                $propertySetters[$propertyReflector->name] = \Closure::fromCallable([$propertyReflector, 'setValue']);
+            }
+        }
+
+        if (!$propertySetters) {
+            return self::$hydrators[$class] = self::$hydrators['stdClass'] ?? self::getHydrator('stdClass');
+        }
+
+        return self::$hydrators[$class] = static function ($properties, $objects) use ($propertySetters) {
+            foreach ($properties as $name => $values) {
+                if ($setValue = $propertySetters[$name] ?? null) {
+                    foreach ($values as $i => $v) {
+                        $setValue($objects[$i], $v);
+                    }
+                    continue;
+                }
+                foreach ($values as $i => $v) {
+                    $objects[$i]->$name = $v;
+                }
+            }
+        };
+    }
+}
diff --git a/vendor/symfony/var-exporter/Internal/Reference.php b/vendor/symfony/var-exporter/Internal/Reference.php
new file mode 100644
index 0000000..e371c07
--- /dev/null
+++ b/vendor/symfony/var-exporter/Internal/Reference.php
@@ -0,0 +1,30 @@
+<?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\VarExporter\Internal;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+class Reference
+{
+    public $id;
+    public $value;
+    public $count = 0;
+
+    public function __construct(int $id, $value = null)
+    {
+        $this->id = $id;
+        $this->value = $value;
+    }
+}
diff --git a/vendor/symfony/var-exporter/Internal/Registry.php b/vendor/symfony/var-exporter/Internal/Registry.php
new file mode 100644
index 0000000..19d91c9
--- /dev/null
+++ b/vendor/symfony/var-exporter/Internal/Registry.php
@@ -0,0 +1,136 @@
+<?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\VarExporter\Internal;
+
+use Symfony\Component\VarExporter\Exception\ClassNotFoundException;
+use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+class Registry
+{
+    public static $reflectors = [];
+    public static $prototypes = [];
+    public static $factories = [];
+    public static $cloneable = [];
+    public static $instantiableWithoutConstructor = [];
+
+    public function __construct(array $classes)
+    {
+        foreach ($classes as $i => $class) {
+            $this->$i = $class;
+        }
+    }
+
+    public static function unserialize($objects, $serializables)
+    {
+        $unserializeCallback = ini_set('unserialize_callback_func', __CLASS__.'::getClassReflector');
+
+        try {
+            foreach ($serializables as $k => $v) {
+                $objects[$k] = unserialize($v);
+            }
+        } finally {
+            ini_set('unserialize_callback_func', $unserializeCallback);
+        }
+
+        return $objects;
+    }
+
+    public static function p($class)
+    {
+        self::getClassReflector($class, true, true);
+
+        return self::$prototypes[$class];
+    }
+
+    public static function f($class)
+    {
+        $reflector = self::$reflectors[$class] ?? self::getClassReflector($class, true, false);
+
+        return self::$factories[$class] = \Closure::fromCallable([$reflector, 'newInstanceWithoutConstructor']);
+    }
+
+    public static function getClassReflector($class, $instantiableWithoutConstructor = false, $cloneable = null)
+    {
+        if (!($isClass = class_exists($class)) && !interface_exists($class, false) && !trait_exists($class, false)) {
+            throw new ClassNotFoundException($class);
+        }
+        $reflector = new \ReflectionClass($class);
+
+        if ($instantiableWithoutConstructor) {
+            $proto = $reflector->newInstanceWithoutConstructor();
+        } elseif (!$isClass || $reflector->isAbstract()) {
+            throw new NotInstantiableTypeException($class);
+        } elseif ($reflector->name !== $class) {
+            $reflector = self::$reflectors[$name = $reflector->name] ?? self::getClassReflector($name, false, $cloneable);
+            self::$cloneable[$class] = self::$cloneable[$name];
+            self::$instantiableWithoutConstructor[$class] = self::$instantiableWithoutConstructor[$name];
+            self::$prototypes[$class] = self::$prototypes[$name];
+
+            return self::$reflectors[$class] = $reflector;
+        } else {
+            try {
+                $proto = $reflector->newInstanceWithoutConstructor();
+                $instantiableWithoutConstructor = true;
+            } catch (\ReflectionException $e) {
+                $proto = $reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize') ? 'C:' : 'O:';
+                if ('C:' === $proto && !$reflector->getMethod('unserialize')->isInternal()) {
+                    $proto = null;
+                } elseif (false === $proto = @unserialize($proto.\strlen($class).':"'.$class.'":0:{}')) {
+                    throw new NotInstantiableTypeException($class);
+                }
+            }
+            if (null !== $proto && !$proto instanceof \Throwable && !$proto instanceof \Serializable && !method_exists($class, '__sleep') && (\PHP_VERSION_ID < 70400 || !method_exists($class, '__serialize'))) {
+                try {
+                    serialize($proto);
+                } catch (\Exception $e) {
+                    throw new NotInstantiableTypeException($class, $e);
+                }
+            }
+        }
+
+        if (null === $cloneable) {
+            if (($proto instanceof \Reflector || $proto instanceof \ReflectionGenerator || $proto instanceof \ReflectionType || $proto instanceof \IteratorIterator || $proto instanceof \RecursiveIteratorIterator) && (!$proto instanceof \Serializable && !method_exists($proto, '__wakeup') && (\PHP_VERSION_ID < 70400 || !method_exists($class, '__unserialize')))) {
+                throw new NotInstantiableTypeException($class);
+            }
+
+            $cloneable = $reflector->isCloneable() && !$reflector->hasMethod('__clone');
+        }
+
+        self::$cloneable[$class] = $cloneable;
+        self::$instantiableWithoutConstructor[$class] = $instantiableWithoutConstructor;
+        self::$prototypes[$class] = $proto;
+
+        if ($proto instanceof \Throwable) {
+            static $setTrace;
+
+            if (null === $setTrace) {
+                $setTrace = [
+                    new \ReflectionProperty(\Error::class, 'trace'),
+                    new \ReflectionProperty(\Exception::class, 'trace'),
+                ];
+                $setTrace[0]->setAccessible(true);
+                $setTrace[1]->setAccessible(true);
+                $setTrace[0] = \Closure::fromCallable([$setTrace[0], 'setValue']);
+                $setTrace[1] = \Closure::fromCallable([$setTrace[1], 'setValue']);
+            }
+
+            $setTrace[$proto instanceof \Exception]($proto, []);
+        }
+
+        return self::$reflectors[$class] = $reflector;
+    }
+}
diff --git a/vendor/symfony/var-exporter/Internal/Values.php b/vendor/symfony/var-exporter/Internal/Values.php
new file mode 100644
index 0000000..21ae04e
--- /dev/null
+++ b/vendor/symfony/var-exporter/Internal/Values.php
@@ -0,0 +1,27 @@
+<?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\VarExporter\Internal;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+class Values
+{
+    public $values;
+
+    public function __construct(array $values)
+    {
+        $this->values = $values;
+    }
+}
diff --git a/vendor/symfony/var-exporter/LICENSE b/vendor/symfony/var-exporter/LICENSE
new file mode 100644
index 0000000..3f853aa
--- /dev/null
+++ b/vendor/symfony/var-exporter/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018-2019 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.
diff --git a/vendor/symfony/var-exporter/README.md b/vendor/symfony/var-exporter/README.md
new file mode 100644
index 0000000..bb13960
--- /dev/null
+++ b/vendor/symfony/var-exporter/README.md
@@ -0,0 +1,38 @@
+VarExporter Component
+=====================
+
+The VarExporter component allows exporting any serializable PHP data structure to
+plain PHP code. While doing so, it preserves all the semantics associated with
+the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`,
+`__serialize`, `__unserialize`).
+
+It also provides an instantiator that allows creating and populating objects
+without calling their constructor nor any other methods.
+
+The reason to use this component *vs* `serialize()` or
+[igbinary](https://github.com/igbinary/igbinary) is performance: thanks to
+OPcache, the resulting code is significantly faster and more memory efficient
+than using `unserialize()` or `igbinary_unserialize()`.
+
+Unlike `var_export()`, this works on any serializable PHP value.
+
+It also provides a few improvements over `var_export()`/`serialize()`:
+
+ * the output is PSR-2 compatible;
+ * the output can be re-indented without messing up with `\r` or `\n` in the data
+ * missing classes throw a `ClassNotFoundException` instead of being unserialized to
+   `PHP_Incomplete_Class` objects;
+ * references involving `SplObjectStorage`, `ArrayObject` or `ArrayIterator`
+   instances are preserved;
+ * `Reflection*`, `IteratorIterator` and `RecursiveIteratorIterator` classes
+   throw an exception when being serialized (their unserialized version is broken
+   anyway, see https://bugs.php.net/76737).
+
+Resources
+---------
+
+  * [Documentation](https://symfony.com/doc/current/components/var_exporter.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)
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/abstract-parent.php b/vendor/symfony/var-exporter/Tests/Fixtures/abstract-parent.php
new file mode 100644
index 0000000..df6a067
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/abstract-parent.php
@@ -0,0 +1,20 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['Symfony\\Component\\VarExporter\\Tests\\ConcreteClass'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\ConcreteClass')),
+    ],
+    null,
+    [
+        'Symfony\\Component\\VarExporter\\Tests\\AbstractClass' => [
+            'foo' => [
+                123,
+            ],
+            'bar' => [
+                234,
+            ],
+        ],
+    ],
+    $o[0],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/array-iterator-legacy.php b/vendor/symfony/var-exporter/Tests/Fixtures/array-iterator-legacy.php
new file mode 100644
index 0000000..c595733
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/array-iterator-legacy.php
@@ -0,0 +1,22 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['ArrayIterator'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('ArrayIterator')),
+    ],
+    null,
+    [
+        'ArrayIterator' => [
+            "\0" => [
+                [
+                    [
+                        123,
+                    ],
+                    1,
+                ],
+            ],
+        ],
+    ],
+    $o[0],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/array-iterator.php b/vendor/symfony/var-exporter/Tests/Fixtures/array-iterator.php
new file mode 100644
index 0000000..ed4df00
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/array-iterator.php
@@ -0,0 +1,19 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['ArrayIterator'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('ArrayIterator')),
+    ],
+    null,
+    [],
+    $o[0],
+    [
+        [
+            1,
+            [
+                123,
+            ],
+            [],
+        ],
+    ]
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/array-object-custom-legacy.php b/vendor/symfony/var-exporter/Tests/Fixtures/array-object-custom-legacy.php
new file mode 100644
index 0000000..35303f8
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/array-object-custom-legacy.php
@@ -0,0 +1,22 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['Symfony\\Component\\VarExporter\\Tests\\MyArrayObject'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\MyArrayObject')),
+    ],
+    null,
+    [
+        'ArrayObject' => [
+            "\0" => [
+                [
+                    [
+                        234,
+                    ],
+                    1,
+                ],
+            ],
+        ],
+    ],
+    $o[0],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/array-object-custom.php b/vendor/symfony/var-exporter/Tests/Fixtures/array-object-custom.php
new file mode 100644
index 0000000..530f0d1
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/array-object-custom.php
@@ -0,0 +1,21 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['Symfony\\Component\\VarExporter\\Tests\\MyArrayObject'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\MyArrayObject')),
+    ],
+    null,
+    [],
+    $o[0],
+    [
+        [
+            1,
+            [
+                234,
+            ],
+            [
+                "\0".'Symfony\\Component\\VarExporter\\Tests\\MyArrayObject'."\0".'unused' => 123,
+            ],
+        ],
+    ]
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/array-object-legacy.php b/vendor/symfony/var-exporter/Tests/Fixtures/array-object-legacy.php
new file mode 100644
index 0000000..a461c6e
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/array-object-legacy.php
@@ -0,0 +1,29 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['ArrayObject'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('ArrayObject')),
+        clone $p['ArrayObject'],
+    ],
+    null,
+    [
+        'ArrayObject' => [
+            "\0" => [
+                [
+                    [
+                        1,
+                        $o[0],
+                    ],
+                    0,
+                ],
+            ],
+        ],
+        'stdClass' => [
+            'foo' => [
+                $o[1],
+            ],
+        ],
+    ],
+    $o[0],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/array-object.php b/vendor/symfony/var-exporter/Tests/Fixtures/array-object.php
new file mode 100644
index 0000000..e2f349e
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/array-object.php
@@ -0,0 +1,28 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['ArrayObject'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('ArrayObject')),
+        clone $p['ArrayObject'],
+    ],
+    null,
+    [],
+    $o[0],
+    [
+        [
+            0,
+            [
+                1,
+                $o[0],
+            ],
+            [
+                'foo' => $o[1],
+            ],
+        ],
+        -1 => [
+            0,
+            [],
+            [],
+        ],
+    ]
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/bool.php b/vendor/symfony/var-exporter/Tests/Fixtures/bool.php
new file mode 100644
index 0000000..438d99e
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/bool.php
@@ -0,0 +1,3 @@
+<?php
+
+return true;
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/clone.php b/vendor/symfony/var-exporter/Tests/Fixtures/clone.php
new file mode 100644
index 0000000..d344780
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/clone.php
@@ -0,0 +1,15 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        (($f = &\Symfony\Component\VarExporter\Internal\Registry::$factories)['Symfony\\Component\\VarExporter\\Tests\\MyCloneable'] ?? \Symfony\Component\VarExporter\Internal\Registry::f('Symfony\\Component\\VarExporter\\Tests\\MyCloneable'))(),
+        ($f['Symfony\\Component\\VarExporter\\Tests\\MyNotCloneable'] ?? \Symfony\Component\VarExporter\Internal\Registry::f('Symfony\\Component\\VarExporter\\Tests\\MyNotCloneable'))(),
+    ],
+    null,
+    [],
+    [
+        $o[0],
+        $o[1],
+    ],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/datetime.php b/vendor/symfony/var-exporter/Tests/Fixtures/datetime.php
new file mode 100644
index 0000000..1c91645
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/datetime.php
@@ -0,0 +1,25 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['DateTime'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('DateTime')),
+    ],
+    null,
+    [
+        'stdClass' => [
+            'date' => [
+                '1970-01-01 00:00:00.000000',
+            ],
+            'timezone_type' => [
+                1,
+            ],
+            'timezone' => [
+                '+00:00',
+            ],
+        ],
+    ],
+    $o[0],
+    [
+        1 => 0,
+    ]
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/error.php b/vendor/symfony/var-exporter/Tests/Fixtures/error.php
new file mode 100644
index 0000000..1362d0e
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/error.php
@@ -0,0 +1,30 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        (\Symfony\Component\VarExporter\Internal\Registry::$factories['Error'] ?? \Symfony\Component\VarExporter\Internal\Registry::f('Error'))(),
+    ],
+    null,
+    [
+        'TypeError' => [
+            'file' => [
+                \dirname(__DIR__).\DIRECTORY_SEPARATOR.'VarExporterTest.php',
+            ],
+            'line' => [
+                234,
+            ],
+        ],
+        'Error' => [
+            'trace' => [
+                [
+                    'file' => \dirname(__DIR__).\DIRECTORY_SEPARATOR.'VarExporterTest.php',
+                    'line' => 123,
+                ],
+            ],
+        ],
+    ],
+    $o[0],
+    [
+        1 => 0,
+    ]
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/external-references.php b/vendor/symfony/var-exporter/Tests/Fixtures/external-references.php
new file mode 100644
index 0000000..0405b59
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/external-references.php
@@ -0,0 +1,7 @@
+<?php
+
+return [
+    [
+        123,
+    ],
+];
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/final-array-iterator-legacy.php b/vendor/symfony/var-exporter/Tests/Fixtures/final-array-iterator-legacy.php
new file mode 100644
index 0000000..9bdb2b3
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/final-array-iterator-legacy.php
@@ -0,0 +1,11 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = \Symfony\Component\VarExporter\Internal\Registry::unserialize([], [
+        'C:54:"Symfony\\Component\\VarExporter\\Tests\\FinalArrayIterator":49:{a:2:{i:0;i:123;i:1;s:21:"x:i:0;a:0:{};m:a:0:{}";}}',
+    ]),
+    null,
+    [],
+    $o[0],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/final-array-iterator.php b/vendor/symfony/var-exporter/Tests/Fixtures/final-array-iterator.php
new file mode 100644
index 0000000..7e838d1
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/final-array-iterator.php
@@ -0,0 +1,17 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['Symfony\\Component\\VarExporter\\Tests\\FinalArrayIterator'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\FinalArrayIterator')),
+    ],
+    null,
+    [],
+    $o[0],
+    [
+        [
+            0,
+            [],
+            [],
+        ],
+    ]
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/final-error-legacy.php b/vendor/symfony/var-exporter/Tests/Fixtures/final-error-legacy.php
new file mode 100644
index 0000000..a020284
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/final-error-legacy.php
@@ -0,0 +1,27 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = \Symfony\Component\VarExporter\Internal\Registry::unserialize([], [
+        'O:46:"Symfony\\Component\\VarExporter\\Tests\\FinalError":1:{s:12:"'."\0".'Error'."\0".'trace";a:0:{}}',
+    ]),
+    null,
+    [
+        'TypeError' => [
+            'file' => [
+                \dirname(__DIR__).\DIRECTORY_SEPARATOR.'VarExporterTest.php',
+            ],
+            'line' => [
+                123,
+            ],
+        ],
+        'Error' => [
+            'trace' => [
+                [],
+            ],
+        ],
+    ],
+    $o[0],
+    [
+        1 => 0,
+    ]
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/final-error.php b/vendor/symfony/var-exporter/Tests/Fixtures/final-error.php
new file mode 100644
index 0000000..b2e7299
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/final-error.php
@@ -0,0 +1,27 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        (\Symfony\Component\VarExporter\Internal\Registry::$factories['Symfony\\Component\\VarExporter\\Tests\\FinalError'] ?? \Symfony\Component\VarExporter\Internal\Registry::f('Symfony\\Component\\VarExporter\\Tests\\FinalError'))(),
+    ],
+    null,
+    [
+        'TypeError' => [
+            'file' => [
+                \dirname(__DIR__).\DIRECTORY_SEPARATOR.'VarExporterTest.php',
+            ],
+            'line' => [
+                123,
+            ],
+        ],
+        'Error' => [
+            'trace' => [
+                [],
+            ],
+        ],
+    ],
+    $o[0],
+    [
+        1 => 0,
+    ]
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/final-stdclass.php b/vendor/symfony/var-exporter/Tests/Fixtures/final-stdclass.php
new file mode 100644
index 0000000..335e009
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/final-stdclass.php
@@ -0,0 +1,11 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        (\Symfony\Component\VarExporter\Internal\Registry::$factories['Symfony\\Component\\VarExporter\\Tests\\FinalStdClass'] ?? \Symfony\Component\VarExporter\Internal\Registry::f('Symfony\\Component\\VarExporter\\Tests\\FinalStdClass'))(),
+    ],
+    null,
+    [],
+    $o[0],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/foo-serializable.php b/vendor/symfony/var-exporter/Tests/Fixtures/foo-serializable.php
new file mode 100644
index 0000000..fd4e267
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/foo-serializable.php
@@ -0,0 +1,11 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = \Symfony\Component\VarExporter\Internal\Registry::unserialize([], [
+        'C:51:"Symfony\\Component\\VarExporter\\Tests\\FooSerializable":20:{a:1:{i:0;s:3:"bar";}}',
+    ]),
+    null,
+    [],
+    $o[0],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/hard-references-recursive.php b/vendor/symfony/var-exporter/Tests/Fixtures/hard-references-recursive.php
new file mode 100644
index 0000000..e62d0f2
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/hard-references-recursive.php
@@ -0,0 +1,16 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [],
+    [
+        $r = [],
+        $r[1] = [
+            &$r[1],
+        ],
+    ],
+    [],
+    [
+        &$r[1],
+    ],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/hard-references.php b/vendor/symfony/var-exporter/Tests/Fixtures/hard-references.php
new file mode 100644
index 0000000..3ddbb41
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/hard-references.php
@@ -0,0 +1,18 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['stdClass'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('stdClass')),
+    ],
+    [
+        $r = [],
+        $r[1] = $o[0],
+    ],
+    [],
+    [
+        &$r[1],
+        &$r[1],
+        $o[0],
+    ],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/incomplete-class.php b/vendor/symfony/var-exporter/Tests/Fixtures/incomplete-class.php
new file mode 100644
index 0000000..c381f52
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/incomplete-class.php
@@ -0,0 +1,11 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = \Symfony\Component\VarExporter\Internal\Registry::unserialize([], [
+        'O:20:"SomeNotExistingClass":0:{}',
+    ]),
+    null,
+    [],
+    $o[0],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/lf-ending-string.php b/vendor/symfony/var-exporter/Tests/Fixtures/lf-ending-string.php
new file mode 100644
index 0000000..f6bcf84
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/lf-ending-string.php
@@ -0,0 +1,4 @@
+<?php
+
+return '\'BOOM\''."\n"
+    .'.var_dump(123)//\'';
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/multiline-string.php b/vendor/symfony/var-exporter/Tests/Fixtures/multiline-string.php
new file mode 100644
index 0000000..5ca94ea
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/multiline-string.php
@@ -0,0 +1,7 @@
+<?php
+
+return [
+    "\0\0\r\n"
+        .'A' => 'B'."\r".'C'."\n"
+        ."\n",
+];
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/partially-indexed-array.php b/vendor/symfony/var-exporter/Tests/Fixtures/partially-indexed-array.php
new file mode 100644
index 0000000..647aa7e
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/partially-indexed-array.php
@@ -0,0 +1,8 @@
+<?php
+
+return [
+    5 => true,
+    1 => true,
+    2 => true,
+    true,
+];
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/php74-serializable.php b/vendor/symfony/var-exporter/Tests/Fixtures/php74-serializable.php
new file mode 100644
index 0000000..06cfac1
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/php74-serializable.php
@@ -0,0 +1,16 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['Symfony\\Component\\VarExporter\\Tests\\Php74Serializable'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\Php74Serializable')),
+        clone ($p['stdClass'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('stdClass')),
+    ],
+    null,
+    [],
+    $o[0],
+    [
+        [
+            $o[1],
+        ],
+    ]
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/private-constructor.php b/vendor/symfony/var-exporter/Tests/Fixtures/private-constructor.php
new file mode 100644
index 0000000..b29978b
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/private-constructor.php
@@ -0,0 +1,17 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['Symfony\\Component\\VarExporter\\Tests\\PrivateConstructor'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\PrivateConstructor')),
+    ],
+    null,
+    [
+        'stdClass' => [
+            'prop' => [
+                'bar',
+            ],
+        ],
+    ],
+    $o[0],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/private.php b/vendor/symfony/var-exporter/Tests/Fixtures/private.php
new file mode 100644
index 0000000..a9e2841
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/private.php
@@ -0,0 +1,26 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['Symfony\\Component\\VarExporter\\Tests\\MyPrivateValue'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\MyPrivateValue')),
+        clone ($p['Symfony\\Component\\VarExporter\\Tests\\MyPrivateChildValue'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\MyPrivateChildValue')),
+    ],
+    null,
+    [
+        'Symfony\\Component\\VarExporter\\Tests\\MyPrivateValue' => [
+            'prot' => [
+                123,
+                123,
+            ],
+            'priv' => [
+                234,
+                234,
+            ],
+        ],
+    ],
+    [
+        $o[0],
+        $o[1],
+    ],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/serializable.php b/vendor/symfony/var-exporter/Tests/Fixtures/serializable.php
new file mode 100644
index 0000000..fcf3227
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/serializable.php
@@ -0,0 +1,14 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = \Symfony\Component\VarExporter\Internal\Registry::unserialize([], [
+        'C:50:"Symfony\\Component\\VarExporter\\Tests\\MySerializable":3:{123}',
+    ]),
+    null,
+    [],
+    [
+        $o[0],
+        $o[0],
+    ],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/simple-array.php b/vendor/symfony/var-exporter/Tests/Fixtures/simple-array.php
new file mode 100644
index 0000000..50bb70d
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/simple-array.php
@@ -0,0 +1,8 @@
+<?php
+
+return [
+    123,
+    [
+        'abc',
+    ],
+];
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/spl-object-storage-legacy.php b/vendor/symfony/var-exporter/Tests/Fixtures/spl-object-storage-legacy.php
new file mode 100644
index 0000000..5e854a4
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/spl-object-storage-legacy.php
@@ -0,0 +1,21 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['SplObjectStorage'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('SplObjectStorage')),
+        clone ($p['stdClass'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('stdClass')),
+    ],
+    null,
+    [
+        'SplObjectStorage' => [
+            "\0" => [
+                [
+                    $o[1],
+                    345,
+                ],
+            ],
+        ],
+    ],
+    $o[0],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/spl-object-storage.php b/vendor/symfony/var-exporter/Tests/Fixtures/spl-object-storage.php
new file mode 100644
index 0000000..023a75f
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/spl-object-storage.php
@@ -0,0 +1,20 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['SplObjectStorage'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('SplObjectStorage')),
+        clone ($p['stdClass'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('stdClass')),
+    ],
+    null,
+    [],
+    $o[0],
+    [
+        [
+            [
+                $o[1],
+                345,
+            ],
+            [],
+        ],
+    ]
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/var-on-sleep.php b/vendor/symfony/var-exporter/Tests/Fixtures/var-on-sleep.php
new file mode 100644
index 0000000..9fd44bd
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/var-on-sleep.php
@@ -0,0 +1,17 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['Symfony\\Component\\VarExporter\\Tests\\GoodNight'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\GoodNight')),
+    ],
+    null,
+    [
+        'stdClass' => [
+            'good' => [
+                'night',
+            ],
+        ],
+    ],
+    $o[0],
+    []
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/wakeup-refl.php b/vendor/symfony/var-exporter/Tests/Fixtures/wakeup-refl.php
new file mode 100644
index 0000000..1fee610
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/wakeup-refl.php
@@ -0,0 +1,13 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['Symfony\\Component\\VarExporter\\Tests\\MyWakeup'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\MyWakeup')),
+    ],
+    null,
+    [],
+    $o[0],
+    [
+        1 => 0,
+    ]
+);
diff --git a/vendor/symfony/var-exporter/Tests/Fixtures/wakeup.php b/vendor/symfony/var-exporter/Tests/Fixtures/wakeup.php
new file mode 100644
index 0000000..89d4ceb
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/Fixtures/wakeup.php
@@ -0,0 +1,25 @@
+<?php
+
+return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
+    $o = [
+        clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['Symfony\\Component\\VarExporter\\Tests\\MyWakeup'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\MyWakeup')),
+        clone $p['Symfony\\Component\\VarExporter\\Tests\\MyWakeup'],
+    ],
+    null,
+    [
+        'stdClass' => [
+            'sub' => [
+                $o[1],
+                123,
+            ],
+            'baz' => [
+                1 => 123,
+            ],
+        ],
+    ],
+    $o[0],
+    [
+        1 => 1,
+        0,
+    ]
+);
diff --git a/vendor/symfony/var-exporter/Tests/InstantiatorTest.php b/vendor/symfony/var-exporter/Tests/InstantiatorTest.php
new file mode 100644
index 0000000..3da6023
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/InstantiatorTest.php
@@ -0,0 +1,73 @@
+<?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\VarExporter\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarExporter\Instantiator;
+
+class InstantiatorTest extends TestCase
+{
+    public function testNotFoundClass()
+    {
+        $this->expectException('Symfony\Component\VarExporter\Exception\ClassNotFoundException');
+        $this->expectExceptionMessage('Class "SomeNotExistingClass" not found.');
+        Instantiator::instantiate('SomeNotExistingClass');
+    }
+
+    /**
+     * @dataProvider provideFailingInstantiation
+     */
+    public function testFailingInstantiation(string $class)
+    {
+        $this->expectException('Symfony\Component\VarExporter\Exception\NotInstantiableTypeException');
+        $this->expectExceptionMessageRegExp('/Type ".*" is not instantiable\./');
+        Instantiator::instantiate($class);
+    }
+
+    public function provideFailingInstantiation()
+    {
+        yield ['ReflectionClass'];
+        yield ['SplHeap'];
+        yield ['Throwable'];
+        yield ['Closure'];
+        yield ['SplFileInfo'];
+    }
+
+    public function testInstantiate()
+    {
+        $this->assertEquals((object) ['p' => 123], Instantiator::instantiate('stdClass', ['p' => 123]));
+        $this->assertEquals((object) ['p' => 123], Instantiator::instantiate('STDcLASS', ['p' => 123]));
+        $this->assertEquals(new \ArrayObject([123]), Instantiator::instantiate(\ArrayObject::class, ["\0" => [[123]]]));
+
+        $expected = [
+            "\0".__NAMESPACE__."\Bar\0priv" => 123,
+            "\0".__NAMESPACE__."\Foo\0priv" => 234,
+        ];
+
+        $this->assertSame($expected, (array) Instantiator::instantiate(Bar::class, ['priv' => 123], [Foo::class => ['priv' => 234]]));
+
+        $e = Instantiator::instantiate('Exception', ['foo' => 123, 'trace' => [234]]);
+
+        $this->assertSame(123, $e->foo);
+        $this->assertSame([234], $e->getTrace());
+    }
+}
+
+class Foo
+{
+    private $priv;
+}
+
+class Bar extends Foo
+{
+    private $priv;
+}
diff --git a/vendor/symfony/var-exporter/Tests/VarExporterTest.php b/vendor/symfony/var-exporter/Tests/VarExporterTest.php
new file mode 100644
index 0000000..1e328d8
--- /dev/null
+++ b/vendor/symfony/var-exporter/Tests/VarExporterTest.php
@@ -0,0 +1,428 @@
+<?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\VarExporter\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+use Symfony\Component\VarExporter\Internal\Registry;
+use Symfony\Component\VarExporter\VarExporter;
+
+class VarExporterTest extends TestCase
+{
+    use VarDumperTestTrait;
+
+    public function testPhpIncompleteClassesAreForbidden()
+    {
+        $this->expectException('Symfony\Component\VarExporter\Exception\ClassNotFoundException');
+        $this->expectExceptionMessage('Class "SomeNotExistingClass" not found.');
+        $unserializeCallback = ini_set('unserialize_callback_func', 'var_dump');
+        try {
+            Registry::unserialize([], ['O:20:"SomeNotExistingClass":0:{}']);
+        } finally {
+            $this->assertSame('var_dump', ini_set('unserialize_callback_func', $unserializeCallback));
+        }
+    }
+
+    /**
+     * @dataProvider provideFailingSerialization
+     */
+    public function testFailingSerialization($value)
+    {
+        $this->expectException('Symfony\Component\VarExporter\Exception\NotInstantiableTypeException');
+        $this->expectExceptionMessageRegExp('/Type ".*" is not instantiable\./');
+        $expectedDump = $this->getDump($value);
+        try {
+            VarExporter::export($value);
+        } finally {
+            $this->assertDumpEquals(rtrim($expectedDump), $value);
+        }
+    }
+
+    public function provideFailingSerialization()
+    {
+        yield [hash_init('md5')];
+        yield [new \ReflectionClass('stdClass')];
+        yield [(new \ReflectionFunction(function (): int {}))->getReturnType()];
+        yield [new \ReflectionGenerator((function () { yield 123; })())];
+        yield [function () {}];
+        yield [function () { yield 123; }];
+        yield [new \SplFileInfo(__FILE__)];
+        yield [$h = fopen(__FILE__, 'r')];
+        yield [[$h]];
+
+        $a = new class() {
+        };
+
+        yield [$a];
+
+        $a = [null, $h];
+        $a[0] = &$a;
+
+        yield [$a];
+    }
+
+    /**
+     * @dataProvider provideExport
+     */
+    public function testExport(string $testName, $value, bool $staticValueExpected = false)
+    {
+        $dumpedValue = $this->getDump($value);
+        $isStaticValue = true;
+        $marshalledValue = VarExporter::export($value, $isStaticValue);
+
+        $this->assertSame($staticValueExpected, $isStaticValue);
+        if ('var-on-sleep' !== $testName && 'php74-serializable' !== $testName) {
+            $this->assertDumpEquals($dumpedValue, $value);
+        }
+
+        $dump = "<?php\n\nreturn ".$marshalledValue.";\n";
+        $dump = str_replace(var_export(__FILE__, true), "\\dirname(__DIR__).\\DIRECTORY_SEPARATOR.'VarExporterTest.php'", $dump);
+
+        if (\PHP_VERSION_ID < 70400 && \in_array($testName, ['array-object', 'array-iterator', 'array-object-custom', 'spl-object-storage', 'final-array-iterator', 'final-error'], true)) {
+            $fixtureFile = __DIR__.'/Fixtures/'.$testName.'-legacy.php';
+        } else {
+            $fixtureFile = __DIR__.'/Fixtures/'.$testName.'.php';
+        }
+        $this->assertStringEqualsFile($fixtureFile, $dump);
+
+        if ('incomplete-class' === $testName || 'external-references' === $testName) {
+            return;
+        }
+        $marshalledValue = include $fixtureFile;
+
+        if (!$isStaticValue) {
+            if ($value instanceof MyWakeup) {
+                $value->bis = null;
+            }
+            $this->assertDumpEquals($value, $marshalledValue);
+        } else {
+            $this->assertSame($value, $marshalledValue);
+        }
+    }
+
+    public function provideExport()
+    {
+        yield ['multiline-string', ["\0\0\r\nA" => "B\rC\n\n"], true];
+        yield ['lf-ending-string', "'BOOM'\n.var_dump(123)//'", true];
+
+        yield ['bool', true, true];
+        yield ['simple-array', [123, ['abc']], true];
+        yield ['partially-indexed-array', [5 => true, 1 => true, 2 => true, 6 => true], true];
+        yield ['datetime', \DateTime::createFromFormat('U', 0)];
+
+        $value = new \ArrayObject();
+        $value[0] = 1;
+        $value->foo = new \ArrayObject();
+        $value[1] = $value;
+
+        yield ['array-object', $value];
+
+        yield ['array-iterator', new \ArrayIterator([123], 1)];
+        yield ['array-object-custom', new MyArrayObject([234])];
+
+        $value = new MySerializable();
+
+        yield ['serializable', [$value, $value]];
+
+        $value = new MyWakeup();
+        $value->sub = new MyWakeup();
+        $value->sub->sub = 123;
+        $value->sub->bis = 123;
+        $value->sub->baz = 123;
+
+        yield ['wakeup', $value];
+
+        yield ['clone', [new MyCloneable(), new MyNotCloneable()]];
+
+        yield ['private', [new MyPrivateValue(123, 234), new MyPrivateChildValue(123, 234)]];
+
+        $value = new \SplObjectStorage();
+        $value[new \stdClass()] = 345;
+
+        yield ['spl-object-storage', $value];
+
+        yield ['incomplete-class', unserialize('O:20:"SomeNotExistingClass":0:{}')];
+
+        $value = [(object) []];
+        $value[1] = &$value[0];
+        $value[2] = $value[0];
+
+        yield ['hard-references', $value];
+
+        $value = [];
+        $value[0] = &$value;
+
+        yield ['hard-references-recursive', $value];
+
+        static $value = [123];
+
+        yield ['external-references', [&$value], true];
+
+        unset($value);
+
+        $value = new \Error();
+
+        $rt = new \ReflectionProperty('Error', 'trace');
+        $rt->setAccessible(true);
+        $rt->setValue($value, ['file' => __FILE__, 'line' => 123]);
+
+        $rl = new \ReflectionProperty('Error', 'line');
+        $rl->setAccessible(true);
+        $rl->setValue($value, 234);
+
+        yield ['error', $value];
+
+        yield ['var-on-sleep', new GoodNight()];
+
+        $value = new FinalError(false);
+        $rt->setValue($value, []);
+        $rl->setValue($value, 123);
+
+        yield ['final-error', $value];
+
+        yield ['final-array-iterator', new FinalArrayIterator()];
+
+        yield ['final-stdclass', new FinalStdClass()];
+
+        $value = new MyWakeup();
+        $value->bis = new \ReflectionClass($value);
+
+        yield ['wakeup-refl', $value];
+
+        yield ['abstract-parent', new ConcreteClass()];
+
+        yield ['foo-serializable', new FooSerializable('bar')];
+
+        yield ['private-constructor', PrivateConstructor::create('bar')];
+
+        yield ['php74-serializable', new Php74Serializable()];
+    }
+}
+
+class MySerializable implements \Serializable
+{
+    public function serialize()
+    {
+        return '123';
+    }
+
+    public function unserialize($data)
+    {
+        // no-op
+    }
+}
+
+class MyWakeup
+{
+    public $sub;
+    public $bis;
+    public $baz;
+    public $def = 234;
+
+    public function __sleep()
+    {
+        return ['sub', 'baz'];
+    }
+
+    public function __wakeup()
+    {
+        if (123 === $this->sub) {
+            $this->bis = 123;
+            $this->baz = 123;
+        }
+    }
+}
+
+class MyCloneable
+{
+    public function __clone()
+    {
+        throw new \Exception('__clone should never be called');
+    }
+}
+
+class MyNotCloneable
+{
+    private function __clone()
+    {
+        throw new \Exception('__clone should never be called');
+    }
+}
+
+class PrivateConstructor
+{
+    public $prop;
+
+    public static function create($prop): self
+    {
+        return new self($prop);
+    }
+
+    private function __construct($prop)
+    {
+        $this->prop = $prop;
+    }
+}
+
+class MyPrivateValue
+{
+    protected $prot;
+    private $priv;
+
+    public function __construct($prot, $priv)
+    {
+        $this->prot = $prot;
+        $this->priv = $priv;
+    }
+}
+
+class MyPrivateChildValue extends MyPrivateValue
+{
+}
+
+class MyArrayObject extends \ArrayObject
+{
+    private $unused = 123;
+
+    public function __construct(array $array)
+    {
+        parent::__construct($array, 1);
+    }
+
+    public function setFlags($flags)
+    {
+        throw new \BadMethodCallException('Calling MyArrayObject::setFlags() is forbidden');
+    }
+}
+
+class GoodNight
+{
+    public function __sleep()
+    {
+        $this->good = 'night';
+
+        return ['good'];
+    }
+}
+
+final class FinalError extends \Error
+{
+    public function __construct(bool $throw = true)
+    {
+        if ($throw) {
+            throw new \BadMethodCallException('Should not be called.');
+        }
+    }
+}
+
+final class FinalArrayIterator extends \ArrayIterator
+{
+    public function serialize()
+    {
+        return serialize([123, parent::serialize()]);
+    }
+
+    public function unserialize($data)
+    {
+        if ('' === $data) {
+            throw new \InvalidArgumentException('Serialized data is empty.');
+        }
+        list(, $data) = unserialize($data);
+        parent::unserialize($data);
+    }
+}
+
+final class FinalStdClass extends \stdClass
+{
+    public function __clone()
+    {
+        throw new \BadMethodCallException('Should not be called.');
+    }
+}
+
+abstract class AbstractClass
+{
+    protected $foo;
+    private $bar;
+
+    protected function setBar($bar)
+    {
+        $this->bar = $bar;
+    }
+}
+
+class ConcreteClass extends AbstractClass
+{
+    public function __construct()
+    {
+        $this->foo = 123;
+        $this->setBar(234);
+    }
+}
+
+class FooSerializable implements \Serializable
+{
+    private $foo;
+
+    public function __construct(string $foo)
+    {
+        $this->foo = $foo;
+    }
+
+    public function getFoo(): string
+    {
+        return $this->foo;
+    }
+
+    public function serialize(): string
+    {
+        return serialize([$this->getFoo()]);
+    }
+
+    public function unserialize($str)
+    {
+        list($this->foo) = unserialize($str);
+    }
+}
+
+class Php74Serializable implements \Serializable
+{
+    public function __serialize()
+    {
+        return [$this->foo = new \stdClass()];
+    }
+
+    public function __unserialize(array $data)
+    {
+        list($this->foo) = $data;
+    }
+
+    public function __sleep()
+    {
+        throw new \BadMethodCallException();
+    }
+
+    public function __wakeup()
+    {
+        throw new \BadMethodCallException();
+    }
+
+    public function serialize()
+    {
+        throw new \BadMethodCallException();
+    }
+
+    public function unserialize($ser)
+    {
+        throw new \BadMethodCallException();
+    }
+}
diff --git a/vendor/symfony/var-exporter/VarExporter.php b/vendor/symfony/var-exporter/VarExporter.php
new file mode 100644
index 0000000..da9a8d4
--- /dev/null
+++ b/vendor/symfony/var-exporter/VarExporter.php
@@ -0,0 +1,114 @@
+<?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\VarExporter;
+
+use Symfony\Component\VarExporter\Exception\ExceptionInterface;
+use Symfony\Component\VarExporter\Internal\Exporter;
+use Symfony\Component\VarExporter\Internal\Hydrator;
+use Symfony\Component\VarExporter\Internal\Registry;
+use Symfony\Component\VarExporter\Internal\Values;
+
+/**
+ * Exports serializable PHP values to PHP code.
+ *
+ * VarExporter allows serializing PHP data structures to plain PHP code (like var_export())
+ * while preserving all the semantics associated with serialize() (unlike var_export()).
+ *
+ * By leveraging OPcache, the generated PHP code is faster than doing the same with unserialize().
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+final class VarExporter
+{
+    /**
+     * Exports a serializable PHP value to PHP code.
+     *
+     * @param mixed $value          The value to export
+     * @param bool  &$isStaticValue Set to true after execution if the provided value is static, false otherwise
+     *
+     * @return string The value exported as PHP code
+     *
+     * @throws ExceptionInterface When the provided value cannot be serialized
+     */
+    public static function export($value, bool &$isStaticValue = null): string
+    {
+        $isStaticValue = true;
+
+        if (!\is_object($value) && !(\is_array($value) && $value) && !$value instanceof \__PHP_Incomplete_Class && !\is_resource($value)) {
+            return Exporter::export($value);
+        }
+
+        $objectsPool = new \SplObjectStorage();
+        $refsPool = [];
+        $objectsCount = 0;
+
+        try {
+            $value = Exporter::prepare([$value], $objectsPool, $refsPool, $objectsCount, $isStaticValue)[0];
+        } finally {
+            $references = [];
+            foreach ($refsPool as $i => $v) {
+                if ($v[0]->count) {
+                    $references[1 + $i] = $v[2];
+                }
+                $v[0] = $v[1];
+            }
+        }
+
+        if ($isStaticValue) {
+            return Exporter::export($value);
+        }
+
+        $classes = [];
+        $values = [];
+        $states = [];
+        foreach ($objectsPool as $i => $v) {
+            list(, $classes[], $values[], $wakeup) = $objectsPool[$v];
+            if (0 < $wakeup) {
+                $states[$wakeup] = $i;
+            } elseif (0 > $wakeup) {
+                $states[-$wakeup] = [$i, array_pop($values)];
+                $values[] = [];
+            }
+        }
+        ksort($states);
+
+        $wakeups = [null];
+        foreach ($states as $k => $v) {
+            if (\is_array($v)) {
+                $wakeups[-$v[0]] = $v[1];
+            } else {
+                $wakeups[] = $v;
+            }
+        }
+
+        if (null === $wakeups[0]) {
+            unset($wakeups[0]);
+        }
+
+        $properties = [];
+        foreach ($values as $i => $vars) {
+            foreach ($vars as $class => $values) {
+                foreach ($values as $name => $v) {
+                    $properties[$class][$name][$i] = $v;
+                }
+            }
+        }
+
+        if ($classes || $references) {
+            $value = new Hydrator(new Registry($classes), $references ? new Values($references) : null, $properties, $value, $wakeups);
+        } else {
+            $isStaticValue = true;
+        }
+
+        return Exporter::export($value);
+    }
+}
diff --git a/vendor/symfony/var-exporter/composer.json b/vendor/symfony/var-exporter/composer.json
new file mode 100644
index 0000000..3d543df
--- /dev/null
+++ b/vendor/symfony/var-exporter/composer.json
@@ -0,0 +1,36 @@
+{
+    "name": "symfony/var-exporter",
+    "type": "library",
+    "description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code",
+    "keywords": ["export", "serialize", "instantiate", "hydrate", "construct", "clone"],
+    "homepage": "https://symfony.com",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Nicolas Grekas",
+            "email": "p@tchwork.com"
+        },
+        {
+            "name": "Symfony Community",
+            "homepage": "https://symfony.com/contributors"
+        }
+    ],
+    "require": {
+        "php": "^7.1.3"
+    },
+    "require-dev": {
+        "symfony/var-dumper": "^4.1.1"
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Component\\VarExporter\\": "" },
+        "exclude-from-classmap": [
+            "/Tests/"
+        ]
+    },
+    "minimum-stability": "dev",
+    "extra": {
+        "branch-alias": {
+            "dev-master": "4.3-dev"
+        }
+    }
+}
diff --git a/vendor/symfony/var-exporter/phpunit.xml.dist b/vendor/symfony/var-exporter/phpunit.xml.dist
new file mode 100644
index 0000000..96e2110
--- /dev/null
+++ b/vendor/symfony/var-exporter/phpunit.xml.dist
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
+         backupGlobals="false"
+         colors="true"
+         bootstrap="vendor/autoload.php"
+         failOnRisky="true"
+         failOnWarning="true"
+>
+    <php>
+        <ini name="error_reporting" value="-1" />
+    </php>
+
+    <testsuites>
+        <testsuite name="Symfony VarExporter Component Test Suite">
+            <directory>./Tests/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory>./</directory>
+            <exclude>
+                <directory>./Resources</directory>
+                <directory>./Tests</directory>
+                <directory>./vendor</directory>
+            </exclude>
+        </whitelist>
+    </filter>
+</phpunit>
--
libgit2 0.24.0