From 4ed1241faff106e530a10c45c5852fd11c64ca59 Mon Sep 17 00:00:00 2001 From: Johnata Date: Sun, 17 Sep 2017 18:19:02 -0300 Subject: [PATCH] API RESTful com framework Silex --- .htaccess | 12 + BD.txt | 15 + composer.json | 5 + composer.lock | 648 +++++ composer.phar | Bin 0 -> 1852323 bytes index.php | 103 + vendor/autoload.php | 7 + vendor/composer/ClassLoader.php | 445 ++++ vendor/composer/LICENSE | 21 + vendor/composer/autoload_classmap.php | 9 + vendor/composer/autoload_files.php | 10 + vendor/composer/autoload_namespaces.php | 10 + vendor/composer/autoload_psr4.php | 18 + vendor/composer/autoload_real.php | 70 + vendor/composer/autoload_static.php | 89 + vendor/composer/installed.json | 652 +++++ vendor/pimple/pimple/.gitignore | 3 + vendor/pimple/pimple/.travis.yml | 40 + vendor/pimple/pimple/CHANGELOG | 55 + vendor/pimple/pimple/LICENSE | 19 + vendor/pimple/pimple/README.rst | 326 +++ vendor/pimple/pimple/composer.json | 29 + vendor/pimple/pimple/ext/pimple/.gitignore | 30 + vendor/pimple/pimple/ext/pimple/README.md | 12 + vendor/pimple/pimple/ext/pimple/config.m4 | 63 + vendor/pimple/pimple/ext/pimple/config.w32 | 13 + vendor/pimple/pimple/ext/pimple/php_pimple.h | 137 + vendor/pimple/pimple/ext/pimple/pimple.c | 1114 +++++++++ .../pimple/pimple/ext/pimple/pimple_compat.h | 81 + .../pimple/pimple/ext/pimple/tests/001.phpt | 45 + .../pimple/pimple/ext/pimple/tests/002.phpt | 15 + .../pimple/pimple/ext/pimple/tests/003.phpt | 16 + .../pimple/pimple/ext/pimple/tests/004.phpt | 30 + .../pimple/pimple/ext/pimple/tests/005.phpt | 27 + .../pimple/pimple/ext/pimple/tests/006.phpt | 51 + .../pimple/pimple/ext/pimple/tests/007.phpt | 22 + .../pimple/pimple/ext/pimple/tests/008.phpt | 29 + .../pimple/pimple/ext/pimple/tests/009.phpt | 13 + .../pimple/pimple/ext/pimple/tests/010.phpt | 45 + .../pimple/pimple/ext/pimple/tests/011.phpt | 19 + .../pimple/pimple/ext/pimple/tests/012.phpt | 28 + .../pimple/pimple/ext/pimple/tests/013.phpt | 33 + .../pimple/pimple/ext/pimple/tests/014.phpt | 30 + .../pimple/pimple/ext/pimple/tests/015.phpt | 17 + .../pimple/pimple/ext/pimple/tests/016.phpt | 24 + .../pimple/pimple/ext/pimple/tests/017.phpt | 17 + .../pimple/pimple/ext/pimple/tests/017_1.phpt | 17 + .../pimple/pimple/ext/pimple/tests/018.phpt | 23 + .../pimple/pimple/ext/pimple/tests/019.phpt | 18 + .../pimple/pimple/ext/pimple/tests/bench.phpb | 51 + .../pimple/ext/pimple/tests/bench_shared.phpb | 25 + vendor/pimple/pimple/phpunit.xml.dist | 14 + vendor/pimple/pimple/src/Pimple/Container.php | 298 +++ .../Exception/ExpectedInvokableException.php | 38 + .../Exception/FrozenServiceException.php | 45 + .../InvalidServiceIdentifierException.php | 45 + .../Exception/UnknownIdentifierException.php | 45 + .../pimple/src/Pimple/Psr11/Container.php | 55 + .../src/Pimple/Psr11/ServiceLocator.php | 75 + .../pimple/src/Pimple/ServiceIterator.php | 69 + .../src/Pimple/ServiceProviderInterface.php | 46 + .../src/Pimple/Tests/Fixtures/Invokable.php | 38 + .../Pimple/Tests/Fixtures/NonInvokable.php | 34 + .../Tests/Fixtures/PimpleServiceProvider.php | 54 + .../src/Pimple/Tests/Fixtures/Service.php | 35 + .../PimpleServiceProviderInterfaceTest.php | 76 + .../pimple/src/Pimple/Tests/PimpleTest.php | 589 +++++ .../src/Pimple/Tests/Psr11/ContainerTest.php | 77 + .../Pimple/Tests/Psr11/ServiceLocatorTest.php | 134 + .../src/Pimple/Tests/ServiceIteratorTest.php | 52 + vendor/psr/container/.gitignore | 3 + vendor/psr/container/LICENSE | 21 + vendor/psr/container/README.md | 5 + vendor/psr/container/composer.json | 27 + .../src/ContainerExceptionInterface.php | 13 + .../psr/container/src/ContainerInterface.php | 37 + .../src/NotFoundExceptionInterface.php | 13 + vendor/psr/log/.gitignore | 1 + vendor/psr/log/LICENSE | 19 + vendor/psr/log/Psr/Log/AbstractLogger.php | 128 + .../log/Psr/Log/InvalidArgumentException.php | 7 + vendor/psr/log/Psr/Log/LogLevel.php | 18 + .../psr/log/Psr/Log/LoggerAwareInterface.php | 18 + vendor/psr/log/Psr/Log/LoggerAwareTrait.php | 26 + vendor/psr/log/Psr/Log/LoggerInterface.php | 123 + vendor/psr/log/Psr/Log/LoggerTrait.php | 140 ++ vendor/psr/log/Psr/Log/NullLogger.php | 28 + .../log/Psr/Log/Test/LoggerInterfaceTest.php | 140 ++ vendor/psr/log/README.md | 45 + vendor/psr/log/composer.json | 26 + vendor/silex/silex/.gitignore | 5 + vendor/silex/silex/.travis.yml | 59 + vendor/silex/silex/LICENSE | 19 + vendor/silex/silex/README.rst | 64 + vendor/silex/silex/composer.json | 72 + vendor/silex/silex/doc/changelog.rst | 387 +++ vendor/silex/silex/doc/conf.py | 17 + vendor/silex/silex/doc/contributing.rst | 34 + .../silex/doc/cookbook/error_handler.rst | 38 + .../silex/silex/doc/cookbook/form_no_csrf.rst | 36 + .../doc/cookbook/guard_authentication.rst | 183 ++ vendor/silex/silex/doc/cookbook/index.rst | 40 + .../silex/doc/cookbook/json_request_body.rst | 95 + .../silex/doc/cookbook/multiple_loggers.rst | 69 + .../silex/doc/cookbook/session_storage.rst | 89 + .../silex/silex/doc/cookbook/sub_requests.rst | 137 + .../silex/doc/cookbook/validator_yaml.rst | 35 + vendor/silex/silex/doc/index.rst | 19 + vendor/silex/silex/doc/internals.rst | 84 + vendor/silex/silex/doc/intro.rst | 50 + vendor/silex/silex/doc/middlewares.rst | 162 ++ .../silex/doc/organizing_controllers.rst | 84 + vendor/silex/silex/doc/providers.rst | 262 ++ vendor/silex/silex/doc/providers/asset.rst | 67 + vendor/silex/silex/doc/providers/csrf.rst | 53 + vendor/silex/silex/doc/providers/doctrine.rst | 137 + vendor/silex/silex/doc/providers/form.rst | 216 ++ .../silex/silex/doc/providers/http_cache.rst | 128 + .../silex/doc/providers/http_fragment.rst | 70 + vendor/silex/silex/doc/providers/index.rst | 24 + vendor/silex/silex/doc/providers/locale.rst | 24 + vendor/silex/silex/doc/providers/monolog.rst | 115 + .../silex/silex/doc/providers/remember_me.rst | 69 + vendor/silex/silex/doc/providers/security.rst | 709 ++++++ .../silex/silex/doc/providers/serializer.rst | 85 + .../doc/providers/service_controller.rst | 142 ++ vendor/silex/silex/doc/providers/session.rst | 120 + .../silex/silex/doc/providers/swiftmailer.rst | 156 ++ .../silex/silex/doc/providers/translation.rst | 193 ++ vendor/silex/silex/doc/providers/twig.rst | 237 ++ .../silex/silex/doc/providers/validator.rst | 217 ++ .../silex/silex/doc/providers/var_dumper.rst | 44 + vendor/silex/silex/doc/services.rst | 269 ++ vendor/silex/silex/doc/testing.rst | 222 ++ vendor/silex/silex/doc/usage.rst | 799 ++++++ vendor/silex/silex/doc/web_servers.rst | 183 ++ vendor/silex/silex/phpunit.xml.dist | 24 + .../Silex/Api/BootableProviderInterface.php | 33 + .../Silex/Api/ControllerProviderInterface.php | 32 + .../Api/EventListenerProviderInterface.php | 25 + vendor/silex/silex/src/Silex/Api/LICENSE | 19 + .../silex/silex/src/Silex/Api/composer.json | 34 + .../src/Silex/AppArgumentValueResolver.php | 47 + vendor/silex/silex/src/Silex/Application.php | 506 ++++ .../silex/src/Silex/Application/FormTrait.php | 55 + .../src/Silex/Application/MonologTrait.php | 36 + .../src/Silex/Application/SecurityTrait.php | 53 + .../Silex/Application/SwiftmailerTrait.php | 33 + .../Silex/Application/TranslationTrait.php | 51 + .../silex/src/Silex/Application/TwigTrait.php | 65 + .../Silex/Application/UrlGeneratorTrait.php | 48 + .../silex/src/Silex/CallbackResolver.php | 78 + vendor/silex/silex/src/Silex/Controller.php | 122 + .../silex/src/Silex/ControllerCollection.php | 239 ++ .../silex/src/Silex/ControllerResolver.php | 54 + .../Silex/EventListener/ConverterListener.php | 66 + .../src/Silex/EventListener/LogListener.php | 134 + .../EventListener/MiddlewareListener.php | 96 + .../StringToResponseListener.php | 51 + .../Exception/ControllerFrozenException.php | 21 + .../silex/src/Silex/ExceptionHandler.php | 56 + .../src/Silex/ExceptionListenerWrapper.php | 93 + .../Silex/Provider/AssetServiceProvider.php | 97 + .../Silex/Provider/CsrfServiceProvider.php | 48 + .../Provider/DoctrineServiceProvider.php | 129 + .../ExceptionHandlerServiceProvider.php | 32 + .../Provider/Form/SilexFormExtension.php | 122 + .../Silex/Provider/FormServiceProvider.php | 89 + .../Silex/Provider/HttpCache/HttpCache.php | 39 + .../Provider/HttpCacheServiceProvider.php | 61 + .../Provider/HttpFragmentServiceProvider.php | 86 + .../Provider/HttpKernelServiceProvider.php | 101 + vendor/silex/silex/src/Silex/Provider/LICENSE | 19 + .../Silex/Provider/Locale/LocaleListener.php | 84 + .../Silex/Provider/LocaleServiceProvider.php | 40 + .../Silex/Provider/MonologServiceProvider.php | 146 ++ .../Provider/RememberMeServiceProvider.php | 107 + .../Provider/Routing/LazyRequestMatcher.php | 55 + .../Routing/RedirectableUrlMatcher.php | 55 + .../Silex/Provider/RoutingServiceProvider.php | 87 + .../Provider/SecurityServiceProvider.php | 696 ++++++ .../Provider/SerializerServiceProvider.php | 50 + .../ServiceControllerServiceProvider.php | 26 + .../Provider/Session/SessionListener.php | 39 + .../Provider/Session/TestSessionListener.php | 39 + .../Silex/Provider/SessionServiceProvider.php | 86 + .../Provider/SwiftmailerServiceProvider.php | 138 ++ .../Provider/TranslationServiceProvider.php | 98 + .../src/Silex/Provider/Twig/RuntimeLoader.php | 41 + .../Silex/Provider/TwigServiceProvider.php | 190 ++ .../Validator/ConstraintValidatorFactory.php | 63 + .../Provider/ValidatorServiceProvider.php | 62 + .../Provider/VarDumperServiceProvider.php | 58 + .../silex/src/Silex/Provider/composer.json | 31 + vendor/silex/silex/src/Silex/Route.php | 202 ++ .../silex/src/Silex/Route/SecurityTrait.php | 31 + .../src/Silex/ServiceControllerResolver.php | 60 + .../silex/src/Silex/ViewListenerWrapper.php | 87 + vendor/silex/silex/src/Silex/WebTestCase.php | 65 + .../Tests/Application/FormApplication.php | 19 + .../Silex/Tests/Application/FormTraitTest.php | 45 + .../Tests/Application/MonologApplication.php | 19 + .../Tests/Application/MonologTraitTest.php | 48 + .../Tests/Application/SecurityApplication.php | 19 + .../Tests/Application/SecurityTraitTest.php | 91 + .../Application/SwiftmailerApplication.php | 19 + .../Application/SwiftmailerTraitTest.php | 45 + .../Application/TranslationApplication.php | 19 + .../Application/TranslationTraitTest.php | 47 + .../Tests/Application/TwigApplication.php | 19 + .../Silex/Tests/Application/TwigTraitTest.php | 81 + .../Application/UrlGeneratorApplication.php | 19 + .../Application/UrlGeneratorTraitTest.php | 39 + .../tests/Silex/Tests/ApplicationTest.php | 725 ++++++ .../Silex/Tests/CallbackResolverTest.php | 82 + .../Silex/Tests/CallbackServicesTest.php | 110 + .../Silex/Tests/ControllerCollectionTest.php | 328 +++ .../Silex/Tests/ControllerResolverTest.php | 39 + .../tests/Silex/Tests/ControllerTest.php | 133 + .../Tests/EventListener/LogListenerTest.php | 94 + .../Silex/Tests/ExceptionHandlerTest.php | 404 +++ .../Silex/Tests/Fixtures/Php7Controller.php | 22 + .../tests/Silex/Tests/Fixtures/manifest.json | 3 + .../tests/Silex/Tests/FunctionalTest.php | 59 + .../silex/tests/Silex/Tests/JsonTest.php | 57 + .../tests/Silex/Tests/LazyDispatcherTest.php | 60 + .../Silex/Tests/LazyRequestMatcherTest.php | 78 + .../silex/tests/Silex/Tests/LocaleTest.php | 84 + .../tests/Silex/Tests/MiddlewareTest.php | 308 +++ .../Provider/AssetServiceProviderTest.php | 52 + .../Provider/DoctrineServiceProviderTest.php | 117 + .../Provider/FormServiceProviderTest.php | 363 +++ .../DisableCsrfExtension.php | 22 + .../Provider/HttpCacheServiceProviderTest.php | 81 + .../HttpFragmentServiceProviderTest.php | 49 + .../Provider/MonologServiceProviderTest.php | 257 ++ .../RememberMeServiceProviderTest.php | 107 + .../Provider/RoutingServiceProviderTest.php | 122 + .../Provider/SecurityServiceProviderTest.php | 491 ++++ .../TokenAuthenticator.php | 79 + .../SerializerServiceProviderTest.php | 37 + .../Provider/SessionServiceProviderTest.php | 126 + .../tests/Silex/Tests/Provider/SpoolStub.php | 47 + .../SwiftmailerServiceProviderTest.php | 134 + .../TranslationServiceProviderTest.php | 182 ++ .../Provider/TwigServiceProviderTest.php | 165 ++ .../Provider/ValidatorServiceProviderTest.php | 206 ++ .../Constraint/Custom.php | 29 + .../Constraint/CustomValidator.php | 32 + .../tests/Silex/Tests/Route/SecurityRoute.php | 19 + .../Silex/Tests/Route/SecurityTraitTest.php | 86 + .../silex/tests/Silex/Tests/RouterTest.php | 286 +++ .../ServiceControllerResolverRouterTest.php | 43 + .../Tests/ServiceControllerResolverTest.php | 90 + .../silex/tests/Silex/Tests/StreamTest.php | 53 + .../tests/Silex/Tests/WebTestCaseTest.php | 78 + vendor/symfony/debug/.gitignore | 3 + vendor/symfony/debug/BufferingLogger.php | 37 + vendor/symfony/debug/CHANGELOG.md | 59 + vendor/symfony/debug/Debug.php | 63 + vendor/symfony/debug/DebugClassLoader.php | 354 +++ vendor/symfony/debug/ErrorHandler.php | 716 ++++++ .../Exception/ClassNotFoundException.php | 33 + .../debug/Exception/ContextErrorException.php | 40 + .../debug/Exception/FatalErrorException.php | 82 + .../debug/Exception/FatalThrowableError.php | 44 + .../debug/Exception/FlattenException.php | 263 ++ .../debug/Exception/OutOfMemoryException.php | 21 + .../debug/Exception/SilencedErrorContext.php | 67 + .../Exception/UndefinedFunctionException.php | 33 + .../Exception/UndefinedMethodException.php | 33 + vendor/symfony/debug/ExceptionHandler.php | 414 ++++ .../ClassNotFoundFatalErrorHandler.php | 206 ++ .../FatalErrorHandlerInterface.php | 32 + .../UndefinedFunctionFatalErrorHandler.php | 84 + .../UndefinedMethodFatalErrorHandler.php | 66 + vendor/symfony/debug/LICENSE | 19 + vendor/symfony/debug/README.md | 13 + vendor/symfony/debug/Resources/ext/README.md | 134 + vendor/symfony/debug/Resources/ext/config.m4 | 63 + vendor/symfony/debug/Resources/ext/config.w32 | 13 + .../debug/Resources/ext/php_symfony_debug.h | 60 + .../debug/Resources/ext/symfony_debug.c | 283 +++ .../debug/Resources/ext/tests/001.phpt | 153 ++ .../debug/Resources/ext/tests/002.phpt | 63 + .../debug/Resources/ext/tests/002_1.phpt | 46 + .../debug/Resources/ext/tests/003.phpt | 85 + .../debug/Tests/DebugClassLoaderTest.php | 387 +++ .../symfony/debug/Tests/ErrorHandlerTest.php | 535 ++++ .../Tests/Exception/FlattenExceptionTest.php | 301 +++ .../debug/Tests/ExceptionHandlerTest.php | 133 + .../ClassNotFoundFatalErrorHandlerTest.php | 176 ++ ...UndefinedFunctionFatalErrorHandlerTest.php | 81 + .../UndefinedMethodFatalErrorHandlerTest.php | 76 + .../debug/Tests/Fixtures/ClassAlias.php | 3 + .../debug/Tests/Fixtures/DeprecatedClass.php | 12 + .../Tests/Fixtures/DeprecatedInterface.php | 12 + .../Tests/Fixtures/ExtendedFinalMethod.php | 17 + .../debug/Tests/Fixtures/FinalClass.php | 10 + .../debug/Tests/Fixtures/FinalMethod.php | 17 + .../Tests/Fixtures/NonDeprecatedInterface.php | 7 + .../debug/Tests/Fixtures/PEARClass.php | 5 + .../symfony/debug/Tests/Fixtures/Throwing.php | 3 + .../debug/Tests/Fixtures/ToStringThrower.php | 24 + .../debug/Tests/Fixtures/casemismatch.php | 7 + .../debug/Tests/Fixtures/notPsr0Bis.php | 7 + .../Tests/Fixtures/psr4/Psr4CaseMismatch.php | 7 + .../debug/Tests/Fixtures/reallyNotPsr0.php | 7 + .../debug/Tests/Fixtures2/RequiredTwice.php | 7 + vendor/symfony/debug/Tests/HeaderMock.php | 38 + .../debug/Tests/MockExceptionHandler.php | 24 + vendor/symfony/debug/composer.json | 40 + vendor/symfony/debug/phpunit.xml.dist | 33 + vendor/symfony/event-dispatcher/.gitignore | 3 + vendor/symfony/event-dispatcher/CHANGELOG.md | 37 + .../ContainerAwareEventDispatcher.php | 211 ++ .../Debug/TraceableEventDispatcher.php | 324 +++ .../TraceableEventDispatcherInterface.php | 34 + .../Debug/WrappedListener.php | 114 + .../RegisterListenersPass.php | 135 + vendor/symfony/event-dispatcher/Event.php | 58 + .../event-dispatcher/EventDispatcher.php | 236 ++ .../EventDispatcherInterface.php | 100 + .../EventSubscriberInterface.php | 46 + .../symfony/event-dispatcher/GenericEvent.php | 186 ++ .../ImmutableEventDispatcher.php | 101 + vendor/symfony/event-dispatcher/LICENSE | 19 + vendor/symfony/event-dispatcher/README.md | 15 + .../Tests/AbstractEventDispatcherTest.php | 442 ++++ .../ContainerAwareEventDispatcherTest.php | 210 ++ .../Debug/TraceableEventDispatcherTest.php | 242 ++ .../RegisterListenersPassTest.php | 167 ++ .../Tests/EventDispatcherTest.php | 22 + .../event-dispatcher/Tests/EventTest.php | 55 + .../Tests/GenericEventTest.php | 140 ++ .../Tests/ImmutableEventDispatcherTest.php | 106 + vendor/symfony/event-dispatcher/composer.json | 47 + .../symfony/event-dispatcher/phpunit.xml.dist | 31 + vendor/symfony/http-foundation/.gitignore | 3 + .../symfony/http-foundation/AcceptHeader.php | 172 ++ .../http-foundation/AcceptHeaderItem.php | 226 ++ .../symfony/http-foundation/ApacheRequest.php | 43 + .../http-foundation/BinaryFileResponse.php | 361 +++ vendor/symfony/http-foundation/CHANGELOG.md | 149 ++ vendor/symfony/http-foundation/Cookie.php | 291 +++ .../Exception/ConflictingHeadersException.php | 21 + .../Exception/RequestExceptionInterface.php | 21 + .../SuspiciousOperationException.php | 20 + .../ExpressionRequestMatcher.php | 47 + .../File/Exception/AccessDeniedException.php | 30 + .../File/Exception/FileException.php | 21 + .../File/Exception/FileNotFoundException.php | 30 + .../Exception/UnexpectedTypeException.php | 20 + .../File/Exception/UploadException.php | 21 + vendor/symfony/http-foundation/File/File.php | 136 + .../File/MimeType/ExtensionGuesser.php | 96 + .../MimeType/ExtensionGuesserInterface.php | 27 + .../MimeType/FileBinaryMimeTypeGuesser.php | 87 + .../File/MimeType/FileinfoMimeTypeGuesser.php | 71 + .../MimeType/MimeTypeExtensionGuesser.php | 809 ++++++ .../File/MimeType/MimeTypeGuesser.php | 144 ++ .../MimeType/MimeTypeGuesserInterface.php | 35 + .../symfony/http-foundation/File/Stream.php | 28 + .../http-foundation/File/UploadedFile.php | 293 +++ vendor/symfony/http-foundation/FileBag.php | 145 ++ vendor/symfony/http-foundation/HeaderBag.php | 325 +++ vendor/symfony/http-foundation/IpUtils.php | 148 ++ .../symfony/http-foundation/JsonResponse.php | 220 ++ vendor/symfony/http-foundation/LICENSE | 19 + .../symfony/http-foundation/ParameterBag.php | 238 ++ vendor/symfony/http-foundation/README.md | 14 + .../http-foundation/RedirectResponse.php | 103 + vendor/symfony/http-foundation/Request.php | 2114 ++++++++++++++++ .../http-foundation/RequestMatcher.php | 178 ++ .../RequestMatcherInterface.php | 29 + .../symfony/http-foundation/RequestStack.php | 103 + vendor/symfony/http-foundation/Response.php | 1288 ++++++++++ .../http-foundation/ResponseHeaderBag.php | 341 +++ vendor/symfony/http-foundation/ServerBag.php | 102 + .../Session/Attribute/AttributeBag.php | 157 ++ .../Attribute/AttributeBagInterface.php | 72 + .../Attribute/NamespacedAttributeBag.php | 160 ++ .../Session/Flash/AutoExpireFlashBag.php | 175 ++ .../Session/Flash/FlashBag.php | 166 ++ .../Session/Flash/FlashBagInterface.php | 95 + .../http-foundation/Session/Session.php | 261 ++ .../Session/SessionBagInterface.php | 48 + .../Session/SessionInterface.php | 182 ++ .../Handler/MemcacheSessionHandler.php | 121 + .../Handler/MemcachedSessionHandler.php | 127 + .../Storage/Handler/MongoDbSessionHandler.php | 232 ++ .../Handler/NativeFileSessionHandler.php | 58 + .../Storage/Handler/NativeSessionHandler.php | 21 + .../Storage/Handler/NullSessionHandler.php | 70 + .../Storage/Handler/PdoSessionHandler.php | 721 ++++++ .../Handler/WriteCheckSessionHandler.php | 91 + .../Session/Storage/MetadataBag.php | 170 ++ .../Storage/MockArraySessionStorage.php | 268 ++ .../Storage/MockFileSessionStorage.php | 138 ++ .../Session/Storage/NativeSessionStorage.php | 423 ++++ .../Storage/PhpBridgeSessionStorage.php | 64 + .../Session/Storage/Proxy/AbstractProxy.php | 124 + .../Session/Storage/Proxy/NativeProxy.php | 41 + .../Storage/Proxy/SessionHandlerProxy.php | 95 + .../Storage/SessionStorageInterface.php | 139 ++ .../http-foundation/StreamedResponse.php | 132 + .../Tests/AcceptHeaderItemTest.php | 113 + .../Tests/AcceptHeaderTest.php | 103 + .../Tests/ApacheRequestTest.php | 93 + .../Tests/BinaryFileResponseTest.php | 352 +++ .../http-foundation/Tests/CookieTest.php | 223 ++ .../Tests/ExpressionRequestMatcherTest.php | 69 + .../http-foundation/Tests/File/FakeFile.php | 45 + .../http-foundation/Tests/File/FileTest.php | 180 ++ .../Tests/File/Fixtures/.unknownextension | 1 + .../Tests/File/Fixtures/directory/.empty | 0 .../Tests/File/Fixtures/other-file.example | 0 .../http-foundation/Tests/File/Fixtures/test | Bin 0 -> 35 bytes .../Tests/File/Fixtures/test.gif | Bin 0 -> 35 bytes .../Tests/File/MimeType/MimeTypeTest.php | 90 + .../Tests/File/UploadedFileTest.php | 273 ++ .../http-foundation/Tests/FileBagTest.php | 149 ++ .../http-foundation/Tests/HeaderBagTest.php | 205 ++ .../http-foundation/Tests/IpUtilsTest.php | 85 + .../Tests/JsonResponseTest.php | 257 ++ .../Tests/ParameterBagTest.php | 194 ++ .../Tests/RedirectResponseTest.php | 97 + .../Tests/RequestMatcherTest.php | 151 ++ .../Tests/RequestStackTest.php | 70 + .../http-foundation/Tests/RequestTest.php | 2207 +++++++++++++++++ .../Tests/ResponseHeaderBagTest.php | 339 +++ .../http-foundation/Tests/ResponseTest.php | 981 ++++++++ .../Tests/ResponseTestCase.php | 89 + .../http-foundation/Tests/ServerBagTest.php | 170 ++ .../Session/Attribute/AttributeBagTest.php | 189 ++ .../Attribute/NamespacedAttributeBagTest.php | 185 ++ .../Session/Flash/AutoExpireFlashBagTest.php | 156 ++ .../Tests/Session/Flash/FlashBagTest.php | 135 + .../Tests/Session/SessionTest.php | 224 ++ .../Handler/MemcacheSessionHandlerTest.php | 133 + .../Handler/MemcachedSessionHandlerTest.php | 139 ++ .../Handler/MongoDbSessionHandlerTest.php | 332 +++ .../Handler/NativeFileSessionHandlerTest.php | 77 + .../Handler/NativeSessionHandlerTest.php | 34 + .../Handler/NullSessionHandlerTest.php | 59 + .../Storage/Handler/PdoSessionHandlerTest.php | 370 +++ .../Handler/WriteCheckSessionHandlerTest.php | 95 + .../Tests/Session/Storage/MetadataBagTest.php | 142 ++ .../Storage/MockArraySessionStorageTest.php | 131 + .../Storage/MockFileSessionStorageTest.php | 127 + .../Storage/NativeSessionStorageTest.php | 247 ++ .../Storage/PhpBridgeSessionStorageTest.php | 96 + .../Storage/Proxy/AbstractProxyTest.php | 145 ++ .../Session/Storage/Proxy/NativeProxyTest.php | 36 + .../Storage/Proxy/SessionHandlerProxyTest.php | 124 + .../Tests/StreamedResponseTest.php | 115 + .../Tests/schema/http-status-codes.rng | 31 + .../Tests/schema/iana-registry.rng | 198 ++ vendor/symfony/http-foundation/composer.json | 37 + .../symfony/http-foundation/phpunit.xml.dist | 31 + vendor/symfony/http-kernel/.gitignore | 5 + vendor/symfony/http-kernel/Bundle/Bundle.php | 231 ++ .../http-kernel/Bundle/BundleInterface.php | 84 + vendor/symfony/http-kernel/CHANGELOG.md | 134 + .../CacheClearer/CacheClearerInterface.php | 27 + .../CacheClearer/ChainCacheClearer.php | 55 + .../CacheClearer/Psr6CacheClearer.php | 58 + .../http-kernel/CacheWarmer/CacheWarmer.php | 32 + .../CacheWarmer/CacheWarmerAggregate.php | 74 + .../CacheWarmer/CacheWarmerInterface.php | 32 + .../CacheWarmer/WarmableInterface.php | 27 + vendor/symfony/http-kernel/Client.php | 206 ++ .../Config/EnvParametersResource.php | 99 + .../http-kernel/Config/FileLocator.php | 56 + .../Controller/ArgumentResolver.php | 94 + .../ArgumentResolver/DefaultValueResolver.php | 40 + .../RequestAttributeValueResolver.php | 40 + .../ArgumentResolver/RequestValueResolver.php | 40 + .../ArgumentResolver/ServiceValueResolver.php | 48 + .../ArgumentResolver/SessionValueResolver.php | 46 + .../VariadicValueResolver.php | 48 + .../Controller/ArgumentResolverInterface.php | 35 + .../ArgumentValueResolverInterface.php | 43 + .../ContainerControllerResolver.php | 76 + .../Controller/ControllerReference.php | 46 + .../Controller/ControllerResolver.php | 265 ++ .../ControllerResolverInterface.php | 59 + .../Controller/TraceableArgumentResolver.php | 44 + .../TraceableControllerResolver.php | 78 + .../ControllerMetadata/ArgumentMetadata.php | 115 + .../ArgumentMetadataFactory.php | 129 + .../ArgumentMetadataFactoryInterface.php | 27 + .../DataCollector/AjaxDataCollector.php | 33 + .../DataCollector/ConfigDataCollector.php | 330 +++ .../DataCollector/DataCollector.php | 128 + .../DataCollector/DataCollectorInterface.php | 39 + .../DataCollector/DumpDataCollector.php | 303 +++ .../DataCollector/EventDataCollector.php | 108 + .../DataCollector/ExceptionDataCollector.php | 104 + .../LateDataCollectorInterface.php | 25 + .../DataCollector/LoggerDataCollector.php | 262 ++ .../DataCollector/MemoryDataCollector.php | 109 + .../DataCollector/RequestDataCollector.php | 397 +++ .../DataCollector/RouterDataCollector.php | 102 + .../DataCollector/TimeDataCollector.php | 136 + .../DataCollector/Util/ValueExporter.php | 99 + .../http-kernel/Debug/FileLinkFormatter.php | 88 + .../Debug/TraceableEventDispatcher.php | 82 + .../AddAnnotatedClassesToCachePass.php | 153 ++ .../AddClassesToCachePass.php | 25 + .../ConfigurableExtension.php | 45 + .../ControllerArgumentValueResolverPass.php | 48 + .../DependencyInjection/Extension.php | 77 + .../FragmentRendererPass.php | 67 + .../LazyLoadingFragmentHandler.php | 81 + .../MergeExtensionConfigurationPass.php | 41 + ...RegisterControllerArgumentLocatorsPass.php | 159 ++ ...oveEmptyControllerArgumentLocatorsPass.php | 76 + .../Event/FilterControllerArgumentsEvent.php | 55 + .../Event/FilterControllerEvent.php | 61 + .../http-kernel/Event/FilterResponseEvent.php | 62 + .../http-kernel/Event/FinishRequestEvent.php | 21 + .../http-kernel/Event/GetResponseEvent.php | 65 + .../GetResponseForControllerResultEvent.php | 61 + .../Event/GetResponseForExceptionEvent.php | 90 + .../symfony/http-kernel/Event/KernelEvent.php | 94 + .../http-kernel/Event/PostResponseEvent.php | 46 + .../EventListener/AbstractSessionListener.php | 54 + .../AbstractTestSessionListener.php | 84 + .../AddRequestFormatsListener.php | 57 + .../EventListener/DebugHandlersListener.php | 146 ++ .../EventListener/DumpListener.php | 59 + .../EventListener/ExceptionListener.php | 116 + .../EventListener/FragmentListener.php | 103 + .../EventListener/LocaleListener.php | 85 + .../EventListener/ProfilerListener.php | 134 + .../EventListener/ResponseListener.php | 58 + .../EventListener/RouterListener.php | 140 ++ .../EventListener/SaveSessionListener.php | 66 + .../EventListener/SessionListener.php | 40 + .../StreamedResponseListener.php | 51 + .../EventListener/SurrogateListener.php | 72 + .../EventListener/TestSessionListener.php | 40 + .../EventListener/TranslatorListener.php | 69 + .../EventListener/ValidateRequestListener.php | 55 + .../Exception/AccessDeniedHttpException.php | 33 + .../Exception/BadRequestHttpException.php | 32 + .../Exception/ConflictHttpException.php | 32 + .../Exception/GoneHttpException.php | 32 + .../http-kernel/Exception/HttpException.php | 51 + .../Exception/HttpExceptionInterface.php | 34 + .../Exception/LengthRequiredHttpException.php | 32 + .../MethodNotAllowedHttpException.php | 35 + .../Exception/NotAcceptableHttpException.php | 32 + .../Exception/NotFoundHttpException.php | 32 + .../PreconditionFailedHttpException.php | 32 + .../PreconditionRequiredHttpException.php | 34 + .../ServiceUnavailableHttpException.php | 38 + .../TooManyRequestsHttpException.php | 40 + .../Exception/UnauthorizedHttpException.php | 35 + .../UnprocessableEntityHttpException.php | 32 + .../UnsupportedMediaTypeHttpException.php | 32 + .../AbstractSurrogateFragmentRenderer.php | 112 + .../Fragment/EsiFragmentRenderer.php | 28 + .../http-kernel/Fragment/FragmentHandler.php | 118 + .../Fragment/FragmentRendererInterface.php | 42 + .../Fragment/HIncludeFragmentRenderer.php | 167 ++ .../Fragment/InlineFragmentRenderer.php | 158 ++ .../Fragment/RoutableFragmentRenderer.php | 90 + .../Fragment/SsiFragmentRenderer.php | 28 + .../HttpCache/AbstractSurrogate.php | 138 ++ vendor/symfony/http-kernel/HttpCache/Esi.php | 115 + .../http-kernel/HttpCache/HttpCache.php | 737 ++++++ .../HttpCache/ResponseCacheStrategy.php | 96 + .../ResponseCacheStrategyInterface.php | 41 + vendor/symfony/http-kernel/HttpCache/Ssi.php | 98 + .../symfony/http-kernel/HttpCache/Store.php | 508 ++++ .../http-kernel/HttpCache/StoreInterface.php | 96 + .../HttpCache/SurrogateInterface.php | 103 + vendor/symfony/http-kernel/HttpKernel.php | 301 +++ .../http-kernel/HttpKernelInterface.php | 43 + vendor/symfony/http-kernel/Kernel.php | 866 +++++++ vendor/symfony/http-kernel/KernelEvents.php | 119 + .../symfony/http-kernel/KernelInterface.php | 164 ++ vendor/symfony/http-kernel/LICENSE | 19 + .../http-kernel/Log/DebugLoggerInterface.php | 38 + .../Profiler/FileProfilerStorage.php | 292 +++ .../symfony/http-kernel/Profiler/Profile.php | 295 +++ .../symfony/http-kernel/Profiler/Profiler.php | 270 ++ .../Profiler/ProfilerStorageInterface.php | 59 + vendor/symfony/http-kernel/README.md | 16 + .../http-kernel/TerminableInterface.php | 35 + .../http-kernel/Tests/Bundle/BundleTest.php | 100 + .../CacheClearer/ChainCacheClearerTest.php | 58 + .../CacheClearer/Psr6CacheClearerTest.php | 69 + .../CacheWarmer/CacheWarmerAggregateTest.php | 101 + .../Tests/CacheWarmer/CacheWarmerTest.php | 68 + .../symfony/http-kernel/Tests/ClientTest.php | 183 ++ .../Config/EnvParametersResourceTest.php | 107 + .../Tests/Config/FileLocatorTest.php | 48 + .../Tests/Controller/ArgumentResolverTest.php | 349 +++ .../ContainerControllerResolverTest.php | 148 ++ .../Controller/ControllerResolverTest.php | 331 +++ .../ArgumentMetadataFactoryTest.php | 148 ++ .../ArgumentMetadataTest.php | 46 + .../Tests/DataCollector/Compiler.log | 4 + .../DataCollector/ConfigDataCollectorTest.php | 66 + .../Tests/DataCollector/DataCollectorTest.php | 38 + .../DataCollector/DumpDataCollectorTest.php | 115 + .../ExceptionDataCollectorTest.php | 40 + .../DataCollector/LoggerDataCollectorTest.php | 126 + .../DataCollector/MemoryDataCollectorTest.php | 59 + .../RequestDataCollectorTest.php | 287 +++ .../DataCollector/TimeDataCollectorTest.php | 55 + .../DataCollector/Util/ValueExporterTest.php | 51 + .../Tests/Debug/FileLinkFormatterTest.php | 67 + .../Debug/TraceableEventDispatcherTest.php | 121 + .../AddAnnotatedClassesToCachePassTest.php | 99 + ...ontrollerArgumentValueResolverPassTest.php | 67 + .../FragmentRendererPassTest.php | 105 + .../LazyLoadingFragmentHandlerTest.php | 66 + .../MergeExtensionConfigurationPassTest.php | 55 + ...sterControllerArgumentLocatorsPassTest.php | 344 +++ ...mptyControllerArgumentLocatorsPassTest.php | 148 ++ .../GetResponseForExceptionEventTest.php | 27 + .../AddRequestFormatsListenerTest.php | 84 + .../DebugHandlersListenerTest.php | 135 + .../Tests/EventListener/DumpListenerTest.php | 81 + .../EventListener/ExceptionListenerTest.php | 149 ++ .../EventListener/FragmentListenerTest.php | 122 + .../EventListener/LocaleListenerTest.php | 103 + .../EventListener/ProfilerListenerTest.php | 71 + .../EventListener/ResponseListenerTest.php | 95 + .../EventListener/RouterListenerTest.php | 188 ++ .../EventListener/SurrogateListenerTest.php | 67 + .../EventListener/TestSessionListenerTest.php | 145 ++ .../EventListener/TranslatorListenerTest.php | 118 + .../ValidateRequestListenerTest.php | 43 + .../AccessDeniedHttpExceptionTest.php | 13 + .../Exception/BadRequestHttpExceptionTest.php | 13 + .../Exception/ConflictHttpExceptionTest.php | 13 + .../Tests/Exception/GoneHttpExceptionTest.php | 13 + .../Tests/Exception/HttpExceptionTest.php | 53 + .../LengthRequiredHttpExceptionTest.php | 13 + .../MethodNotAllowedHttpExceptionTest.php | 24 + .../NotAcceptableHttpExceptionTest.php | 13 + .../Exception/NotFoundHttpExceptionTest.php | 13 + .../PreconditionFailedHttpExceptionTest.php | 13 + .../PreconditionRequiredHttpExceptionTest.php | 13 + .../ServiceUnavailableHttpExceptionTest.php | 29 + .../TooManyRequestsHttpExceptionTest.php | 29 + .../UnauthorizedHttpExceptionTest.php | 24 + .../UnprocessableEntityHttpExceptionTest.php | 28 + .../UnsupportedMediaTypeHttpExceptionTest.php | 23 + .../Tests/Fixtures/123/Kernel123.php | 37 + .../Fixtures/BaseBundle/Resources/foo.txt | 0 .../Fixtures/BaseBundle/Resources/hide.txt | 0 .../Fixtures/Bundle1Bundle/Resources/foo.txt | 0 .../Tests/Fixtures/Bundle1Bundle/bar.txt | 0 .../Tests/Fixtures/Bundle1Bundle/foo.txt | 0 .../Tests/Fixtures/Bundle2Bundle/foo.txt | 0 .../Fixtures/ChildBundle/Resources/foo.txt | 0 .../Fixtures/ChildBundle/Resources/hide.txt | 0 .../Controller/BasicTypesController.php | 19 + .../Fixtures/Controller/ExtendingRequest.php | 18 + .../Fixtures/Controller/ExtendingSession.php | 18 + .../Controller/NullableController.php | 19 + .../Controller/VariadicController.php | 19 + .../DataCollector/CloneVarDataCollector.php | 41 + .../ExtensionAbsentBundle.php | 18 + .../ExtensionLoadedExtension.php | 22 + .../ExtensionLoadedBundle.php | 18 + .../ExtensionNotValidExtension.php | 20 + .../ExtensionNotValidBundle.php | 18 + .../Command/BarCommand.php | 17 + .../Command/FooCommand.php | 22 + .../ExtensionPresentExtension.php | 22 + .../ExtensionPresentBundle.php | 18 + .../Tests/Fixtures/KernelForOverrideName.php | 28 + .../Tests/Fixtures/KernelForTest.php | 37 + .../Tests/Fixtures/KernelWithoutBundles.php | 33 + .../Fixtures/Resources/BaseBundle/hide.txt | 0 .../Fixtures/Resources/Bundle1Bundle/foo.txt | 0 .../Fixtures/Resources/ChildBundle/foo.txt | 0 .../Fixtures/Resources/FooBundle/foo.txt | 0 .../http-kernel/Tests/Fixtures/TestClient.php | 31 + .../Tests/Fixtures/TestEventDispatcher.php | 28 + .../Fragment/EsiFragmentRendererTest.php | 110 + .../Tests/Fragment/FragmentHandlerTest.php | 99 + .../Fragment/HIncludeFragmentRendererTest.php | 89 + .../Fragment/InlineFragmentRendererTest.php | 250 ++ .../Fragment/RoutableFragmentRendererTest.php | 94 + .../Fragment/SsiFragmentRendererTest.php | 97 + .../http-kernel/Tests/HttpCache/EsiTest.php | 248 ++ .../Tests/HttpCache/HttpCacheTest.php | 1367 ++++++++++ .../Tests/HttpCache/HttpCacheTestCase.php | 185 ++ .../HttpCache/ResponseCacheStrategyTest.php | 222 ++ .../http-kernel/Tests/HttpCache/SsiTest.php | 215 ++ .../http-kernel/Tests/HttpCache/StoreTest.php | 301 +++ .../Tests/HttpCache/TestHttpKernel.php | 92 + .../HttpCache/TestMultipleHttpKernel.php | 81 + .../http-kernel/Tests/HttpKernelTest.php | 403 +++ .../symfony/http-kernel/Tests/KernelTest.php | 907 +++++++ vendor/symfony/http-kernel/Tests/Logger.php | 88 + .../Profiler/FileProfilerStorageTest.php | 350 +++ .../Tests/Profiler/ProfilerTest.php | 91 + .../http-kernel/Tests/TestHttpKernel.php | 42 + .../http-kernel/Tests/UriSignerTest.php | 64 + vendor/symfony/http-kernel/UriSigner.php | 112 + vendor/symfony/http-kernel/composer.json | 70 + vendor/symfony/http-kernel/phpunit.xml.dist | 40 + vendor/symfony/polyfill-mbstring/LICENSE | 19 + vendor/symfony/polyfill-mbstring/Mbstring.php | 664 +++++ vendor/symfony/polyfill-mbstring/README.md | 13 + .../Resources/unidata/lowerCase.php | 1101 ++++++++ .../Resources/unidata/upperCase.php | 1109 +++++++++ .../symfony/polyfill-mbstring/bootstrap.php | 56 + .../symfony/polyfill-mbstring/composer.json | 34 + vendor/symfony/routing/.gitignore | 3 + vendor/symfony/routing/Annotation/Route.php | 146 ++ vendor/symfony/routing/CHANGELOG.md | 219 ++ vendor/symfony/routing/CompiledRoute.php | 171 ++ .../RoutingResolverPass.php | 46 + .../routing/Exception/ExceptionInterface.php | 21 + .../Exception/InvalidParameterException.php | 21 + .../Exception/MethodNotAllowedException.php | 44 + .../MissingMandatoryParametersException.php | 22 + .../Exception/ResourceNotFoundException.php | 23 + .../Exception/RouteNotFoundException.php | 21 + .../ConfigurableRequirementsInterface.php | 55 + .../Generator/Dumper/GeneratorDumper.php | 45 + .../Dumper/GeneratorDumperInterface.php | 39 + .../Generator/Dumper/PhpGeneratorDumper.php | 123 + .../routing/Generator/UrlGenerator.php | 339 +++ .../Generator/UrlGeneratorInterface.php | 86 + vendor/symfony/routing/LICENSE | 19 + .../routing/Loader/AnnotationClassLoader.php | 270 ++ .../Loader/AnnotationDirectoryLoader.php | 89 + .../routing/Loader/AnnotationFileLoader.php | 147 ++ .../symfony/routing/Loader/ClosureLoader.php | 46 + .../ServiceRouterLoader.php | 40 + .../routing/Loader/DirectoryLoader.php | 58 + .../routing/Loader/ObjectRouteLoader.php | 95 + .../symfony/routing/Loader/PhpFileLoader.php | 66 + .../symfony/routing/Loader/XmlFileLoader.php | 342 +++ .../symfony/routing/Loader/YamlFileLoader.php | 208 ++ .../Loader/schema/routing/routing-1.0.xsd | 146 ++ .../Matcher/Dumper/DumperCollection.php | 161 ++ .../routing/Matcher/Dumper/DumperRoute.php | 66 + .../routing/Matcher/Dumper/MatcherDumper.php | 45 + .../Matcher/Dumper/MatcherDumperInterface.php | 39 + .../Matcher/Dumper/PhpMatcherDumper.php | 431 ++++ .../Matcher/Dumper/StaticPrefixCollection.php | 238 ++ .../Matcher/RedirectableUrlMatcher.php | 65 + .../RedirectableUrlMatcherInterface.php | 31 + .../Matcher/RequestMatcherInterface.php | 39 + .../routing/Matcher/TraceableUrlMatcher.php | 141 ++ vendor/symfony/routing/Matcher/UrlMatcher.php | 266 ++ .../routing/Matcher/UrlMatcherInterface.php | 39 + vendor/symfony/routing/README.md | 13 + vendor/symfony/routing/RequestContext.php | 344 +++ .../routing/RequestContextAwareInterface.php | 29 + vendor/symfony/routing/Route.php | 589 +++++ vendor/symfony/routing/RouteCollection.php | 277 +++ .../routing/RouteCollectionBuilder.php | 383 +++ vendor/symfony/routing/RouteCompiler.php | 319 +++ .../routing/RouteCompilerInterface.php | 32 + vendor/symfony/routing/Router.php | 388 +++ vendor/symfony/routing/RouterInterface.php | 32 + .../routing/Tests/Annotation/RouteTest.php | 50 + .../routing/Tests/CompiledRouteTest.php | 27 + .../RoutingResolverPassTest.php | 36 + .../AnnotatedClasses/AbstractClass.php | 16 + .../Fixtures/AnnotatedClasses/BarClass.php | 19 + .../Fixtures/AnnotatedClasses/BazClass.php | 19 + .../Fixtures/AnnotatedClasses/FooClass.php | 16 + .../Fixtures/AnnotatedClasses/FooTrait.php | 13 + .../Tests/Fixtures/CustomCompiledRoute.php | 18 + .../Tests/Fixtures/CustomRouteCompiler.php | 26 + .../Tests/Fixtures/CustomXmlFileLoader.php | 26 + .../OtherAnnotatedClasses/NoStartTagClass.php | 3 + .../OtherAnnotatedClasses/VariadicClass.php | 19 + .../Tests/Fixtures/RedirectableUrlMatcher.php | 30 + .../routing/Tests/Fixtures/annotated.php | 0 .../routing/Tests/Fixtures/bad_format.yml | 3 + vendor/symfony/routing/Tests/Fixtures/bar.xml | 0 .../Fixtures/directory/recurse/routes1.yml | 2 + .../Fixtures/directory/recurse/routes2.yml | 2 + .../Tests/Fixtures/directory/routes3.yml | 2 + .../Fixtures/directory_import/import.yml | 3 + .../Tests/Fixtures/dumper/url_matcher1.apache | 163 ++ .../Tests/Fixtures/dumper/url_matcher1.php | 317 +++ .../Tests/Fixtures/dumper/url_matcher2.apache | 7 + .../Tests/Fixtures/dumper/url_matcher2.php | 349 +++ .../Tests/Fixtures/dumper/url_matcher3.php | 58 + .../Tests/Fixtures/dumper/url_matcher4.php | 109 + .../Tests/Fixtures/dumper/url_matcher5.php | 158 ++ .../Tests/Fixtures/dumper/url_matcher6.php | 204 ++ .../Tests/Fixtures/dumper/url_matcher7.php | 228 ++ .../symfony/routing/Tests/Fixtures/empty.yml | 0 .../routing/Tests/Fixtures/file_resource.yml | 0 vendor/symfony/routing/Tests/Fixtures/foo.xml | 0 .../symfony/routing/Tests/Fixtures/foo1.xml | 0 .../routing/Tests/Fixtures/incomplete.yml | 2 + .../routing/Tests/Fixtures/list_defaults.xml | 20 + .../Tests/Fixtures/list_in_list_defaults.xml | 22 + .../Tests/Fixtures/list_in_map_defaults.xml | 22 + .../Tests/Fixtures/list_null_values.xml | 22 + .../routing/Tests/Fixtures/map_defaults.xml | 20 + .../Tests/Fixtures/map_in_list_defaults.xml | 22 + .../Tests/Fixtures/map_in_map_defaults.xml | 22 + .../Tests/Fixtures/map_null_values.xml | 22 + .../routing/Tests/Fixtures/missing_id.xml | 8 + .../routing/Tests/Fixtures/missing_path.xml | 8 + .../Tests/Fixtures/namespaceprefix.xml | 16 + .../Fixtures/nonesense_resource_plus_path.yml | 3 + .../nonesense_type_without_resource.yml | 3 + .../routing/Tests/Fixtures/nonvalid.xml | 10 + .../routing/Tests/Fixtures/nonvalid.yml | 1 + .../routing/Tests/Fixtures/nonvalid2.yml | 1 + .../routing/Tests/Fixtures/nonvalidkeys.yml | 3 + .../routing/Tests/Fixtures/nonvalidnode.xml | 8 + .../routing/Tests/Fixtures/nonvalidroute.xml | 12 + .../routing/Tests/Fixtures/null_values.xml | 12 + .../Tests/Fixtures/scalar_defaults.xml | 33 + .../Tests/Fixtures/special_route_name.yml | 2 + .../routing/Tests/Fixtures/validpattern.php | 18 + .../routing/Tests/Fixtures/validpattern.xml | 15 + .../routing/Tests/Fixtures/validpattern.yml | 13 + .../routing/Tests/Fixtures/validresource.php | 18 + .../routing/Tests/Fixtures/validresource.xml | 13 + .../routing/Tests/Fixtures/validresource.yml | 8 + .../Fixtures/with_define_path_variable.php | 5 + .../routing/Tests/Fixtures/withdoctype.xml | 3 + .../Dumper/PhpGeneratorDumperTest.php | 181 ++ .../Tests/Generator/UrlGeneratorTest.php | 692 ++++++ .../Loader/AbstractAnnotationLoaderTest.php | 33 + .../Loader/AnnotationClassLoaderTest.php | 254 ++ .../Loader/AnnotationDirectoryLoaderTest.php | 80 + .../Tests/Loader/AnnotationFileLoaderTest.php | 80 + .../Tests/Loader/ClosureLoaderTest.php | 49 + .../Tests/Loader/DirectoryLoaderTest.php | 74 + .../Tests/Loader/ObjectRouteLoaderTest.php | 123 + .../Tests/Loader/PhpFileLoaderTest.php | 83 + .../Tests/Loader/XmlFileLoaderTest.php | 290 +++ .../Tests/Loader/YamlFileLoaderTest.php | 111 + .../Matcher/Dumper/DumperCollectionTest.php | 34 + .../Matcher/Dumper/PhpMatcherDumperTest.php | 386 +++ .../Dumper/StaticPrefixCollectionTest.php | 175 ++ .../Matcher/RedirectableUrlMatcherTest.php | 72 + .../Tests/Matcher/TraceableUrlMatcherTest.php | 122 + .../routing/Tests/Matcher/UrlMatcherTest.php | 430 ++++ .../routing/Tests/RequestContextTest.php | 160 ++ .../Tests/RouteCollectionBuilderTest.php | 325 +++ .../routing/Tests/RouteCollectionTest.php | 305 +++ .../routing/Tests/RouteCompilerTest.php | 389 +++ vendor/symfony/routing/Tests/RouteTest.php | 258 ++ vendor/symfony/routing/Tests/RouterTest.php | 162 ++ vendor/symfony/routing/composer.json | 56 + vendor/symfony/routing/phpunit.xml.dist | 30 + 860 files changed, 99276 insertions(+) create mode 100644 .htaccess create mode 100644 BD.txt create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 composer.phar create mode 100644 index.php create mode 100644 vendor/autoload.php create mode 100644 vendor/composer/ClassLoader.php create mode 100644 vendor/composer/LICENSE create mode 100644 vendor/composer/autoload_classmap.php create mode 100644 vendor/composer/autoload_files.php create mode 100644 vendor/composer/autoload_namespaces.php create mode 100644 vendor/composer/autoload_psr4.php create mode 100644 vendor/composer/autoload_real.php create mode 100644 vendor/composer/autoload_static.php create mode 100644 vendor/composer/installed.json create mode 100644 vendor/pimple/pimple/.gitignore create mode 100644 vendor/pimple/pimple/.travis.yml create mode 100644 vendor/pimple/pimple/CHANGELOG create mode 100644 vendor/pimple/pimple/LICENSE create mode 100644 vendor/pimple/pimple/README.rst create mode 100644 vendor/pimple/pimple/composer.json create mode 100644 vendor/pimple/pimple/ext/pimple/.gitignore create mode 100644 vendor/pimple/pimple/ext/pimple/README.md create mode 100644 vendor/pimple/pimple/ext/pimple/config.m4 create mode 100644 vendor/pimple/pimple/ext/pimple/config.w32 create mode 100644 vendor/pimple/pimple/ext/pimple/php_pimple.h create mode 100644 vendor/pimple/pimple/ext/pimple/pimple.c create mode 100644 vendor/pimple/pimple/ext/pimple/pimple_compat.h create mode 100644 vendor/pimple/pimple/ext/pimple/tests/001.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/002.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/003.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/004.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/005.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/006.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/007.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/008.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/009.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/010.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/011.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/012.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/013.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/014.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/015.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/016.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/017.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/017_1.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/018.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/019.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/bench.phpb create mode 100644 vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb create mode 100644 vendor/pimple/pimple/phpunit.xml.dist create mode 100644 vendor/pimple/pimple/src/Pimple/Container.php create mode 100644 vendor/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php create mode 100644 vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php create mode 100644 vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php create mode 100644 vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php create mode 100644 vendor/pimple/pimple/src/Pimple/Psr11/Container.php create mode 100644 vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php create mode 100644 vendor/pimple/pimple/src/Pimple/ServiceIterator.php create mode 100644 vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Invokable.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Fixtures/PimpleServiceProvider.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php create mode 100644 vendor/psr/container/.gitignore create mode 100644 vendor/psr/container/LICENSE create mode 100644 vendor/psr/container/README.md create mode 100644 vendor/psr/container/composer.json create mode 100644 vendor/psr/container/src/ContainerExceptionInterface.php create mode 100644 vendor/psr/container/src/ContainerInterface.php create mode 100644 vendor/psr/container/src/NotFoundExceptionInterface.php create mode 100644 vendor/psr/log/.gitignore create mode 100644 vendor/psr/log/LICENSE create mode 100644 vendor/psr/log/Psr/Log/AbstractLogger.php create mode 100644 vendor/psr/log/Psr/Log/InvalidArgumentException.php create mode 100644 vendor/psr/log/Psr/Log/LogLevel.php create mode 100644 vendor/psr/log/Psr/Log/LoggerAwareInterface.php create mode 100644 vendor/psr/log/Psr/Log/LoggerAwareTrait.php create mode 100644 vendor/psr/log/Psr/Log/LoggerInterface.php create mode 100644 vendor/psr/log/Psr/Log/LoggerTrait.php create mode 100644 vendor/psr/log/Psr/Log/NullLogger.php create mode 100644 vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php create mode 100644 vendor/psr/log/README.md create mode 100644 vendor/psr/log/composer.json create mode 100644 vendor/silex/silex/.gitignore create mode 100644 vendor/silex/silex/.travis.yml create mode 100644 vendor/silex/silex/LICENSE create mode 100644 vendor/silex/silex/README.rst create mode 100644 vendor/silex/silex/composer.json create mode 100644 vendor/silex/silex/doc/changelog.rst create mode 100644 vendor/silex/silex/doc/conf.py create mode 100644 vendor/silex/silex/doc/contributing.rst create mode 100644 vendor/silex/silex/doc/cookbook/error_handler.rst create mode 100644 vendor/silex/silex/doc/cookbook/form_no_csrf.rst create mode 100644 vendor/silex/silex/doc/cookbook/guard_authentication.rst create mode 100644 vendor/silex/silex/doc/cookbook/index.rst create mode 100644 vendor/silex/silex/doc/cookbook/json_request_body.rst create mode 100644 vendor/silex/silex/doc/cookbook/multiple_loggers.rst create mode 100644 vendor/silex/silex/doc/cookbook/session_storage.rst create mode 100644 vendor/silex/silex/doc/cookbook/sub_requests.rst create mode 100644 vendor/silex/silex/doc/cookbook/validator_yaml.rst create mode 100644 vendor/silex/silex/doc/index.rst create mode 100644 vendor/silex/silex/doc/internals.rst create mode 100644 vendor/silex/silex/doc/intro.rst create mode 100644 vendor/silex/silex/doc/middlewares.rst create mode 100644 vendor/silex/silex/doc/organizing_controllers.rst create mode 100644 vendor/silex/silex/doc/providers.rst create mode 100644 vendor/silex/silex/doc/providers/asset.rst create mode 100644 vendor/silex/silex/doc/providers/csrf.rst create mode 100644 vendor/silex/silex/doc/providers/doctrine.rst create mode 100644 vendor/silex/silex/doc/providers/form.rst create mode 100644 vendor/silex/silex/doc/providers/http_cache.rst create mode 100644 vendor/silex/silex/doc/providers/http_fragment.rst create mode 100644 vendor/silex/silex/doc/providers/index.rst create mode 100644 vendor/silex/silex/doc/providers/locale.rst create mode 100644 vendor/silex/silex/doc/providers/monolog.rst create mode 100644 vendor/silex/silex/doc/providers/remember_me.rst create mode 100644 vendor/silex/silex/doc/providers/security.rst create mode 100644 vendor/silex/silex/doc/providers/serializer.rst create mode 100644 vendor/silex/silex/doc/providers/service_controller.rst create mode 100644 vendor/silex/silex/doc/providers/session.rst create mode 100644 vendor/silex/silex/doc/providers/swiftmailer.rst create mode 100644 vendor/silex/silex/doc/providers/translation.rst create mode 100644 vendor/silex/silex/doc/providers/twig.rst create mode 100644 vendor/silex/silex/doc/providers/validator.rst create mode 100644 vendor/silex/silex/doc/providers/var_dumper.rst create mode 100644 vendor/silex/silex/doc/services.rst create mode 100644 vendor/silex/silex/doc/testing.rst create mode 100644 vendor/silex/silex/doc/usage.rst create mode 100644 vendor/silex/silex/doc/web_servers.rst create mode 100644 vendor/silex/silex/phpunit.xml.dist create mode 100644 vendor/silex/silex/src/Silex/Api/BootableProviderInterface.php create mode 100644 vendor/silex/silex/src/Silex/Api/ControllerProviderInterface.php create mode 100644 vendor/silex/silex/src/Silex/Api/EventListenerProviderInterface.php create mode 100644 vendor/silex/silex/src/Silex/Api/LICENSE create mode 100644 vendor/silex/silex/src/Silex/Api/composer.json create mode 100644 vendor/silex/silex/src/Silex/AppArgumentValueResolver.php create mode 100644 vendor/silex/silex/src/Silex/Application.php create mode 100644 vendor/silex/silex/src/Silex/Application/FormTrait.php create mode 100644 vendor/silex/silex/src/Silex/Application/MonologTrait.php create mode 100644 vendor/silex/silex/src/Silex/Application/SecurityTrait.php create mode 100644 vendor/silex/silex/src/Silex/Application/SwiftmailerTrait.php create mode 100644 vendor/silex/silex/src/Silex/Application/TranslationTrait.php create mode 100644 vendor/silex/silex/src/Silex/Application/TwigTrait.php create mode 100644 vendor/silex/silex/src/Silex/Application/UrlGeneratorTrait.php create mode 100644 vendor/silex/silex/src/Silex/CallbackResolver.php create mode 100644 vendor/silex/silex/src/Silex/Controller.php create mode 100644 vendor/silex/silex/src/Silex/ControllerCollection.php create mode 100644 vendor/silex/silex/src/Silex/ControllerResolver.php create mode 100644 vendor/silex/silex/src/Silex/EventListener/ConverterListener.php create mode 100644 vendor/silex/silex/src/Silex/EventListener/LogListener.php create mode 100644 vendor/silex/silex/src/Silex/EventListener/MiddlewareListener.php create mode 100644 vendor/silex/silex/src/Silex/EventListener/StringToResponseListener.php create mode 100644 vendor/silex/silex/src/Silex/Exception/ControllerFrozenException.php create mode 100644 vendor/silex/silex/src/Silex/ExceptionHandler.php create mode 100644 vendor/silex/silex/src/Silex/ExceptionListenerWrapper.php create mode 100644 vendor/silex/silex/src/Silex/Provider/AssetServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/CsrfServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/DoctrineServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/ExceptionHandlerServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/Form/SilexFormExtension.php create mode 100644 vendor/silex/silex/src/Silex/Provider/FormServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/HttpCache/HttpCache.php create mode 100644 vendor/silex/silex/src/Silex/Provider/HttpCacheServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/HttpFragmentServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/HttpKernelServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/LICENSE create mode 100644 vendor/silex/silex/src/Silex/Provider/Locale/LocaleListener.php create mode 100644 vendor/silex/silex/src/Silex/Provider/LocaleServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/MonologServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/RememberMeServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/Routing/LazyRequestMatcher.php create mode 100644 vendor/silex/silex/src/Silex/Provider/Routing/RedirectableUrlMatcher.php create mode 100644 vendor/silex/silex/src/Silex/Provider/RoutingServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/SecurityServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/SerializerServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/ServiceControllerServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/Session/SessionListener.php create mode 100644 vendor/silex/silex/src/Silex/Provider/Session/TestSessionListener.php create mode 100644 vendor/silex/silex/src/Silex/Provider/SessionServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/SwiftmailerServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/TranslationServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/Twig/RuntimeLoader.php create mode 100644 vendor/silex/silex/src/Silex/Provider/TwigServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/Validator/ConstraintValidatorFactory.php create mode 100644 vendor/silex/silex/src/Silex/Provider/ValidatorServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/VarDumperServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/composer.json create mode 100644 vendor/silex/silex/src/Silex/Route.php create mode 100644 vendor/silex/silex/src/Silex/Route/SecurityTrait.php create mode 100644 vendor/silex/silex/src/Silex/ServiceControllerResolver.php create mode 100644 vendor/silex/silex/src/Silex/ViewListenerWrapper.php create mode 100644 vendor/silex/silex/src/Silex/WebTestCase.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/FormApplication.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/FormTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/MonologApplication.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/MonologTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/SecurityApplication.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/SecurityTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerApplication.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/TranslationApplication.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/TranslationTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/TwigApplication.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/TwigTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorApplication.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/ApplicationTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/CallbackResolverTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/CallbackServicesTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/ControllerCollectionTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/ControllerResolverTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/ControllerTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/EventListener/LogListenerTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/ExceptionHandlerTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Fixtures/Php7Controller.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Fixtures/manifest.json create mode 100644 vendor/silex/silex/tests/Silex/Tests/FunctionalTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/JsonTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/LazyDispatcherTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/LazyRequestMatcherTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/LocaleTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/MiddlewareTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/AssetServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/DoctrineServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest/DisableCsrfExtension.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/HttpCacheServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/HttpFragmentServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/MonologServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/RememberMeServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/RoutingServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest/TokenAuthenticator.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/SerializerServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/SessionServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/SpoolStub.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/SwiftmailerServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/TranslationServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/TwigServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/Custom.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/CustomValidator.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Route/SecurityRoute.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Route/SecurityTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/RouterTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverRouterTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/StreamTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/WebTestCaseTest.php create mode 100644 vendor/symfony/debug/.gitignore create mode 100644 vendor/symfony/debug/BufferingLogger.php create mode 100644 vendor/symfony/debug/CHANGELOG.md create mode 100644 vendor/symfony/debug/Debug.php create mode 100644 vendor/symfony/debug/DebugClassLoader.php create mode 100644 vendor/symfony/debug/ErrorHandler.php create mode 100644 vendor/symfony/debug/Exception/ClassNotFoundException.php create mode 100644 vendor/symfony/debug/Exception/ContextErrorException.php create mode 100644 vendor/symfony/debug/Exception/FatalErrorException.php create mode 100644 vendor/symfony/debug/Exception/FatalThrowableError.php create mode 100644 vendor/symfony/debug/Exception/FlattenException.php create mode 100644 vendor/symfony/debug/Exception/OutOfMemoryException.php create mode 100644 vendor/symfony/debug/Exception/SilencedErrorContext.php create mode 100644 vendor/symfony/debug/Exception/UndefinedFunctionException.php create mode 100644 vendor/symfony/debug/Exception/UndefinedMethodException.php create mode 100644 vendor/symfony/debug/ExceptionHandler.php create mode 100644 vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php create mode 100644 vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php create mode 100644 vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php create mode 100644 vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php create mode 100644 vendor/symfony/debug/LICENSE create mode 100644 vendor/symfony/debug/README.md create mode 100644 vendor/symfony/debug/Resources/ext/README.md create mode 100644 vendor/symfony/debug/Resources/ext/config.m4 create mode 100644 vendor/symfony/debug/Resources/ext/config.w32 create mode 100644 vendor/symfony/debug/Resources/ext/php_symfony_debug.h create mode 100644 vendor/symfony/debug/Resources/ext/symfony_debug.c create mode 100644 vendor/symfony/debug/Resources/ext/tests/001.phpt create mode 100644 vendor/symfony/debug/Resources/ext/tests/002.phpt create mode 100644 vendor/symfony/debug/Resources/ext/tests/002_1.phpt create mode 100644 vendor/symfony/debug/Resources/ext/tests/003.phpt create mode 100644 vendor/symfony/debug/Tests/DebugClassLoaderTest.php create mode 100644 vendor/symfony/debug/Tests/ErrorHandlerTest.php create mode 100644 vendor/symfony/debug/Tests/Exception/FlattenExceptionTest.php create mode 100644 vendor/symfony/debug/Tests/ExceptionHandlerTest.php create mode 100644 vendor/symfony/debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php create mode 100644 vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php create mode 100644 vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/ClassAlias.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/DeprecatedClass.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/DeprecatedInterface.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/ExtendedFinalMethod.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/FinalClass.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/FinalMethod.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/NonDeprecatedInterface.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/PEARClass.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/Throwing.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/ToStringThrower.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/casemismatch.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/notPsr0Bis.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/psr4/Psr4CaseMismatch.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/reallyNotPsr0.php create mode 100644 vendor/symfony/debug/Tests/Fixtures2/RequiredTwice.php create mode 100644 vendor/symfony/debug/Tests/HeaderMock.php create mode 100644 vendor/symfony/debug/Tests/MockExceptionHandler.php create mode 100644 vendor/symfony/debug/composer.json create mode 100644 vendor/symfony/debug/phpunit.xml.dist create mode 100644 vendor/symfony/event-dispatcher/.gitignore create mode 100644 vendor/symfony/event-dispatcher/CHANGELOG.md create mode 100644 vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php 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/README.md create mode 100644 vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php create mode 100644 vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php create mode 100644 vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.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/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/AcceptHeader.php create mode 100644 vendor/symfony/http-foundation/AcceptHeaderItem.php create mode 100644 vendor/symfony/http-foundation/ApacheRequest.php create mode 100644 vendor/symfony/http-foundation/BinaryFileResponse.php create mode 100644 vendor/symfony/http-foundation/CHANGELOG.md create mode 100644 vendor/symfony/http-foundation/Cookie.php create mode 100644 vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php create mode 100644 vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php create mode 100644 vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php create mode 100644 vendor/symfony/http-foundation/ExpressionRequestMatcher.php create mode 100644 vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/FileException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/UploadException.php create mode 100644 vendor/symfony/http-foundation/File/File.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php create mode 100644 vendor/symfony/http-foundation/File/Stream.php create mode 100644 vendor/symfony/http-foundation/File/UploadedFile.php create mode 100644 vendor/symfony/http-foundation/FileBag.php create mode 100644 vendor/symfony/http-foundation/HeaderBag.php create mode 100644 vendor/symfony/http-foundation/IpUtils.php create mode 100644 vendor/symfony/http-foundation/JsonResponse.php create mode 100644 vendor/symfony/http-foundation/LICENSE create mode 100644 vendor/symfony/http-foundation/ParameterBag.php create mode 100644 vendor/symfony/http-foundation/README.md create mode 100644 vendor/symfony/http-foundation/RedirectResponse.php create mode 100644 vendor/symfony/http-foundation/Request.php create mode 100644 vendor/symfony/http-foundation/RequestMatcher.php create mode 100644 vendor/symfony/http-foundation/RequestMatcherInterface.php create mode 100644 vendor/symfony/http-foundation/RequestStack.php create mode 100644 vendor/symfony/http-foundation/Response.php create mode 100644 vendor/symfony/http-foundation/ResponseHeaderBag.php create mode 100644 vendor/symfony/http-foundation/ServerBag.php create mode 100644 vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php create mode 100644 vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php create mode 100644 vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php create mode 100644 vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php create mode 100644 vendor/symfony/http-foundation/Session/Flash/FlashBag.php create mode 100644 vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php create mode 100644 vendor/symfony/http-foundation/Session/Session.php create mode 100644 vendor/symfony/http-foundation/Session/SessionBagInterface.php create mode 100644 vendor/symfony/http-foundation/Session/SessionInterface.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/MetadataBag.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php create mode 100644 vendor/symfony/http-foundation/StreamedResponse.php create mode 100644 vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php create mode 100644 vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php create mode 100644 vendor/symfony/http-foundation/Tests/ApacheRequestTest.php create mode 100644 vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php create mode 100644 vendor/symfony/http-foundation/Tests/CookieTest.php create mode 100644 vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php create mode 100644 vendor/symfony/http-foundation/Tests/File/FakeFile.php create mode 100644 vendor/symfony/http-foundation/Tests/File/FileTest.php create mode 100644 vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension create mode 100644 vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty create mode 100644 vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example create mode 100644 vendor/symfony/http-foundation/Tests/File/Fixtures/test create mode 100644 vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif create mode 100644 vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php create mode 100644 vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php create mode 100644 vendor/symfony/http-foundation/Tests/FileBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/HeaderBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/IpUtilsTest.php create mode 100644 vendor/symfony/http-foundation/Tests/JsonResponseTest.php create mode 100644 vendor/symfony/http-foundation/Tests/ParameterBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/RedirectResponseTest.php create mode 100644 vendor/symfony/http-foundation/Tests/RequestMatcherTest.php create mode 100644 vendor/symfony/http-foundation/Tests/RequestStackTest.php create mode 100644 vendor/symfony/http-foundation/Tests/RequestTest.php create mode 100644 vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/ResponseTest.php create mode 100644 vendor/symfony/http-foundation/Tests/ResponseTestCase.php create mode 100644 vendor/symfony/http-foundation/Tests/ServerBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/SessionTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php create mode 100644 vendor/symfony/http-foundation/Tests/StreamedResponseTest.php create mode 100644 vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng create mode 100644 vendor/symfony/http-foundation/Tests/schema/iana-registry.rng create mode 100644 vendor/symfony/http-foundation/composer.json create mode 100644 vendor/symfony/http-foundation/phpunit.xml.dist create mode 100644 vendor/symfony/http-kernel/.gitignore create mode 100644 vendor/symfony/http-kernel/Bundle/Bundle.php create mode 100644 vendor/symfony/http-kernel/Bundle/BundleInterface.php create mode 100644 vendor/symfony/http-kernel/CHANGELOG.md create mode 100644 vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php create mode 100644 vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php create mode 100644 vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php create mode 100644 vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php create mode 100644 vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php create mode 100644 vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php create mode 100644 vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php create mode 100644 vendor/symfony/http-kernel/Client.php create mode 100644 vendor/symfony/http-kernel/Config/EnvParametersResource.php create mode 100644 vendor/symfony/http-kernel/Config/FileLocator.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php create mode 100644 vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ControllerReference.php create mode 100644 vendor/symfony/http-kernel/Controller/ControllerResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php create mode 100644 vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php create mode 100644 vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php create mode 100644 vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php create mode 100644 vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php create mode 100644 vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/DataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php create mode 100644 vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/EventDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php create mode 100644 vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.php create mode 100644 vendor/symfony/http-kernel/Debug/FileLinkFormatter.php create mode 100644 vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/Extension.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php create mode 100644 vendor/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php create mode 100644 vendor/symfony/http-kernel/Event/FilterControllerEvent.php create mode 100644 vendor/symfony/http-kernel/Event/FilterResponseEvent.php create mode 100644 vendor/symfony/http-kernel/Event/FinishRequestEvent.php create mode 100644 vendor/symfony/http-kernel/Event/GetResponseEvent.php create mode 100644 vendor/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php create mode 100644 vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.php create mode 100644 vendor/symfony/http-kernel/Event/KernelEvent.php create mode 100644 vendor/symfony/http-kernel/Event/PostResponseEvent.php create mode 100644 vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/AbstractTestSessionListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/DumpListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/ExceptionListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/FragmentListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/LocaleListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/ProfilerListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/ResponseListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/RouterListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/SaveSessionListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/SessionListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/StreamedResponseListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/SurrogateListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/TestSessionListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/TranslatorListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php create mode 100644 vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/BadRequestHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/ConflictHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/GoneHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/HttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php create mode 100644 vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/NotFoundHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php create mode 100644 vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php create mode 100644 vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php create mode 100644 vendor/symfony/http-kernel/Fragment/FragmentHandler.php create mode 100644 vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php create mode 100644 vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php create mode 100644 vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php create mode 100644 vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php create mode 100644 vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php create mode 100644 vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php create mode 100644 vendor/symfony/http-kernel/HttpCache/Esi.php create mode 100644 vendor/symfony/http-kernel/HttpCache/HttpCache.php create mode 100644 vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php create mode 100644 vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php create mode 100644 vendor/symfony/http-kernel/HttpCache/Ssi.php create mode 100644 vendor/symfony/http-kernel/HttpCache/Store.php create mode 100644 vendor/symfony/http-kernel/HttpCache/StoreInterface.php create mode 100644 vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php create mode 100644 vendor/symfony/http-kernel/HttpKernel.php create mode 100644 vendor/symfony/http-kernel/HttpKernelInterface.php create mode 100644 vendor/symfony/http-kernel/Kernel.php create mode 100644 vendor/symfony/http-kernel/KernelEvents.php create mode 100644 vendor/symfony/http-kernel/KernelInterface.php create mode 100644 vendor/symfony/http-kernel/LICENSE create mode 100644 vendor/symfony/http-kernel/Log/DebugLoggerInterface.php create mode 100644 vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php create mode 100644 vendor/symfony/http-kernel/Profiler/Profile.php create mode 100644 vendor/symfony/http-kernel/Profiler/Profiler.php create mode 100644 vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php create mode 100644 vendor/symfony/http-kernel/README.md create mode 100644 vendor/symfony/http-kernel/TerminableInterface.php create mode 100644 vendor/symfony/http-kernel/Tests/Bundle/BundleTest.php create mode 100644 vendor/symfony/http-kernel/Tests/CacheClearer/ChainCacheClearerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/CacheClearer/Psr6CacheClearerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php create mode 100644 vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/ClientTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Config/EnvParametersResourceTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Config/FileLocatorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Controller/ArgumentResolverTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Controller/ContainerControllerResolverTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php create mode 100644 vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php create mode 100644 vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/Compiler.log create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/DataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/DumpDataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/ExceptionDataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/LoggerDataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/MemoryDataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/RequestDataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/TimeDataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/Util/ValueExporterTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Debug/FileLinkFormatterTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Debug/TraceableEventDispatcherTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DependencyInjection/FragmentRendererPassTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Event/GetResponseForExceptionEventTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/AddRequestFormatsListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/DebugHandlersListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/DumpListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/ExceptionListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/FragmentListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/ValidateRequestListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/AccessDeniedHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/BadRequestHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/ConflictHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/GoneHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/HttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/LengthRequiredHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/MethodNotAllowedHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/NotAcceptableHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/NotFoundHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/PreconditionFailedHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/PreconditionRequiredHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/ServiceUnavailableHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/UnauthorizedHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/123/Kernel123.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/hide.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/Resources/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/bar.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Bundle2Bundle/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/hide.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Controller/BasicTypesController.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingRequest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingSession.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Controller/NullableController.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Controller/VariadicController.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/DependencyInjection/ExtensionNotValidExtension.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/ExtensionNotValidBundle.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/FooCommand.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/KernelForOverrideName.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/KernelForTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/KernelWithoutBundles.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Resources/BaseBundle/hide.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Resources/Bundle1Bundle/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Resources/ChildBundle/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Resources/FooBundle/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/TestClient.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/TestEventDispatcher.php create mode 100644 vendor/symfony/http-kernel/Tests/Fragment/EsiFragmentRendererTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fragment/FragmentHandlerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fragment/HIncludeFragmentRendererTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fragment/InlineFragmentRendererTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fragment/RoutableFragmentRendererTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fragment/SsiFragmentRendererTest.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/EsiTest.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTest.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTestCase.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/ResponseCacheStrategyTest.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/SsiTest.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/StoreTest.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/TestHttpKernel.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/TestMultipleHttpKernel.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpKernelTest.php create mode 100644 vendor/symfony/http-kernel/Tests/KernelTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Logger.php create mode 100644 vendor/symfony/http-kernel/Tests/Profiler/FileProfilerStorageTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Profiler/ProfilerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/TestHttpKernel.php create mode 100644 vendor/symfony/http-kernel/Tests/UriSignerTest.php create mode 100644 vendor/symfony/http-kernel/UriSigner.php create mode 100644 vendor/symfony/http-kernel/composer.json create mode 100644 vendor/symfony/http-kernel/phpunit.xml.dist create mode 100644 vendor/symfony/polyfill-mbstring/LICENSE create mode 100644 vendor/symfony/polyfill-mbstring/Mbstring.php create mode 100644 vendor/symfony/polyfill-mbstring/README.md create mode 100644 vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php create mode 100644 vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php create mode 100644 vendor/symfony/polyfill-mbstring/bootstrap.php create mode 100644 vendor/symfony/polyfill-mbstring/composer.json create mode 100644 vendor/symfony/routing/.gitignore create mode 100644 vendor/symfony/routing/Annotation/Route.php create mode 100644 vendor/symfony/routing/CHANGELOG.md create mode 100644 vendor/symfony/routing/CompiledRoute.php create mode 100644 vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php create mode 100644 vendor/symfony/routing/Exception/ExceptionInterface.php create mode 100644 vendor/symfony/routing/Exception/InvalidParameterException.php create mode 100644 vendor/symfony/routing/Exception/MethodNotAllowedException.php create mode 100644 vendor/symfony/routing/Exception/MissingMandatoryParametersException.php create mode 100644 vendor/symfony/routing/Exception/ResourceNotFoundException.php create mode 100644 vendor/symfony/routing/Exception/RouteNotFoundException.php create mode 100644 vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php create mode 100644 vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php create mode 100644 vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php create mode 100644 vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php create mode 100644 vendor/symfony/routing/Generator/UrlGenerator.php create mode 100644 vendor/symfony/routing/Generator/UrlGeneratorInterface.php create mode 100644 vendor/symfony/routing/LICENSE create mode 100644 vendor/symfony/routing/Loader/AnnotationClassLoader.php create mode 100644 vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php create mode 100644 vendor/symfony/routing/Loader/AnnotationFileLoader.php create mode 100644 vendor/symfony/routing/Loader/ClosureLoader.php create mode 100644 vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php create mode 100644 vendor/symfony/routing/Loader/DirectoryLoader.php create mode 100644 vendor/symfony/routing/Loader/ObjectRouteLoader.php create mode 100644 vendor/symfony/routing/Loader/PhpFileLoader.php create mode 100644 vendor/symfony/routing/Loader/XmlFileLoader.php create mode 100644 vendor/symfony/routing/Loader/YamlFileLoader.php create mode 100644 vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd create mode 100644 vendor/symfony/routing/Matcher/Dumper/DumperCollection.php create mode 100644 vendor/symfony/routing/Matcher/Dumper/DumperRoute.php create mode 100644 vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php create mode 100644 vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php create mode 100644 vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php create mode 100644 vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php create mode 100644 vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php create mode 100644 vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php create mode 100644 vendor/symfony/routing/Matcher/RequestMatcherInterface.php create mode 100644 vendor/symfony/routing/Matcher/TraceableUrlMatcher.php create mode 100644 vendor/symfony/routing/Matcher/UrlMatcher.php create mode 100644 vendor/symfony/routing/Matcher/UrlMatcherInterface.php create mode 100644 vendor/symfony/routing/README.md create mode 100644 vendor/symfony/routing/RequestContext.php create mode 100644 vendor/symfony/routing/RequestContextAwareInterface.php create mode 100644 vendor/symfony/routing/Route.php create mode 100644 vendor/symfony/routing/RouteCollection.php create mode 100644 vendor/symfony/routing/RouteCollectionBuilder.php create mode 100644 vendor/symfony/routing/RouteCompiler.php create mode 100644 vendor/symfony/routing/RouteCompilerInterface.php create mode 100644 vendor/symfony/routing/Router.php create mode 100644 vendor/symfony/routing/RouterInterface.php create mode 100644 vendor/symfony/routing/Tests/Annotation/RouteTest.php create mode 100644 vendor/symfony/routing/Tests/CompiledRouteTest.php create mode 100644 vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/CustomCompiledRoute.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/annotated.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/bad_format.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/bar.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.apache create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.apache create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/empty.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/file_resource.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/foo.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/foo1.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/incomplete.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/list_defaults.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/list_null_values.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/map_defaults.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/map_null_values.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/missing_id.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/missing_path.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonvalid.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonvalid.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/null_values.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/special_route_name.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/validpattern.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/validpattern.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/validpattern.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/validresource.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/validresource.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/validresource.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/withdoctype.xml create mode 100644 vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php create mode 100644 vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php create mode 100644 vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php create mode 100644 vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php create mode 100644 vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php create mode 100644 vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.php create mode 100644 vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php create mode 100644 vendor/symfony/routing/Tests/RequestContextTest.php create mode 100644 vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php create mode 100644 vendor/symfony/routing/Tests/RouteCollectionTest.php create mode 100644 vendor/symfony/routing/Tests/RouteCompilerTest.php create mode 100644 vendor/symfony/routing/Tests/RouteTest.php create mode 100644 vendor/symfony/routing/Tests/RouterTest.php create mode 100644 vendor/symfony/routing/composer.json create mode 100644 vendor/symfony/routing/phpunit.xml.dist diff --git a/.htaccess b/.htaccess new file mode 100644 index 00000000..5624e80a --- /dev/null +++ b/.htaccess @@ -0,0 +1,12 @@ + + Options -MultiViews + + RewriteEngine On + #RewriteBase /test/ + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [QSA,L] + + + +#FallbackResource /index.php \ No newline at end of file diff --git a/BD.txt b/BD.txt new file mode 100644 index 00000000..25919f9f --- /dev/null +++ b/BD.txt @@ -0,0 +1,15 @@ + +### Criar Base +CREATE DATABASE `test` /*!40100 DEFAULT CHARACTER SET latin1 */; + +### criar tabela de clientes +CREATE TABLE `tb_customers` ( + `id_customer` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(150) NOT NULL, + `cpf` int(11) NOT NULL, + `birth_date` date NOT NULL, + `email` varchar(150) NOT NULL, + `dt_register` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `dt_change` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id_customer`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1; diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..fd5e19ef --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "silex/silex": "~2.0" + } +} \ No newline at end of file diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..cc2f631d --- /dev/null +++ b/composer.lock @@ -0,0 +1,648 @@ +{ + "_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#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "85677501dca45c0b473c524c2f1a82b0", + "packages": [ + { + "name": "pimple/pimple", + "version": "v3.2.2", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "4d45fb62d96418396ec58ba76e6f065bca16e10a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/4d45fb62d96418396ec58ba76e6f065bca16e10a", + "reference": "4d45fb62d96418396ec58ba76e6f065bca16e10a", + "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": "2017-07-23T07:32:15+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/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.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": "2016-10-10T12:19:37+00:00" + }, + { + "name": "silex/silex", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Silex.git", + "reference": "ec7d5b5334465414952d4b2e935e73bd085dbbbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Silex/zipball/ec7d5b5334465414952d4b2e935e73bd085dbbbb", + "reference": "ec7d5b5334465414952d4b2e935e73bd085dbbbb", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0", + "symfony/event-dispatcher": "~2.8|^3.0", + "symfony/http-foundation": "~2.8|^3.0", + "symfony/http-kernel": "~2.8|^3.0", + "symfony/routing": "~2.8|^3.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35 || >= 5.0, <5.4.3" + }, + "replace": { + "silex/api": "self.version", + "silex/providers": "self.version" + }, + "require-dev": { + "doctrine/dbal": "~2.2", + "monolog/monolog": "^1.4.1", + "swiftmailer/swiftmailer": "~5", + "symfony/asset": "~2.8|^3.0", + "symfony/browser-kit": "~2.8|^3.0", + "symfony/config": "~2.8|^3.0", + "symfony/css-selector": "~2.8|^3.0", + "symfony/debug": "~2.8|^3.0", + "symfony/doctrine-bridge": "~2.8|^3.0", + "symfony/dom-crawler": "~2.8|^3.0", + "symfony/expression-language": "~2.8|^3.0", + "symfony/finder": "~2.8|^3.0", + "symfony/form": "~2.8|^3.0", + "symfony/intl": "~2.8|^3.0", + "symfony/monolog-bridge": "~2.8|^3.0", + "symfony/options-resolver": "~2.8|^3.0", + "symfony/phpunit-bridge": "^3.2", + "symfony/process": "~2.8|^3.0", + "symfony/security": "~2.8|^3.0", + "symfony/serializer": "~2.8|^3.0", + "symfony/translation": "~2.8|^3.0", + "symfony/twig-bridge": "~2.8|^3.0", + "symfony/validator": "~2.8|^3.0", + "symfony/var-dumper": "~2.8|^3.0", + "symfony/web-link": "^3.3", + "twig/twig": "~1.28|~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Silex\\": "src/Silex" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "The PHP micro-framework based on the Symfony Components", + "homepage": "http://silex.sensiolabs.org", + "keywords": [ + "microframework" + ], + "time": "2017-07-23T07:40:14+00:00" + }, + { + "name": "symfony/debug", + "version": "v3.3.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "8beb24eec70b345c313640962df933499373a944" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/8beb24eec70b345c313640962df933499373a944", + "reference": "8beb24eec70b345c313640962df933499373a944", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "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 Debug Component", + "homepage": "https://symfony.com", + "time": "2017-09-01T13:23:39+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.3.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "54ca9520a00386f83bca145819ad3b619aaa2485" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/54ca9520a00386f83bca145819ad3b619aaa2485", + "reference": "54ca9520a00386f83bca145819ad3b619aaa2485", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.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": "2017-07-29T21:54:42+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v3.3.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "2cdc7de1921d1a1c805a13dc05e44a2cd58f5ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/2cdc7de1921d1a1c805a13dc05e44a2cd58f5ad3", + "reference": "2cdc7de1921d1a1c805a13dc05e44a2cd58f5ad3", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.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": "2017-09-06T17:07:39+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v3.3.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "70f5bb3cdd737624249953b61023411e26be5db7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/70f5bb3cdd737624249953b61023411e26be5db7", + "reference": "70f5bb3cdd737624249953b61023411e26be5db7", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0", + "symfony/debug": "~2.8|~3.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "~3.3" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/var-dumper": "<3.3", + "twig/twig": "<1.34|<2.4,>=2" + }, + "require-dev": { + "psr/cache": "~1.0", + "symfony/browser-kit": "~2.8|~3.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/css-selector": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/dom-crawler": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0", + "symfony/routing": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~3.3" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "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 HttpKernel Component", + "homepage": "https://symfony.com", + "time": "2017-09-11T16:13:23+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7c8fae0ac1d216eb54349e6a8baa57d515fe8803", + "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-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": "2017-06-14T15:44:48+00:00" + }, + { + "name": "symfony/routing", + "version": "v3.3.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "970326dcd04522e1cd1fe128abaee54c225e27f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/970326dcd04522e1cd1fe128abaee54c225e27f9", + "reference": "970326dcd04522e1cd1fe128abaee54c225e27f9", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.3" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/yaml": "~3.3" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/dependency-injection": "For loading routes from a service", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "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 Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "time": "2017-07-29T21:54:42+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/composer.phar b/composer.phar new file mode 100644 index 0000000000000000000000000000000000000000..508f9cf274be7a7526630f9ef01c183e6b17612f GIT binary patch literal 1852323 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 z

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≧je4X%n_kBB=*03I@s}HS%LJCUqZCDzdt;m@n|@=YazH$&^T;>zhFav z<4oWg*r9ej2%_)J=wvj*$>8vT{lRII8L2n?4g=uOg;AWI#-cWr;X^;LyxhgMYwzUB z_N0`Wgx5?^G*#$OCyO;|C43<_oZ$gshd$Gd7f9EdG>&x@va`Of$q)u1d`!S-L0I+N zk<9Cwj1tDY&VU5;0+t7MR3Qq-3=(KKS)xo-h88TIo-W37*yX*Y*ZD)EOFXwc1`pt| z|43%n?bNYFesW-%2#E~#0W(>n#M4baDDR`pt7!-dk8~jdwq%jSWD$yu_^hc7cORX;Tct$*wmW}YF*@v0* z&25*0Y27TMu>*rZuhugq%nyxsimwcOd_c7Q-q6bWmKN?*H21PBJ9rb5C_RPIg(5yK zctCK@W_2}vjH#lZ^Rt}MXm{}m4=ZcG64;Z=oSfBFv>BWYX>p zE1hAfN3`_oTg(I;quwyv(zjd_u2f72|bI7IVCb7e`B);o@4{sRtP+oC(s% z9hm+mFmz49l$gQ=dF44=X`YzQNW8WJ&=IRjaL#xR;=tiFSQj0+AOPub0R$ymiP*r4LW`YyJS>R^deWAu2;-*N zzu<;qb`0S{2`&30IqL8J>AI#MFFOJ*($AViz6W-IW4E>(zU)0y-dBuTr|v?++Akht znkm_++9()1{|rcwsfAj?=m6UfNt5lr;X&ry$_xp#5gvpg&&DHMh&uXZ2(+M5LR}v| zC9xj8aB6V2Hs|~uco;AMS-G_0CTGO%i(y0Qu~a{dRfc)S){(FmGhMlzA>d6pa@gY<<^)$ zbh!I!Tn7zT0Q>3?#*_yG%}h3uELl|uACoKX?uv@TVtI-lvXM0|h-bKfj{IxZpF|!( z1fD=0NC|1wuRmW}zOA{+i}$3=Ejzv;}k5kdQP@Ghf38+=AH;wNQq$ zfnXM-Wl>gx%%uTsS`twv>MeYTDV|yfF$Rf;HUc$<2hRX%+JnU`qAPuEe4Mw*KY0&80e zjT&wuU%D}n(y(bB4>A~Kg>Z{zabyf-P`F~t&1bQhkqS)=$2WMe69Y;b!|_#-wQ$J9 z4h%#?Zh&rdN#TtGlVo7|TVwM$csV5nQD-PbZXRl>{2V{?r)e~)$`d|n9pGj@WKgyu zZ>Jky%dqydGHfmB-{WQ7Vslt9+c7M>cMo`}A=S&M-g^wu<+#PQSI$3GqW5pPUKQGN z+2{xaPt<9GbP(O~G!aA%8|026ktr&4F_@mGh1GL;SOKh~QDsvd;@V=KgRkx}#Gv~E?rvbj>&&TXUR5?5?=vE7^pq*&@qBfN&$dDJtDtbM&euDQtRPHcVgG` ztl`*kWZJSJtUzu-;h^*ZDvSuX49A)Z!gsv#fqtyiL&e%|JK5#wL$rF$HZAquK-M)B zoTw0%jk7Eq*CU`7+P@$S)qKfs9KX-K_w4e5 z#p1L8pdtm1Ne(BMM|^hwOEF0*jRc_2akHy39Tdw%@0Myo|B!U2&97 zgtvZGyy-sxhryr8yG?-4$C!qfD-a0sDjsz$3?21}^A25I_PoCXdDzLqy6y#l%HcvX zc1XN$O}Y0`VmTAhe@64n;IKdG5yuAmxaSFPKrUHU$j4$Zio(hf`UJhO)s$$%!Gy%$ z5jL#(4m`*;H;Z#~w!H)U1_%kHFedIyxF70ctW_?z}{BqBA`f zsfz-%)RQQ>-Z>qNu@C9baJ2SxbbvyPoxyAddki1oezD2Lc+8@ip5odW9N|Qr++s_w z1}Zah30W-d{n-Wo#?|jy^a(vf`wV_UbxlQxZ$$qCH1HT~QCGy&r-CntKldM~;D0xm z5Ee5jjWLS?)7`JF)q}rk>uSuxs(luHE_c6P@lO@tDgX?HTr#>8%9c91l~_qOu6D&i zH<`L=(WXD7F z=b4Cx_QkSa8B7tDe;`RZWovK_`5atR)zB36VWTcHwff*ObJN9yXazq))@Wh~hK6a( zza}q`QmHu|kh4n@_!R3ctebIe6@V80tslx)oIVAq|2SVBDm&xoo-3c$1(6j~>DtI4 zRTHRWg3EX;H?$xmE>MOZwshysuyY7o=)HsxhX=T)%XWCAc8?~$gMUK|>jPy{h#>q% z5DbfrJYW$rr-S$G>$BD-S??!eyCaxlIq zuALGWGFnOYKjaUg**43u%;a@}2OyjZ@Mp?Vv=!+}b5jVD`(OL0@nB)ZXZ3WUzk__b zj(xOfr<||=<`APFR|13nTO@A!)2rL1IEm^YNrVnVJWI8K`YFD+`z2)uq~FD4Y-|J~Z{;M82}m5a|Dnn| zgx@=un=ls=S;WPq{4&j6BHzRlm z!d=2*>Vy~cZ-hK)dz$s_pQWDU#9FF_GWhT`bHsEi}=ONNtW;wK4ZA+>VLn;>mV-YU@u*p)-xw4l8`W+39i)khU3 zv6(shqD+*e+eo#-=6fwU&B#C1zQb(PpTar$?#_KI{RhcF!OzW>g$)!A1c37VyJ_|V90UdP|S%6Yd!}i!1X2%mE!{z zKWM_f6xA7LKD2-@I%P?hNblozsjjbo+`Jzj;LTNkmNMKhwhwA(eZigMtap7wzV@#jIA%G~Y)*84n5D|#oWYYHFCfk~D1TaiO^Zd?dFyYOt78UVT%XCvnf85bBn`Z3IrBq-Lw8TjYLh%8hnBl4x%zs zdxrx8ql9Cbwi4Q6P(}+gb z^J_hoAG-*zjzGT%OD9srm6cA#x{g>1GbqS3)p-a`LDzdnrJzW)*no=DD`j7<>a=4j zJN+xT*jSG!rk~>WI$Q-b+V7#Xo#&zH2UUvU@EeJ?5xH(XIl>(2$6C%CKAy6G^oKp2Dip|2>~dTuYQJBb%~Uyk+9_7Bt>? zhCB-tKORF%3M;re>>LcS@gFhAvF_H=oEOhcYI4uaqM~KoVC$>PPd4f zwbnHxV8&_dM%mfJ*2`#U*WH-4YJO9{o9DX=T=zPh6c@}G%X%aKcT=is6m8|I94{zW z`)+c!k3lwmMlugFIh?0uSJ3!BE;?q%m&=V*eJOS?CN!zzm#GHBkioK6>Q;!tLv#KH z%n-)#QqG@bPbS!D`I=@gU^a8!Mei1_j98dMw#6Z`EA4Uo0CxoF*e=d|E9n{Z@zeCe zkI19DEyty&=fi#6bR*}b{|giUFS4cN$=YdXTuDK5nWvW%cJ_296*P+C)Y4MTkz1Z7 zPBq!hiD`9p`1AQ-d>~gT%icui>N5$}pkdku+05yUHz`11*$DaK+84$Xw&Ie3f@#uw zgxoGv;MNXt-5!i*Ypbk)jZEk{Vw!R@{t69Z_}r4EW1F3>y=0&PiN3XOQeRxteh%uU zq^Dx@3|&)oMLYefwj#M#4soQ4saao@Y6gS9nw)&kbkI;zAB$|;2#^i#*uZ7kq#!AT z#)R2NPq#MKU%uG-+uruZ=GxX8{say^0guKZ34%!GXUn1bq@Q8eT_P>)J=<+IK3lW=bI5cD7 zFs+tj41r#Nw&D0KHrX$}-+ZyXv9-7L;>F9ozie!6KYj5WDb`$dx&#*P>N&xTyXYIB zf*AIAj2%?*wy#_CCmU;z=#$3m@8KWfW@!O&#p`x*U%r;^6by8Hf59kH!Y#=>i5tex zY?5ZtiC@E*xCS@U*ISKGKH@$m>+SmFH8%3XFB(nmbr0etz3W#?PfVNX4 zwG1aXDGP31MB>;va>tksqwx@UPjGn*2Llh)G8>-ayufIN3zOzU^U2v!r$4^e>GzRu zJ)6D%=CdbUFdf=wlOvd?FmUESA!!C=_39}7jy7h>XGWq;_NcvXHruG0qaF`2{z5=O zvgL)a_f7}rz0ev3zy>0`OoZzFKnO91*N+F|@$h6z zGP{T!$=gOPmGviU&!2BR+k3uE;<6+7=NNOD1yshv*~-3t+P}Aa7qka?im%q5J=;>{+btg-%nnvi%xJa@e+Cn^kIr~DBJ^W@{LQZ%-rjrq{PBx<`m@eZNLqMZ z@{m*L><;63VY6-`WSrsF#>U z90a7@;Z$5^Dx57(Q2KJ?KnAJU2fS<*>jg`KYO}Y9^*S0cD^3OWuYO?H?uYZ&U=2AW zBd2yxQ~>Y`XB6(z*s#Q8Fi>!7+~Ndq1Db=@D_B;qN_)&piMMe~rtY_JzCCxwS1{|M2~@ zjT78rH=f%|tJ6uqvci{uCw#+n!9Q|ztAv%?nD>%;{6IBM}` za`w}3yxi&e9v_f!m1wzj{_C^pe=Ski3Jw%~Ztemm)!)|>q_PCMEj6L|6uI0qh5eRE zJ(?DN!X6ud1;cL_735O>R~qUmUwUeR^kMgna8i1CWx}8wlf0p0yB6fgd}<)rWB;6Q z{BN-mYQceKd@dCYF-1qA3I!d@#X&g@e7DB&YqDMpoR%*)gL}O)Y6|gHeK6NihS^f9 z{tJ#vW-nDVV_0a0tA>;B2j`T(MNlGLe^iBZf$J8ytn*?q<3fCVyB|-?zg-{Z4V|b z3iJj}`0j4<&Sqv+k%Tg9c!lukh7ojPQqHJ66^UY3-EvE084}KrGh=fqhXzTjJZ>U7!S^&_@kE zcDi=H5qnrrf(f>Si73>u=U3a61cE7~q=28*q0yBKwP=BbT|>=ET8}?8EGUUOHsN%R zuwfcbe%OR*M4)iOb{`lXPk~`{f4I6D?Qza4`e7>Qmy|%;LdvELKDOEMOOPN|;?{V& z3SRIl{jeijWR+uH_(MgG;Q^HD zWZ_bZK&ZK3&JfK3N~XEgdC-w7g09?uoK2iOD<;`^PLy(+$kj7$YL%Uz!EU%R-sLR+ zEfinWDXr=+XupFY?qEKEo9g|R=s{3pn-cU5N2|_LyQAzGbV0`5RGZ;Z7}tHX8B-Q| zSOtVFI_)V#dQc*$>g2Y_qBqMrEKdut$Lc5Kj0hc&$&bdx2DPV7N15jA$=&LLaMCtwrq0a7=m1c1mw+6gQH(Zq?Q8^N_NiZgu_vcaqlQZ~VV(`aTZB#5_KQRRUNfsGpu zIh{jdjBdif{;iGJ7E;ZA$mM8>rUIgk{nXyO&enyCm*EO+vJTw*G`Pi-uY3&e94HwdF48r2lJy#L@W zh{76$s}(ZR(Pnfz8Jdi@{VVI5u3idmTLL4RvgZ~82%?^cE9xZ+QIWUNPO~tf50ftd?r4xO!xDl(99$8 zH(Xu&lAq6K$5&w#e1M$MKH>$7MX3^|A$#*>$)qeT3IRi zyCIqx28~~~*k_PZK?R9#`v-bi1C7D0uo5E^?pwH$F_uL_Ks$}**nTOiJ&I8=0L&iq z_l~M`E`x)04XC{Lx5=z<@>zl{@7Z(45~2gWPM$IfgseCBuJ8#o@Kx=$xdCcgt_X(N z)cY&lWo%YG$IIW#tWYBC+-&<14Av`XWbcL|jmQyOX`aowhP1Ve)m~hnG#CZ$_6>&G&Om;3d|e=1ZcOW8Nsikuy?KV-#uxD=DEN z&8{1qaDUAIN|fi(KKmYo6242w#JlHG`rl!x!v}mDeh8XthQ&&6*)y83#7dHt^huW8dJ3X%AhN+s3j8rRvb`7U`1vU zxf+90h8#_wu<|GM+C2Rk8yC>tyDNjmPNIyK#dIrMhb%t6Uw-WCZK-2*l} zu~%Qqs}pIdq$wc04bT|4i`}mWIS+L7j5sQ=asis=A!(@S+0kS$aC*0WS_LtRnW^vz zDqhp06k!64(|03;4dLU%Q*B7KzzO40SOhw8_0{otcU2ITC_>CZUg!cO8j3G4cPo-; zJ^XZ3+d~E2rb`Xo0iy62hFdqy6SMWthmSKELu2zaTL#P!KD65D@I~EpF6r-Vitcot z(*d@swSCuioZtyZxdsTdUh7-vBT8J9`GZH6z$VMYRfJw-Bw4n=x!D^@61FTpB(F;F z!C4|U(8o#7`XIr)8?UY{Y}iEFy7?I#QiYtYNNUJg>jhL51YFeQ*;7MdP{QfUW|64k z3HdlS{qH7c7w0Y`k@lbHLpIgd!W+q<2|<65NzR_qHQ70cy7UeE$iNESr_wqFkKD+V0;q!4g+LNC%vv5VprZT`qXWS?6k}8V5)kO0JQOT4KKlS{Re7 zPLZjA2Nil>!HTa9-%QKF!M%v{A!yM&{DwFAC;*`L{f8j{T1_s7f}2CckVu3`&Md{k zi5Eyu#1ADSj*803_X*HoixXsv{np6L@MJm^x;-Mr2c6=?#8;;85)+PZBMW3aX4YfoH}xd|M%y+i`Ry~^2Y$J<+s@~_yn6CV5ZS?lRT4aA9pu)Lj8PKNBA zC|7r2Pe@(BvaO)3c{gBun{Cw+`5*ib6$LOaXWBlcN|+)Wg@{a^gk^R@(&a8B3#(jY z#L0d{{$jBnMT420B)SiscN?vR_+=zmeqRx)r1HzeZtM(@x?KEYGf<6xis37!YWhbI&*ssRS5vq@+sf~w{zKe zw39`Tc#cSWl5Mt0VA*73NW(sXbo5L4OVg$j^MGj}+aX`s2A#GrCdW1g`5_F-@L;MaS1zW9Bqx4XQ1 z@c9xRy#Alkrrzwjya(-?J2{z88x5 z@Gz69u+rsk3YD5I@%o#MT0FHZ$fRAF4Vk=3vLchG9zCPO%~4q-IcE`t1Uw)l0Smu8 zDU)|s<*A`~nGBC#J}j5S?6{<7(qi%xD&M&{O&?B24^0dP_7JdRUzb3!P5CVJlYq;1 z$z%I24FrvBq>^9ZF?cgN!R=xRQ$cT7svuh%Yn3FCu~q0r zI3@i$;gHZsC^wF4^UX(9s!e-prHQm*%|hgTxAe5_VAXUI;OF;OT@he0D?-p(>uP&= z3YC!;oT3?_&;JtC|A*DA+6w2}lA=#$)hWX+A~LmqgeaT)>j@>pUcRvu$0FHC^j%POx$KyU%RsnVRS zPDRah6S4drwWhSdL}k|SAEFLLun$I(UP-!hg(OQPD}^dM#uTfodZTl>qQQZT;%rRT z0awMY69NQ0wtr3G(FT1vR%`*m=GV;V5^LJv%=iKQGwzkiOnUx&PJyz~Fu|TsK*<@B zB;uScH?-3~Z1;@0@#pl|qk%=|N^yn_+`FopwCfa>zz;bA5 zhg8g7rKna+@M}Lpoc0_}kHpHWxK>fvOq-Qphr2indab*Ylm3o)d7zXzI^fxd@|NcE zibC5L5$$ePlWz?r5^AL8I)i{TUBZ%Ps_!^u(5M2R(GIW^ck}0#EG^r4`;pB0vYJax znI;vmY^h`+au92@f7p(z@E2n%kDujU?qtexnkOJbbF6JzkHT?sPOyT4_*(jiCT!~I zuoJ{m(}!~rS%<4+e_cR!Htv1|8HXYPT&WE4C_mtB;9F922a6F&uc>rRJ0-3M;v!{{ ztSS>uvBKRdYml>{0b#edR_+Yvl|q?nTwO)Qw{KDZ4PZn1B_;N}ke;EQgS1nsfYAV{ z5sN|!BA#PD00_A$$WAd;g~1c`mU8-{>M%5L7qWelYBx9=M3YJa57x2LgzktCpOw-( z!-F0IRS4KlPk7}xGFU{3cj;u2%M>A$efL|s^^i{Q^@0wS};Kc;NRyYanN*$w|}jLiwekGBGf0s zJC8C)MQ_RUk+QE{P7t^Lk#2;G;){gz=3%4wVeC;x&Nvm++So7|P>bj8r#$7#Ms+$W z?{fDMhtj+mbI`=c3PI>c`#s@J?8>EW+!g^%AQLWhR&SdP1BW6-VmIwzrAbWn^UyB2 zOqyI5@~e;}=gHU_e>#$Og=wid%U#OP2zf$imTlrLycXJ_9ET)#c^br6=96ktDZ!-l z+Ek$FXj^)p{RfDd3i+wfKN-E53?}boUwp1x0BMrCq5a#!Fix9DiR|=ycN(kN(9nZ7 zLe*dh@$_@J&J0Rc{HsEz<`aU<`bYWs8fwtf2DWogU2$ij$g2dk6{Id}6&IsE9ZPVS z2S2C&DO?6+vn@YK5qPz1fi+eZK7O(Fz1I#xdE4(cet-l*&8^cLiNgQx0~%BK^Ujo-lD(5pD{_2vDDKqbIw!hz~Wz!(>G#`!BM) z?KBccwN|IAry&Rv|JaiTOV6zPvEvkq4)?xB{-%9j~SqO7jbog#e^Q$vY!!xOMp@Zsv}=CfDdJ$=5nw)xac z-$OE^58@i_>yV#0eX;>CDAa^)2!-R<&PTRZh2-glixB$f(z4HE8Bxy~hP_j^I zN1f^W(>Ew3+6=s^6yaIU0eRY^F@0*B%->&bqfi1|ZuZ0H1zprOE4;pZPH1$M8T}nw zyUxpF#vwY}gTvuWjp*9-K-X9j5QZ` zS!jQaH1yS%3NoFk**dH}Mjif*mtSEa;1_cH1A#IWKC=S+nHpKfAQE0o$yeC$={t+%2bZ1}+%5*Qq>=HkWi zQ9F}^6VakUqyE&mPV%9f6azM*@L{80s0HLqO+1PAo*aGfvu1{G zEH^4b)gjz2OnCI-<;#snjQL z{IT?KcY3?GbGQHR%dlj6JA?i&uW!SmkzC@i+wW;Uu*)5b4aoF%kTA^WSj~&c*b;}4 zS}bY`GGAz+NJjbPwv4kVxhPpL;XWN)$4>hPdr~Yp7#x2X5FC)mW=2%xf}S+(ckV59 zZg=i~^(RK|vL-k!b~Zs7cpq-ZKR{k`zw_^3VeQK)EO+sLSuX(UPUpqu#&clK7iRC} z#t$z^I%)POfUav+F5)?oLh2|WT|rs6-EEqg#Ftr_>suQR0LHdci$+-&7o&sPUXRaR zPIRSB<;$NDYOEnB66E%?mwQbV3DZhXX2;qFo%^zYX3Ym4y84c0ZhJjfZ{RMI6_uS2 zUeU)&i3LVB&}uXkGK=3-41;u$Nrpj}ge@Sx!V9y}g3b5Kvn^aayv@GH5*v*xlo>;3(4Tc*#k;S8R z0%cI7$S1oaTkhae3!YB8gw(LHd@ZFAcMRYNuDO`RczO;Z>@E2TT1<~lD{46-ENof4 zPI$QTE%^}Q;I>S~yGJS#{PL+sJ-8(B3aT!B#hi^EayBmpC*to&0ozQqGqXDcWhNqp zyjv$JyHiM3*lRqWB_bSYMGbR+E#Eh*AWS6Upx)C`qE$yE8+-Htv(;HBl1ZYW_dgCz zh)PO|WXU7MR&txjM?6I1@GNWU9OIO2WML6?PyT? z@gH=)^{tex%wHbX!n$0xtpV?&!ZRRC*o^3uv`cqweTba&V$r*oWp>p-5t!XgT4nl_ z(aGj&y^9JETS@JYgU{u#nfwpTNIQA9Bo-XnMlUCbK|ZTD^3D7b^DtQV+4n$KmsbEH zL-9ip6{DZz{>T)P<7hM|azV(N)DMg`QN9re+h??%0d9R%DV$Exe5Z$@tPTe&19aUnDj_ z^@?pE7&|c=1V6AFJA0QT^t($719Q`yE6emtP5D!<;&VkEDAuAyGh~;D;0vC8t}s0Y zmSXBaaIM3!<6FEE_?BpB?3RvYgPZ}px%Tpj+$@Pr4z@kGn^!@QtAuqkPV90^Qsn6IZZB}b4E0hwgAN>T zxZ~0zIk@Q3$!wXIry&g`kXrXwz)MT;;tlrm=}31vICXnZ=t{0}N{HKOYjqWRd^+UM z^vj!=H&~>8-YCl0nVogs3^7f)E?kfRFYCC&^xF@Do3+bc zL8l8j-BVDyG{~02k0DhO5Wrn3&fsGg!JCLN+LON8ZgE4L)0;y$lqcL_3@g$U;bneG_K<}W)Q+K{6 z=%!S2L?M-Ci24-L$sC9m13_1m!w#(4)SUwu@Al~;ne`p}tSYzIvf+JVm z@~9CVu}Ta0zBsC+{S$ivu}ETc#c%X)yqHtoU>{Byaxs<&1Ehpyjt>#i!*&-Ol}=Ka zsxl330bmqn#JVwQ(0m|m29K~e7shMOMNMOJ2|FP71GQXvgTyq5P8-!q_T-a zQx7W%5toO_u+rJ_KwEgZJ;&v;5k5Bb9_ z@59ImSe_$_k7N(m)ZN>CMpkQk`pf=w&Q8oUXzIw!{&_n?+E`;x*@4#9i5-n*O})%N zF?4c#@#aSQIT_qQKhw7omaSX&`6qRFHl2=6K73LrD~!q9m1*B z)YF>1pO*?@l{a;@iFbK7F2TUHE|7e~s6s8u;8eK9P7;+7^4=yv#p-46{GH^-LCM9) zXwp`KqHtdhe-0ZER~;xB3KI}X9I6q;@=MItCf`P{drQWqa)Z>p)j{zI9)qNX23u6F zYr;jdq0M__i@<@3)1XLAywf=wpS*{)HpRHGWs_F#VCczlVO24r++tNzHk=>AtsntA zx3KA#`7d{Gp)!(~kb8c|jPwe3OU)c02AAQx%wZ2J-X* zsh!7zw={)0m?Cy~U^>}ck-!;3u+S`A^t7@TC+o++agI%VOL)dqcodrIu7MW6TMe>$ z<-{*xHY{m01Qdp_JX}9!uZ0Ki-4<{zCw%d3|G_1L@pId*c@Mb=)tF1%lvS278K6!8 zaI5S+H(5J#O|sI7m+C$@`C+O+9jDpEYvB^fQO-SCxN_AHw%T$wdf$tGCW1ENU1nXK zmY(yA%~CQc$Afm~5w$qbJY$OSws$d}9*+)TK~y2gkfbPZ2~%0ws^+E6Rm}j_#~)%R zn1shVo5d#5DhjULu+oWEL3@PFW95}2JwIBGVW3=bsw#!huMguyZf zl%;pNN+4)%r9dV5RZJM;^`Z#~R-tb!Es_95>y&7yGfou%2K{05fpP7$1R&|4Bx?s& z7}6l4@eM~X>;Ig+8>d!vw7tJxc{iAhVRk=Idl4=t`Vh$R?bwF!g-kd+`F=P%K082r znr2DvpFYRgICcliF_Fx&Cx_2?a4O@(Uc`BWm^h}6Wf0|^pkBY&VX;c2E`W8}0e;F? zqGE7FwegL#dXApah_3GF&Zt$AKyMIYoSDu;kNOFj`f7q=^-o=x@UxKT;kmkQqC@K@ zI!}47WO*=7q%6gfunPc^rKyuD1!IvIq-_)}@=T7(Kma$Mr>BZqo6b~?6-$V7e*Ds za5O3uV~b^q0MAv2+lAK$%$nJS>&TTxZPV=8BghCzo}TPhFV!yEGS;)}@t@+IuoH@o zy}8)`X*k2k1ICo#b?NAY!>dO~;()`{cQF7{pJ0y3kvLpTE3n2xa*F_zXCad(lb}Si z#}JTDr&H9g&Yn)r&Sp=M%K~4}sOV=lILg~1>wAph^M{PY3I;)08sP;4CeZjtucY`3 zZ^=nsa){a&$7iR*+86JJZ(x5&26x`o{>j;R_=Lsd;&-I1WHhKH$uL&TVlk3;Z}+D! zq2ln6-xTR|N>w<#( zUI?|JSK!^y_4L;&(?xZ_tIl5fB(?s*2nVxpgOGL-K!{IBq2X7g&N~Z!D`y?fA%HLs zDat0^5$!!+!bC0E==pnrReBLYmkY0qV4@&Qh$3wMO`tzQ^C!PO&{3 z&klPFTJ#8XfCGxl8SYo^FW+4W^M=S@`HcVX;b({>1hh3GiS)H6$uoPhH}5UzvcNku zJUOsS>G~jLhIQ}sg?6|e5_?7fU4roSzZSoi1yE$XzkA zRxmC(4FxkuGt9=*E{J0N5O4Qjqe9rpNA^uFibP!&<9G|+PlJPQhr9~HK8~WQ?nAz< z`6!C9x{r=$I95>|DT=t{BYT(tWD*PWPnu2lPuaxvG;<4`v`e8T3$15xS$_v5+~|IW zQ#5D;D*SmC)G$=TdmeXb8bDy<5PkuWE^g2%I|($prAgpXyIw_D(oW4@&?GUV{i(2e zfS^6jRV4GP%8&Yyn>Yr8fFxnQVw;zdNphOGOkdr@=TEhG~QQUyS~1Z6^Kp)A|#d>?#kTHZfO`UPPT|2l4ZrKCVa zs~&@8DA3UrY%t}qyhI}$YT+b6sxj}Txm#z;tm_D8-n};Ew;W@d?Jm!R(t%kIW4TfFmOCk1U1X_?L2H zEqFn$%J#C5#zLR1pS5GvIuD4O-!GI@K1YU)Gf)IDz5!fWB{>tg$yK2)%n{|$ zSz8m80Bw?sbn9ap4_OyYGEDH839on94I#`HrPrMf<2xZv=lyGjN8D1|K=p!6K(8@) zAvYLUGDD!Z$fP>3cO3k~Rl_CyD^lIEs3n@JoprC)&RUra-l@fvFl?i_l}r*;$cc;f zl-rJ?JLfTyR6W#Vcpc?xkA|>7vSs~n%r^9UEsWM%t}7t6)6tAJtQw);9AFk##4gcF zu0Tgv-kY#US@_Z^hNhyenfT&}=9eQlyllvwu7E7jQEopfdp<1jw%GV7&wcpf)buF} zW3ZZOPj9Ox&8iGATMwSY3(u3*7l2asH+Tbc72aA@M$!)xUTxuWmL0p<308Vkx&z#` zOq_3&RJ&qQAg$$JM(5WcDPQ#xlU?a9K_DP%!y4XAp&u_!1)V6K)`CT|u|CvV)^21R z>M?HM)QPTZP2(!j3;YR`>O9FyPWl<1i)|)90>Ru_XxdfcMp!6H9AcJf-@AJkm&2wT zO}$uaS;ElvHx964#g@gG7{h^uMT5Z#!LhtJv2>0}iwi(KMg&f9&1VeApDysgEo%0*zo5Q+gv2ArA+v zrJ}E5D3C&0NIhEJAG|q?T{V)t+wuwS{n3?4-#`4A&d1^foyZ@-4RNFf ze$b<}4YbGoL3ZXv&|)??oQX+|7o|;Kq3g0%3hp(StNOXJSA|6!PG=LfAF7u_Kq|Jo z(nJLSFgMi0vb3L_#eOxNf+ck?6}V02xoc_ZPwuBUD?ZX>`x3fhLs^v z>AJLU#IvOn30q1j5Wbk9rT>8Apv5mkaeO*jl5Z%A=2#DNIV1p$rV^?{><5t^3(l5H zpLA|t;=*k5khn)5dxaFSML?FCin=H+KKzvbLVzg-HxfTKJn=x__VgH1(8|c*-k$y0 z@E|7K)(;to(Ex)^xj1)-G@|0zIvVD~r1ZIK^$lWNFS~05gr9_HNTo1>Kv@YMWh1;X znVe1FbN?EJ2+k%C9uKfjJK!NG@f3Hk?w#cN&=Q~yt{_P=0O-kn-TN<^RHJFR`}y_ zNz3u%3~}^y<=Pgm7TL=AO~R+}s*9P($Ti&TtQ;;G($VgT&&A#eKS_{cUW6xpmR|D) zB={v(hFn2mlA{R4pO+po*c9R?_&dVset08!rSe+Ro_!CY z@U1v6sT;2qsA+H@oTBHxO8v`sB&oaLBkXFg9%8SJdDoq1mSFX!%K43-5r9YfdR;u5 zt{crcF5Z@jo}^8Ub3c|N6fHsSxTIp_W>Mm(ZNq;CsL0!5dzw9!U0-luuoek)aE4(S^SKrkcV=jq5>up*Z`GI z2C)bk!Qt(^c`sTESuZs5I><(uAgxVJOeYqn0;W!+m5`~t)sy~)0gTLZ{u+r{I zfy~P~Jcr_+qeJVal1NzE1sCVou1k4~WGU~|t)O0G##V`m&^aBv=kXm(kNq6*m`phX znB`)YyttU1U!bHPmM`dhRbS5LI6O}JrV0Hv(s$hgfH7lBZx9ww8R+Va(!K&7^zx-u zCiraHVVtS6nd^tuRb1aY-~ph3S2$NLX%r?vKa5h03QGMuN}fP&C2o!5+b7}^u<|UV zrqK~@jTj#25?w9_O1m;FI@oL({TcD|To%Q}A&#CL-027hsjUd#Ht2|(6&SW48`L|c zfhk5KD^|dWd<&7X+_|+o-bHm0s;oQ+{y)G>tnolYLndaC~({4pHT` zW4t6C)_$CeY<%dYND)Ddr2Qj<$58pTPF6|R&W5U>zT20~oR zOl~&m^|97cgkp&iUubL7vdj&^M=W)zE*;oXX5hFBt}OL%w$?NGtwY~or$6r8@BG$5 zMVAx)eGh*R-u=|s?fxoBc%8+2zwaVagUPChXqvT84`$~s_KF%9R4nL+=pu_6{vEsK zUia0@brk<=nY^+%=32+0bgV01;t!BW2;4Mc#)$`CqUJLEU6)NP?&k}_n4aTCcx?M2 zYh0qsJ|8^?kZ^Eha3paI{40~R6~j-$Yx}Z_)ZptDoJ1klfEVB02_XI>#n0lKv$LO0 z2a}(!hBfW#BOy*ZPe2C1LO}TzY~{z@+v3~6(kXq}hr+!+*_}oGOOUUBrug?}Tm%1G z-aA+7VrQ@7{Wf5Ewp>rw{i=ex_`>fcnf?=CMbeJU4uV3btmgp|c{oKIHt z%az}DZQJ?@8KUWP4Nb&dWwAr|&-guNm$rkf-?ZCTbT`koe+oNH*(&+xwgiG4T{9EL z=#vs;Gq;w-HyhE>6xlJra3g0T>ik2^?w#*Ovk*!soIJ&I z2!TkOiDSCS&M_I3`-Kxc)V>!#NohFA^3)0D-0WfBZ1ss-;+GeP_h5#kN4m~(SZY#GNU@08h4yC`)Q)1ZPp$h@73;$MjaZl`g1g+Ua z>`=Hj;juZms(IXIaMB6kkfbZ|ScA}T93l0vQXZ-30g6iJYw#%L*u^)Vt(|w~Ze_{0 ztAEAogii-NlXzw0^`i*EtXBLS=RlS}f4C%PK=k}aqX-e|lY>CKT&eDcWf)VX!(;oW z(fK*U7lGeUiDT9|&|^g~4CuvD`gt?lACNW}B(W;{a;-xck~DFOOiXmgO)f=E<6YP; zkAn2^2QHw5-K(+D#)w9=6my<8feJ}u!6rWjfl28Gfd~zWqX=XM$`@G{ewyG7zso?T z7^zY}pnQC6K>8E(RqOlp(c|D0?hLtGJC$nI+Tg87Nd@am@f zP~EYk)IWL=7PtvX|G`dw=>W?@gA)Jn=Otl*?4+IK?-w7aC4%h<{?qsYIxh6eUy>7Y zaSdt?$fL{%@-P=uS>%8&fJgtZr~#-Nq@SCWzwYeruDljmvhq+ar;u7SO0;By zD18<7D5~)mFXceQE9p6w15H1#i%0+mu^;RNVI*b&iMe9}%gq4^2?`h$q4*Mh7NI70EyE9fVM`3Mqo`162w%5nR!djI|kstX>opy_|J*i-pl zUIGD((jS~@jAej;3cQ|Rcb3(aG6B=+JKVCuUiQ(;*=dLHc)(>5ol=fPox@-5;+`i* zMoJQjBKN*fobdg4|ND_7E|}c}WCa&RAUN29_PW&Zm1Kt-4r=7licCsGfOVIn6a%~O zC3gR0&u9sL8_o}rX|LyA+;jw+u{zdt_f@8im5R&Ff4RZa@Ps_UvKctf`}J{{kr zhI`6xBW@)IGY0XlWDgTPMgCa)LHdI;CGFZL%;U?Y$?omW=RMx^Bho`Z!U#z@eO>kn zo{|I2b`ubttr%es?pj=P(|L z9jBY#slz)Yr_(uWUS#}bWTHk&P|N=u{^$i7>z$LcqfY-G_*EnP-+cBNJo$3qZZmi7 z#6W0*$b}kTbmVT|6=bgFUiZ)4wBhAnfVl$7UU8eRs^_CPl2UBI zn+_g>tqw0Y{=oL`H~a%EoFRfUTzdHD-&R)eV0T&nMX_aC#VeQi(*HS~;aafW&JYCR z^SGdhU$9q3!myZIec@_yGh{)AvL%haDfhZ#@$0sr5PYY|m+EpXrUmfIP(kPgu&BlL8aP!(xj`=bkpwZvY49UBXuNl%cwZfcb8EXbUNR+}eeoJ)X|vYh_%-L(cj*;f~`{v~_Hd z`uAa9Do*w9oOpbXO(_IIQeh~n&U%y6CtF2+ku)eN&SGE+`Kx@(74!DpR~^~}{*w10G%ixf2Wx6(aL|#n&3+Q7| zAVvUM5SWAn=rWJeCPn4i`6vaQ>eYEdk7cP57)BS16cmV$pf0+Nq9EvVrW#d zf=mE!SKfY+h3xWK>*p;*P@g(>9sJky#Br0Zf_Ucims6PPV!p8;rTbnU}f{Ty$wReb}L%mUrv30 z#KZRMR%+>KOuBEWU@$jpUe>KFBnjnzaH%Tc6Kpr2!-f-K5H;Q%CI0HFwW|`E3Wi7U zEmtd7H8lS=?6u{Sq!rAQR?r~dg5OF{r>$tg82;~UG92RHXc7ep8DJIB9DYR|k4I8l z3F=pcOe>)SVfE6E(<1fR{jn13#*t) zRTFRZA4$V{)8g`c-ekⅈ+uu6?hhryz`hx1-6E8^hu#QTte?Cf6ZN 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�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%0D}p%Hh{e#zU+^n?Yl_1L0!#ZC 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›xQX zi%cabBjy~TbFL=d|G_y0BjcG582Pjh+{t!RqArA-qc)Vj;fPj;%3v{W^g^lP-{kH{ zA7?C=kST@bk}Kl3$zS98Jw6`wvt0#}4U>dT-S9X@S%7t1Cuw&MNZCzv*wzPb{bq_% zXll|U(I2BF+Y-U>QGZ-#GN<=e<#hEM7jEj~ z^2VO1i;v;{IY1#w5J_2l+a9EJ@~A%o0jfOxH~ZRYw>nK41^=Z^`V83DWExVA25hQ!zbxFurf{O%cXknp0 zgEQ&`cLstCPup!I=RmSObNZxPt=3j+<>Q^L>dtCyJ=MG+n|H_K{tsv&=WxgoqVdhG z*DBk`>rYoJNkG9Kqd~u0@Cj@R;vbnU;3kn_=LB{;qGtNog%cZI$S~)B|F&h z@ZsIuzd}bilJs?bRA27aR$nB0;n)kqPslHNCB~iWG~mPB(#dL7tsz53(chCZZ!A%qf^>O^U*or}JyN}WDH3zqbDoQ`GNbfOuVN)#XvPlnS%wOvAvv%^qh zp}^YkPCUyOA&e^9Xl1>d7T}Lcr!A$<%p^w=5Ue%Ed`#T4;{LI1u0f9 zx>f!bZ=JjWfnE+_`g_Qx5a!lxb7RilcZTKjixG%ROnzepH0a)6^e@<)ISsTOXfC?5 zEtLeCW9>&ljGR6dAlJ5zv_)#6h>v$X~1rg=vEHji221IIg&N7EFdCmwDrdHPpl>K}` zVeAzlwu28c8m6}JxPUH`*_HGKw#WHJhhQ2+p2Y=8tAf7r=5%>^`3Jx%&b|V!MlQd= z%=7wSxt4ei|1SNm|GugfUjMfEyF6Km{+%hwjovelONU=dukOvgmUlBNxbT>jXK+C< z^dR>!=Ee++I;RDXM7`{S{ABhth%Zh+cPC2Pr}1h6>nwBkjrx}tI9vh;omK(u>CV(J z#;k7W0$P=pT{*cdjR^!iM<(0q*LWB}Eh*nQObQ@{R~-$HlF6ns zC;im@e#4`K*C~#f*0)SyI@*(CQSN7^9CX5?NG}}!UbiBsfGJC=z3*}@M@nu&AV%9Y*K?c@E*USn$m!IhS~w_e#Ii2`-)L9f|GvUUi(4;|Qvmav`7iAT0+G=0e} z9}iXfg*!eOJ=dL+gbzET0>!q^l;FjB@B=9%ItIj~lvypSou-S1jVhFs$VOXR;t`;s zWW#orF@{0bO4V_ss&80p+RwP6J~A<#qwzsLCp&LI?ZB)^YOE^n0;6M!bAuh?s@zaU;7M*Nh&pXk&JA8N)VP2ixLUUi@v zhQyqB?>0jIvD4YYNh~Mhiy*bRLHkV`!N{j=@L-70yIsf2{*HyO?AUi_Xc1U{D`Lm= zP4V}M5K~Dpz0wInQWlnkq;&Lz6$ezQR^;n3qH5Hy=w@?Ou;(TsGhL~2SQ`acp<57> zssYf*lg%Mnp$$q2jQhjx)=GssaveB*J`xBdU}3fiEo12fZG*VScMc5kW=VJ-aX3GM zLA5qx93|G_E40S2QJbQ1Ob$Vz7$cKAroN*}d9(+Lmw-<>(>hR0=%a+FNNAwRIs()< zPz*)b!sW@4H)6C!gEaC;AezOn1VMao<4RTj1ZE`jf7nVI2M0TS`>9w?wFG+p^Z^`_tDi2VK7fk--(nD8hnm46XHBpMZrl z0%AML@%bI+L~-hD5x%r8(rR^ZY?V?S_qCi2`X5CxOK(S%il_C;HdZ*C)`p3VKw|74 z&bZwBdsjiP#R;EA65w`30uzUm$+$6rncj=x1-gc8ot*a#$0WPP0m0 zp{M25jdI2E!c8bwDc9 zX4kG%)Llr?hh>&zZL)|IQzG89v6sA~8^xp>%3bT5il}T-NST;-;xal9ICC?^>3SJU z%34hjr5USfpZWwSgu?wz;dk$s9gMaXR$n=K}YJ3$p8E|UQNBvVN zkhkKmQ&?jEg=2=ogb@QCPS!%GFk>%^0^mzGf&ufzxk z@gRHQMxa2E`T)W9$MWJB*GIOx@Zb8WL+Z}Kpw|Z-;4c=MnF2?P^o?;tG6V4gQihht z%0}98^n@2wu8*l;*kr-EsuBogu>ILiLBeSxg=9Jmj9eLq=2Rt7;BF)u# zY8?q!K|l_phR>qNV|_@T3na>j<*uuL(vMsy5-0Em7U>a56hXFADR3XzMiRp7+ab*7 zX9u!bc5T^>nx?-6*Yc<&J3edU-kE`|XE3J5iM8BXvYwfFpq{Ec)^)yV$DvtZoi?7c z3zQKzvb+?g749LI$*>1z55;`5fdSDneR+VX4=$@a!7R`02xrDWX*dQ$QxXun?K zs2m7NM|v~nCOzF=hU!4Qa3^VbP*XB9!0hJ4tEyk6!ufUG$w0+`x!m^$h?)|!ew7(i zCyGLqc((SLTPCQ*X;B!fYeo-4S<0C^Ik|3l5c5*;nph)|qM~HAYs*#s!8btb4@yoBJ(yL!r?mzz9>w7aR z*e||+AHVtYyBYj?^{ad<9r3F@`y2Jcna5uc8P68I@BrK(@N+2G5>OV!Ly&+ULb0=G z5LF~aSI8-kizdj9ZHc)zrETdo@07S4o%IJuK^nh#z&G?Z#Sb68*6e`n_n+7F`>Ocw z%l6{I|)_cNa9*TzBxnqT*=e|`_OUMU}NT2t-0 zH)vPY7Ql4^mwBb#l?(3J9`D3hv$owy9N!r_Ch{!wEFM*uCRuwj9f$X6goQVPcE>G> zZ_f>>1_6jOFGbWD4WyN&YsWW;MsBo1BnYd;?>xqO+#~EF^pVAA%m>UFauUz-NsM(l z03pP<37nBL?LN}L5)I0@{d`p0vK7Mp{ltkhUjQ({f#uC;i23^n{h2?S7dwCc{>*nX z--!T^p#UOOG=t2Lk8~SnO1t$7qQJIwR6H)co+;d4hWmF>sUj}>2rd|gpq4x&4%C!t z0?wr60)8F%)zNSN+^zQ*!^+eX#?%EyOmO1|+d%#E0tsJ!s*57bMLd^ED;YpA9+gpc zBZXrq2dJ21bJ3vCj^lVislSypLMjmJP+H`=7^9VQVbq;GMkfT zP3S4Iyl^zkFBoVF-csrDPj`;yX9QYKzrZS$#*Xp+eJLo1vaF;VO$y4iJobl(A>`B} zIbOiANI+9oe9BzjREI=EW3@61Hm{1tpFLHRQP4bbiY}cwqcWj9$vZddKT`+15c%_u zQeL3ziVG*HcW2;L!MsWp7eVAp(blERzgZLB8UlXh6Dg?Hf2^U7ZCKURVCha{*P=Hh z5BGaJP0AbL&k}s(kX!T}3?IWM!fiW?j+*Ze-rjEwnr}vPrG;0^4_?o>cnfHya)|Q+ z;kG!UgK9{pIkLBL6LVU9dU2lZN(xtwDS`?l=`jYF)accLJ8m>{Z@md>@?evW-6zcw zFb8oH&AF#LQAd0)kQ?aKSjF$CpkY4trDR`wJ>GFahq^jj#86~(iqxHg6dhd-dLmGy zq))vV6?F%1{WRwoy^t7^whgz&W3mLF*v<%RmhU0qyTWBXOUvrM8WXQ0%9sPwM`TAX z`fJ$MuN*}|tzKe*ePeg!4Mg3fVR0KFQY+H42KzAP5ZU5NAY65pc#>2ZGTS^=ov}D6 z9!HqBK%n#Fl+L)p1b{|`8NY$7rxrd_l6`KKa}iP_KB=?L8>kt! zByl?2su7jeX^0+RWKd?p#o~I{+b?d`iDDw|Gg)bYODB(_OZTBt3* zyd|!~uVh&uE*n2Fc3mk=6*pas%v}j=&SIBA&v%0phc+WFfa&S2=b$&ssxJeHVif{_ zg3;h%x)Cq{V^nYu1*{4vi0nWlkH#Am#(ipLn{$_5-pq%MoVW)^VXOZTB*v zG6bH8CeP*ybtfw&ag~570K^~1n#u%w)T)vUN$x}k-N(`KYk1Oh=g!sRC4sudr-z(^ zP+@w4fF-LG)B*3A@S9JdE9GCP^cNm}x1YagJ8*I9l0ZXNcLmVXe!wcr(i4gs$pHMf2wYMn-+ZUd~cC?mT#qrM}$bhEBX z*b58$|B4V-!h8`t0U;hoWQJnz*dM1z$3!GtYXLq0iT8#+`JOHdpGNRb(6{DxU0v|U z--onE2JL2-WL!Ev-q@-fA5(L`KR>Vkhtcgj@-u40coQ!fNDQA2kDo~_ZA4XmiBmU? z6ilPh^r$gxOXOd_eVPkG+Kn|4hDln!Ou&tRNO=m^Z+@sTE*idPJa55+Xif~2jWZQx zDyZIUfz660nFV#Xc|V2^iC7aU;?80MWv3iNArmYKLQHsXL(%XTdAn(!1%T(eN`e z;)AA4Zbw%L>yXf!S$Yujr|)+(9gtg6{2?^;GMW!hJq5nLZj(ugT3-)NRoVzH9d@|E zsKq>!TRwsHs54us;yj$|GVS!TP&-CQrvvQ{mKp($!bPQ!uLn73^+E;TZ>lWpf-2Xw zrjbr*X6{iqKa_a2vNGxLAViBFpo@_GZv-7jT;ylT*Fr&EtI}@cmT4c~HnEIEO|! zWRA0WxD@IXouDK3`sTZkmvFRcFlX$JumtwO7Rifg5j2Zv5-2{RoQN+Y=F z?^dnGT}&T3XBknw6g*jy#vrLZFy<~Q>f_vTVoOi4TR+o_v z(I~f`W(CgK!Z{L}y}<=dl4Kicu&7B55q8f#i9zS&a)grbly^I)ZG^AG0pDxg2cJRs zAW>KU0*5}QfRz|JDMQeK8}x4|V!wrtXV@5x%gU)Kxc$*3kiy~ir-lgNMUU^0=8I2Q z{7s*CuyJS*%PmO?G+ddQs^L}hr2nBUW6_C4@E1T8bjt(?5J(>eEZeK$8BQxOiL4?F z{ldA3M-cfO4ilNl)&M5H1KeNq2gEO)Cx>0Z57Kna{RsJ$%gZ5CE{UlxAZ+TEBflwa&u%GGLNuQIi@v%kGn#V29S!RvGm+3p?cs5~(2x+1l3jR_*2N)aF)gmu+tD zRSK(x{nbisYn|L**soXi_o`)dzkz0Vw{|xx=%vh6a2~zlS)ts(k3#h+*G_abwOR-C z6~b3o-`js#*?RJ{R(QI%jUyJ-!de-St*&jCwJQv2eS39lXST4hy0iL(&{Yb1Xr(g6 zVhUa1;AxqU*xxGtzb*?ehOxfK!x-zeS&Xt$^HmSFs^!_jYGtcRglty!c4nuDPSil_ zXax0k%i0Q2EeP^6f+9p%9fNkT6gJ9O@OPhJdUj=quJC+H9gM^2orpN9UbL>Lg~-_@ z{@7No7*YoWWkRFb-dZp3R?F4n%`IRC+RPBe=8SuWx{-$_oO`<)9!Kk3MPOMzZS6SanaCKs*Ug>(Mno=$Dyi`;X@~+!;b9Q z=<>kFUfIM7<%}Q?Sh{&EPS(uYr!8E9H|o6UFl7t!P}p~NN^J$naj=HE^VbdoJap_z z#;#52*x@N?4Y}2zCfIYyXe`$E5Zkt#TG{4xwKOgwbVz$>{%myK{hO7jA)^=dShup- zc=mtp1p^88ZYoB&`Y*sD-Br&ATWj8w#H6Kk5&1HLs zL^Ms>&7$>2&ot%CX zpycKYTbn*#gKh2y*ZkN7zop`;#OY%pr*6}G8#dqP^<oM{jqH}};X#tA-FnIHoZR^Zyi=mDaO-5ho*e>)@u^6J^MrRP8tZj9WHVAxaz99`9 z174W2BrH{lT`PkD>w?L2nw;w9Op>S25uE~}>j9?|m0UXq}RcZ&0})qX{eTFem`m&P*sO zgEJcUNuG|+k+;7ne#w7d;siIiNEsaIM*QtUt`ZGaSXYCoL*iLX#L2V>!7sN`@a3sV|zljV&u{uo`(W;j;lLL`=-SHi5!JcfqTs0pRZUhxj2 zp_a-+p>Nw)X!)m~ev%A=0JQYDgo|jmcFQwU1$_Qi=(KcQSt%@jS1PP-?>}8F+%Ifw zJ=v<^?;QS{FKAf=xw^uSUL9R*A6@Lu&ml|V?C&#(Kaj?3taF{#N@3J|k38Mt#Kr>w1!Y zkpdm9Ji@8&xS7)9e}rac9!KrOZ#?0^FLdlc(H4*d z!P!cH=pR-K=`@b+JuZAHBpxMxqoo-S4R*UOm_FOVBj5lrHm8L`5?p#E?oW6pHmw_g zpA8w8ua(oK*!X$RLtlAsq1EI(wvG@_Unu3hFU$m*cGcz7?IWp4Vg~XuNJiq8!qM;^ z-mTDG^@T&7u_q4ue#DrDna934Hu;YgXyvs_NW1d#qckDLNP zr0z2i;r03g$H|xe;LBdcgXzmh9UGjUBEsxB@}hKDEaebF9(FzK)=r8{^B_>_%px3q zXUHgAB~MS59$cN8`nAM+2T)~qZ?#%2!&`)%keJ+~4v~>{>i4TD5q}hka?}DMQI>Q0 z!6-3^$w)OuIqJO&s>MkmE_$}=hLgoC6eYp%1tY~cn}VbwGWm_a1C-c+Hh6z<2?ZEZ z8+xX7iTIQojR z>M&;MrreqU-O+u<+0_WGfWxDP6erko!yXef1ztsGUzcbgn`l#OmZA^aOmX4jBl;+O zjaVsn?xKu2AG+Nel>dF%?AH2mlcBX6_5bs&0qDY8{cVH(>8tzv14jJQ?MK}HRxJN~ z`#!M|_RW)fXF|8*$Sa3Pr;pcRWx3JRB1E?_r49~Yh`Lasv4Ijg7K7LHEL;QPO+we&w+V%rimqpw}q$%fgC>=PG z*UIs|g4#c`7JW5YgIwIw<(W~;ZM}{uXFh=%+uGdQKQ2E9PH;QRsqBp6!FCM~d~6jT zaElZHRKCO$mROD-u*8Gt0gRQO3lBZ1nRm$+GCd)(cL91tDe@k-cr{|0SoCk z!2UdhnSA(D^4WF`1tHQC1-Hw1_$c}82@5_-6@0>iKc!0IinpmEL}#K&J~ZwOj`7rp zz<*&x5!7;$l(KBfKO~lMM<^D;=ozraXNiJDHXcGv2nC;LK@28(NSvb}MZN+W2>>UG zIU~_ylSHYfF>(7TOvUd0fzl$$9Dm6QIbrL3qgh$O`pw|_I{@ESH6yVg(HW-xJ%poN!+QT6)89>E=Uq7 z*+QH3+iJLwo}phZakDEsJLR_@upfz-rFQqt$_m7L9;V=YQ8bt!ca4ClCk+XnYL)E9 zPbltmaJRlFRx@a|3Tkw~E@(o*I>CRgV|+DrYz-l9i0c;Cq1S^7r2`$>LThRIMIw3ZR(M;(rm)?+{X-=X2+zI3`=~_)FxBTbgwVA#hxW) z3FnxxdPp*lB)wx{a~I+{TrnxTSLJrUqPd{%#Z694XkF^ox$RUt#SSPCcituD*Q1Vc7*v4Bi}pSUcNxuOoPft)IVPeHEy zr0Zs^xcp*iCF?8w3jjb3F}ZLt!27>ws~d%Qipjyz{~aqjJOA;-2486L)oTfF`f57% z6TO4$Iy+xG(Wl;bCMjkiP~2S!X#*^Jg-Y)(dpJw*p>3vFn&x9oUG#mbkj^n+nQ2`U zP_;-Jk*_2wN-Z|}i3Lz4K?H&oam_hpHw_Zla6Nz!ho6Ne%_Z;8vZveV4AF$^D8C8! zb{rTT1iuYkp}2`V0Sx!rwUMQ%TUov$n&U+!LLNnYIv^FV9U>$mHT-62YM{}SM1C^2 z8pUUd9n**X)v!?9zTZ1H@7Q+}dUmjYb238u|3?BS!l4hgW8l*5rXy}eV%>c}YxG74 z*u;PFRU8OTMJAGr`zwn=>{DHwREq?;#RBckTTvK zDM#ft%JOgyPeD#8=c1`?`^+`PEkO4%fs)=#8{%x&5T_Lo*BDgfc=!N^eCLB)O?66i zL*~qT4#>Hq(a^0y1<8(6xZd<33g*?1EG%nk-3y3Pa&tu6nS12(IQsF>r4dgkT1s|q ziMc}4z(tG!AkhdZew-(Lg4E>1sLOC1!V&N8G87vwZEmg#a!ie=oi5S{QpH?bcS-E!RF&gTXHg@eEVf2>~tuWLTQqDf(M$F(?TZ$dn9Go?* z6Mv~8wC=rgR0+L<2X4}jM&W)-M`WzHrxb5_Oe?Udo**_5O^)+`*adgqssAmm@m#=c zO#-LHhtlv1vew~RR$7EM(|ztZgY*Jx%u@BXM9gYCuEYd}118X%BGTqHG!+x9VX)hN zgS|uhAqOPwPLv3tZuw_ff8a&R;#Wl7*vSDwQevqo&0X7-m9S%P^#P)<9Mzr+JxV){ z@BPFYPayji=@r|X5qbk=!)Wzf4X;cmLbZ+{)1i^KVB$4SuO!oRYf%Dm3pE|wdgE2c z8=jgrf!N{+~ZTTR0-EyU7WFuZSATJW16l+e)31J+%(`Y-mP+!d- z&%J((=tyV>%z^ynq&;eWIr&mq|AKrVXU#9oFUSgVdiLd_`{iN=34K>yA-MVRQS08! zclTxnMAx1%|_**!*l9~|cP{8ofJzRM3Sb@YcH|pcOG!IO<;Q?%E2?!$0 zE)S(Vd$pVhs1E>SOD<|j8MaJ-S|HlY;%gb{i4N1QML-Skr*XSZ8V4sW=+xyj(VWnP zR`P8OPA8NFr6`|!0E~(Rzd|Wv$i&N;h*VgYWcf*m#s31i#2EL_NsCU(`G+#tB7ufx z4Dg^DPy3evsu(oCHhP%+v|?7@;D}U1*%^RHjt(%in|C}yYzi(Gb9A87?-$Rwy2#OF zbFP{KCP^3*MENUj+?YKYzP@)4S8d)yBBFcu!jtLWBWCgRC%qUnfX-9N_%0r=aL)!( zY86|oLYJ!!1(2ww5)IrR#fmbqoDSJJM_6UeDqTP&T z7hhriD-aefP3NCy4nmr0T%i90t*?V*g? zq0~=Pdx*t!YCa`C@N@}G{@CVHe$wKbUntJx6^68l3opN(R2X@)sN(3s@3IQ5$TEcM180D>?-S1!XP1A++Vn>tE46qWZIBU)49Ze!7c@dT z_yHgS#uJa7e3G%rEM$Z*s%ylHFpi3{KGKjdOc05V zAfV4Lk)#5-Pntc1H!v8{P)B_Fo46AI{yGUZgC_gQYVTyI;IT@7zVsALQ#<XM2I0)wBoA4S8zXamVNJ=ToyqOGRDDDHmli=NDVg6UqABp0e-Q(mV*JGraVx2z#u8 zSPh9EAnePls~6uvhHzcH!6U<@g6agRlCzt7NJ}6M7Ad?!OtQH6BR1NRIr!m^(0-;n zX=!Eh+@XKEBk~bM<;@GTZ@wcAUKEk_sdyDH6;gxC=~ZH`;ufSr(Ucw5HCV;L8k8TW zggY%pChc094K`%TdBz?S zq<%Zy9|T;{PcN7mUcjh;iH9SLM_-PvZNRM&s}v8 zp*0M`^rs9Q$&t{h=BaN{G&Vz61~F;UPI8xZjP}r64qzzFErOKKC`OC5px}Tum9i88 z0hoMXUFkd+-Ma`h%Ic+fu&xXb%1$w>ap)fxjYYBlDfk)l5{km*Fu7=MpMcBVPy;cF z#C;>gvd*yO{Njt_iX%P`SPq+HZcvuD|O2{jTuIWw3+g8~$k@Y#ai?Hqz8QR{&g zSVLlihdf&h;Sz`j9pn+8u60xC!#E5#kK-||=~ZcZW;Rovm~n6d62iGgV?p3Vd5N{X z1xvVjg9p9Y9kz`j!f56mjoC_G_nT&@g|w3Lle?>koSx%7(_a6hax4lnHNUYxCK>yzW$qgDQbSDX|FU}Le;z#+Yrym_ zg-1*`I*lW?aJV-Q3tMt@9B?3R<0W231!3`@M{@DhO-z@m;=0&BINnzf+H%Gn5(~KC z4-LGFegjJG&d=w?!vE*d-B~fL5Cn1p2g4_12}M>+X658&2(8My|GDdf=jDd7v?IeQ zzI5b3<`s(?ju=0t9@!sCR!>k74*LX4G^IV$b(GTxA152Z5u&k;&{>t!2p=aKpH~0~q-@HZ1@r&mH6fR7 zZXp2^GURLJ(ijpD*K-0usuv6M@;{MVxvJN?R~<@XCL{zG=o?&_P}efLy6E;%zhl(- zM7vozm%FnD?T7^q`LVV@ljaCK7VL7ZO%IER>}6lyu>8 zoRvBMvPCz)rEAvDAa$HOk~bOk&x>+O>Q=&qSjxh{)n{790a52|lhX86L=N~R+UNp> zxP6C+r|uyJ6Lxb96IUb&bNiLV<8?qj-;o_37n>yUAWoNET)^&@pCybn{3zf1?QMKZ}IgA2FVzqIim5}wKGbE*kZSw=j3RMP0TD-#RjU7lu43kV>HvS zqYBY=m^L8ZcQuWPUY^u*HqR<|;x+QVh7_pdI~i<66OtD#!7Jn=dK|TpV_bVqAqnqX z=nCTm?V|^>ee?)t$_o7IIt_sC$ zfFaYsJrwm2f{pZo)wqkFXpQ}5S^i}AKXeO7T|cO6l@$Q-@hdSAqxwImBE*CQ+Mbt% z+jR;PA@dBuA(>|nG@R2WD>F-(@CDDjHxJtyA|)mF^@FuRRGi`xE5sWXSKM$c_7K@g zE4?ulsENy3Yo;I!0*=*PCl_sdahYlm6WO;oxYy|CawDnOQ1|z540=O~3aE zi4EuQx0G{z#ER5KAdk(0(Fr z0Gf-mJl=^U8|5nPa`lE)wOSkmyiEp~?f3I!4rR25MaQ zkOwp_0!_ork_s{odw3}DRDjJJ3Ey$Wy#_866R^0!g6k?T2Xblz_ptFkh*A;h6G$QR zr%fE~f^Kh^j0yuPsMg&!l5B>jj8s@KfCG->_fn0doJDacb~6lz@;4g^bcVArBLN}> zzoE%2exrAj@EeL5RL0BXi4d+aRlMIGo!-BA-?=~P4)33Wl<{wK?nH#j{OMo>J7Ee} z!m<=5B9vfJX6(bRz%Z|zZ{wnUZf#r+2lu=E(`NTRlJ^eUp{)Tpb`J9#wZpQB{xT5t zFGk7ME(ZV)|IFpVpXvMB(hB2b7-Fo<=MD5m1&qiAAHEeD#vwfBG%FdZ)*;CWX4Xw%!%CsT7?K9G&9FRhssmp6nXoI}yXWNHx843pC^}!f z{~h8ACrA|6r~x=e@HoK=@uG0G)~!=nEoOcMxd$t9{A!b184IO#Yggs~YFws_D|5h9 zcjp-pR_bL7~r1ARo%&pP5w6Z7S)Ph2VPSgtbs~J}nw_4+FYwJZFb7>5Y zDQohjk6bN?!of7NmE?6kzF|@|_q|B5cidFqqN_*|s5alU5glM;eP`N60fKPL9bVbj zxkr49*xo1~+{^x@&!WmS`TI}zd8rbjjuCJC=8Xgl7jP};dZRpt>qD34zJIW^AeT*^ zHhbK69|8dElVkS}G=5%)1YN;q1!q`i3m@BDc>2A!C_B7_N`a_sMlfGq;KI>Xo7Xp? z>*EG4T0}yAiDz+R(Zm{xH@iy(Xmc(wNN&L-a25QPp;+mp-|xQxCS~p{&(DxqMqHuJdMKkjMI}Oc+dgtdx2ec$ltv`ex?g$`}ffFbGp{Ao&0~ zJ^e2JBfp1m9cf{tuDP2qLLPB|%}79U1d84<>oTQLW@SaHfOOA>qIo*r+9*6icG89K z7sPjxuY)GUo*_pl~?WI_-YT9md92D_>`e} zy**s`r^b5u3tKeQjv`w;P%tKgQ_F%zfTh>d@IF!5wn1 zfR}8ybYFx26I&VZwjw+OD;54lz`t%3afbb^KE>aDEzXNm)(&83m6`cs>D9vA53lbb z02!Vdp&ThDPAh-iMT9BzavrZVr4qsmXYf$skU<>@AcTsHOx(ak$`NObryDoGQt{#Z z^8E7LKkp530d)H_Z_--)*9@qLJ2aE)J(zzmxA@>5SoxoqsP@mB)molkT$o$Dgeo<< zT;^e>A6V~NK8XpzM7P+7xK{+zHiWrz1H&?J?as23L!hn_79k-k#x&(HASrHF7e?FF z#q~lJyoFj{}V_^euQe5{mrH=frj4-XeMm#g(>YljQn zU!K=5zHimeKJE7EONaGm?`rk($I8NP?X=#iRS&j4S8&_>^Y_mxjr!(pwSHEvH8wxD z7IrHK_2<7dx|@~CLSwo5zEN$I8>sv2LuH|S)u=Z%p1)|-8&4Jkjisx_ z+UDW=lP5c$>*tGyjpei9%kzuPdVO(ix7=FWX*4#U_gc%<)r;C*`G<0A`C0RH>Dj+4 zjmGZt_uZ$@YnzQqeYe+IxN7Wl4?i82Tf^4*vq7V`);W2<_@MgqV(qZ}tXJzEu0MaV zyIVOQUNxUQdwTNZ;;ixc{nh&U?!)KrE6sy4c0cQfdncRkKi5%r=UwGNwNY6+d~bE@ zi@TMD(?s2u%kLN4-HR8kjWx8@T|DTb?}Ocky9dKx4!V`i)B57Z;X-SpzP$OgzR>OL zEL19$`o&tk+wHvkyjwllyckt_jh&Om;rA~;KRakQaHsM4)!EMCvyU&|Z7rTW*?h74 z;sWoV4QutY#=-N8W~E$Tto1I|S__Ny=P%B7E9K4QTD`K?T5R>6*EiR`=D+p#UsrE( zUuDpcwc9wuh0FDiwMJ#LUH_q60o?8L(f62>53QxvyV{GhjrRM^U!I@0_8RY>tsMaG z&BbR=YK>>ht%X+apboNXY(A@$pY7E?UI335gO^K-eZkMu$^qv6d(3;2^V;Y&cI)pq zmn%PdOAcCy^se%|Q*QeFC?Sv@#gdb#j$ zt$r|CubiJPwBMqi#lz7-<8-0bxOfSA0R3L9k%mssD^E|J9(F)eA77q7+{OE+jTf!n z;X>mgpNBWe!{D{<`uh9IGWe5ax1S&U@a*|}@NpSD@NTz%veEkGptkn%<-*}*^F?>F z+FPsD7b;gL@7DHD>(4&zb~m5zzH2O=E?hjX_nv>)+gv-VJo(}3d9VIUYoT%2SpQ+~ zWp%i8*n>oQa!PcR%5aC;pM@@+RO8+wc5cC-|sfowsyPyLH%>3 zR^5Eo++C`Jw=2(@-L-!oJ~{h$bG85Rq<+=g={9z2pBoQPH`WezKfhnDEVVxG9bE0T zHV%KOpC4}RZEWt=-aY&9vU~R8^mApe_4Hz)R$g4JY#eqEmp4D2zB_wgdGhSj&gaTm zZF%RxdT(uT__T4c+i0A%7Awu>GWhw$+D^TEcC~x3yW1=uK0T?ef@)tbK3}-{u(SE{ zQ~k;FhrcY0)=rj2&riE+?`rQF+qLqj`?A+x28NV{t#n$+IoLhf4SV`Ppb^`S3;i z$-{+qz5A@yJ%nsuTnAr1ZFV>RjrrYuelQaHJ}ftu>-F7Tyjxy>-vGm199A0_&p?Bn z-ObHjrT%RFd1Dj2)apKmYzE~~HovbtZ9%4S-8lRJSyWdZKC402m+^ZWzk8>Up_Tf} zU!He2-{IN6pVuF*A1;j`OGg)#&)wyn^0T#iqwxfN?_+&pdrx5J~W%DclS&G%PV2b*W_pC3H?aQbxZ#mUkx==b?jW#Rc% zdt>MG^QF;n9k@E}T|C`h7(J*T{Lsg{Pu26rRdea=S?%Dee6obKrTnku=2H3dUUjtp zvUYZ{(>r{Dwd`TDTYvDpak11`0G@h{t(UdRCgjA!%IDKxPBu?JJwMp&y{sQ@SKjY_ z+G|uU4>l|3;M)h_>CKnDm&?_~!^-YC#{J}U@pPk6YxdS&R5!|l+KckXo#jUL|6}h> zn^ecHMA7g171_SNams}F3dW`QR{@|GMt>P zju8vGxD-l_b~q^qTtpdNk`hB1XlWrDEnKo_yR!z^a$3jzT7~{Xb`LnQTt{sm)B>Su92e_Ies{*V6IU&5-7Qhx;?EzZkwGHG6gJ@@~w05n1K>I2X^ps7`-Qa^z%-Zy5@solT0 zN)(Wl4!CM4Yg6@9JN|d;K&R>ngcDW9{oCe2BnFB}{^@^0kwTQP9;O9PkQ)0z5P?9_ z$G;xC12>MV(@WJ&2PA+$f%#)&?tg);cjNyjaRZcV%Z1pgUmW*O-yS~(Gkx_jh&2f2 zs&@|}(YL?7hrQo^`t2{jzCiUF*E2&@R*_hW?H31%uD_rP)hKb+{|B@8AXt01|H})4 zB~b4~M-&(hp*r4AhCq7WM_L_yD_BMXC9qHAGL)4LrTXWSB4D+3;4!KAlf3|N-tFg#9PbXp;OYi9Z*N0NwW~+n zJ?~(plZF~)cf?pZV%0(0V1!0S_L+Nj!(V@81V-i9h;PB)r;I z&@`T>V1~d-{4>K7Gcdb2YKH&J;FQcOJVngDF#hxK6-E4K#%5%BE`MR* z_j`Ubp}ZbfMf~oE_JuYsRY@Cz zFpS-?0ss?8B3C?v94D*S)vZOZs{4O`e7jHR&Fy~PUseELb%*%z`{OGpq8C@dzQ3wL z1-+@k?*0OuBzlY>;{LGOXY{l>F?jr=r2{uz7pr)wQ7(blt%}LpfbI7c?><8Wu%h(` zpSdbkSo2zT;-h1QZAU2((#LTX^x!roh&s3G0b%9*bR#2wd@9i8)fvC;2{!0zV?-PD zqb))x|Mv5|T_5b!JMwUcoUh#5+al`$pO^Y&O7o_h=U#45`A6sT+|QTM|C`yo+n+yx z${#vVB90e%GA+j^0zC3$NFF@9TNlpxCWDuG9P9PVfiq zA&(SH^SLkZa!vW@7y>G(O{_*{HpgO-M`#&F$$FP}(p=^52~}FwLlldA;?--VthL#`}`FKVfiA zRkH`}2inkj3qE)W>AK!+$DK!b+Ks=Vef~S-lzbTFDlWYr;i~<3-+RUFexc_Zq5Ww3 zw|5`S{>BG?VTh~T_I~nL4aWP)SIqGjdcKQ(@B6+hDBkye<(a?G|4n{;-~CMq@xJ$~ z3ivIAe{`m`4Enz7TFdahV?<=XFz2fj`@Y|+8sUA%70>-?&)>yY$~Hda$$Wp+A5?du zva(z;3KAA;o#6upd_YnN-+&NanIvm00XXv;dpMwQi4U$QLdsN)He~l0f|QN0^B)

-pdcH8?i*q0*|&2>G#cg7TyQsvsEGqkIRp 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 + } + + ### listar clientes + $app->get('/customers', function () use ($app, $dbh) { + // consulta todos clientes + $sth = $dbh->prepare('SELECT name, cpf, birth_date, email FROM tb_customers'); + $sth->execute(); + $customers = $sth->fetchAll(PDO::FETCH_ASSOC); + + return $app->json($customers); + }); + + ### listar cliente conforme id + $app->get('/customers/{id}', function ($id) use ($app, $dbh) { + // consulta todos clientes + $sth = $dbh->prepare('SELECT name, cpf, birth_date, email FROM tb_customers WHERE id_customer = ?'); + $sth->execute([ $id ]); + $customers = $sth->fetchAll(PDO::FETCH_ASSOC); + + echo __LINE__ . "
"; + + return $app->json($customers); + }); + + ### incluir cliente + $app->post('/customers', function(Request $request) use ($app, $dbh) { + $dados = array( + 'name' => $request->get('name'), + 'cpf' => $request->get('cpf'), + 'birth_date' => $request->get('birth_date'), + 'email' => $request->get('email'), + ); + + $sth = $dbh->prepare(" + INSERT INTO tb_customers (name, cpf, birth_date, email) + VALUES(:name, :cpf, :birth_date, :email) + "); + + $sth->execute($dados); + $id = $dbh->lastInsertId(); + + ### response, 201 created + $response = new Response('Ok', 201); + $response->headers->set('Location', "/customers/$id"); + return $response; + }); + + // editar cliente + $app->put('/customers/{id}', function(Request $request, $id) use ($app, $dbh) { + $dados = array( + 'name' => $request->get('name'), + 'cpf' => $request->get('cpf'), + 'birth_date' => $request->get('birth_date'), + 'email' => $request->get('email'), + 'id' => $id, + ); + $sth = $dbh->prepare(" + UPDATE tb_customers + SET name = :name, cpf = :cpf, birth_date = :birth_date, email = :email + WHERE id_customer = :id + "); + + $sth->execute($dados); + return $app->json($dados, 200); + })->assert('id', '\d+'); + + ### excluir cliente + $app->delete('/customers/{id}', function($id) use ($app, $dbh) { + $sth = $dbh->prepare('DELETE FROM tb_customers WHERE id_customer = ?'); + $sth->execute([ $id ]); + + if($sth->rowCount() < 1) { + return new Response("Cliente com id {$id} não encontrado para exclusão!", 404); + } + + ### registro foi excluido, retornar 204 - no content + return new Response(null, 204); + })->assert('id', '\d+'); + + $app->run(); \ No newline at end of file diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 00000000..fa1a3675 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + foreach ($this->prefixDirsPsr4[$search] as $dir) { + $length = $this->prefixLengthsPsr4[$first][$search]; + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 00000000..f27399a0 --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +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/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 00000000..7a91153b --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 00000000..c3cd0229 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,10 @@ + array($vendorDir . '/pimple/pimple/src'), +); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php new file mode 100644 index 00000000..845ad2e3 --- /dev/null +++ b/vendor/composer/autoload_psr4.php @@ -0,0 +1,18 @@ + array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'), + 'Symfony\\Component\\HttpKernel\\' => array($vendorDir . '/symfony/http-kernel'), + 'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'), + 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), + 'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'), + 'Silex\\' => array($vendorDir . '/silex/silex/src/Silex'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 00000000..06d14708 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit55d0196e4984142c4bf289e9cf5a13bd::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit55d0196e4984142c4bf289e9cf5a13bd::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire55d0196e4984142c4bf289e9cf5a13bd($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire55d0196e4984142c4bf289e9cf5a13bd($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 00000000..f96d6030 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,89 @@ + __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Component\\Routing\\' => 26, + 'Symfony\\Component\\HttpKernel\\' => 29, + 'Symfony\\Component\\HttpFoundation\\' => 33, + 'Symfony\\Component\\EventDispatcher\\' => 34, + 'Symfony\\Component\\Debug\\' => 24, + 'Silex\\' => 6, + ), + 'P' => + array ( + 'Psr\\Log\\' => 8, + 'Psr\\Container\\' => 14, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Component\\Routing\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/routing', + ), + 'Symfony\\Component\\HttpKernel\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-kernel', + ), + 'Symfony\\Component\\HttpFoundation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-foundation', + ), + 'Symfony\\Component\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', + ), + 'Symfony\\Component\\Debug\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/debug', + ), + 'Silex\\' => + array ( + 0 => __DIR__ . '/..' . '/silex/silex/src/Silex', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + ); + + public static $prefixesPsr0 = array ( + 'P' => + array ( + 'Pimple' => + array ( + 0 => __DIR__ . '/..' . '/pimple/pimple/src', + ), + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit55d0196e4984142c4bf289e9cf5a13bd::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit55d0196e4984142c4bf289e9cf5a13bd::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit55d0196e4984142c4bf289e9cf5a13bd::$prefixesPsr0; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 00000000..d92f025a --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,652 @@ +[ + { + "name": "psr/container", + "version": "1.0.0", + "version_normalized": "1.0.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" + }, + "time": "2017-02-14T16:28:37+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "symfony/routing", + "version": "v3.3.9", + "version_normalized": "3.3.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "970326dcd04522e1cd1fe128abaee54c225e27f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/970326dcd04522e1cd1fe128abaee54c225e27f9", + "reference": "970326dcd04522e1cd1fe128abaee54c225e27f9", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.3" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/yaml": "~3.3" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/dependency-injection": "For loading routes from a service", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "time": "2017-07-29T21:54:42+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "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 Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ] + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.5.0", + "version_normalized": "1.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7c8fae0ac1d216eb54349e6a8baa57d515fe8803", + "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2017-06-14T15:44:48+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "symfony/http-foundation", + "version": "v3.3.9", + "version_normalized": "3.3.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "2cdc7de1921d1a1c805a13dc05e44a2cd58f5ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/2cdc7de1921d1a1c805a13dc05e44a2cd58f5ad3", + "reference": "2cdc7de1921d1a1c805a13dc05e44a2cd58f5ad3", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0" + }, + "time": "2017-09-06T17:07:39+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.3.9", + "version_normalized": "3.3.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "54ca9520a00386f83bca145819ad3b619aaa2485" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/54ca9520a00386f83bca145819ad3b619aaa2485", + "reference": "54ca9520a00386f83bca145819ad3b619aaa2485", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "time": "2017-07-29T21:54:42+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "psr/log", + "version": "1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-10-10T12:19:37+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "symfony/debug", + "version": "v3.3.9", + "version_normalized": "3.3.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "8beb24eec70b345c313640962df933499373a944" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/8beb24eec70b345c313640962df933499373a944", + "reference": "8beb24eec70b345c313640962df933499373a944", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0" + }, + "time": "2017-09-01T13:23:39+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "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 Debug Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/http-kernel", + "version": "v3.3.9", + "version_normalized": "3.3.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "70f5bb3cdd737624249953b61023411e26be5db7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/70f5bb3cdd737624249953b61023411e26be5db7", + "reference": "70f5bb3cdd737624249953b61023411e26be5db7", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0", + "symfony/debug": "~2.8|~3.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "~3.3" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/var-dumper": "<3.3", + "twig/twig": "<1.34|<2.4,>=2" + }, + "require-dev": { + "psr/cache": "~1.0", + "symfony/browser-kit": "~2.8|~3.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/css-selector": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/dom-crawler": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0", + "symfony/routing": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~3.3" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "time": "2017-09-11T16:13:23+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "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 HttpKernel Component", + "homepage": "https://symfony.com" + }, + { + "name": "pimple/pimple", + "version": "v3.2.2", + "version_normalized": "3.2.2.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "4d45fb62d96418396ec58ba76e6f065bca16e10a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/4d45fb62d96418396ec58ba76e6f065bca16e10a", + "reference": "4d45fb62d96418396ec58ba76e6f065bca16e10a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/container": "^1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.2" + }, + "time": "2017-07-23T07:32:15+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "silex/silex", + "version": "v2.2.0", + "version_normalized": "2.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Silex.git", + "reference": "ec7d5b5334465414952d4b2e935e73bd085dbbbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Silex/zipball/ec7d5b5334465414952d4b2e935e73bd085dbbbb", + "reference": "ec7d5b5334465414952d4b2e935e73bd085dbbbb", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0", + "symfony/event-dispatcher": "~2.8|^3.0", + "symfony/http-foundation": "~2.8|^3.0", + "symfony/http-kernel": "~2.8|^3.0", + "symfony/routing": "~2.8|^3.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35 || >= 5.0, <5.4.3" + }, + "replace": { + "silex/api": "self.version", + "silex/providers": "self.version" + }, + "require-dev": { + "doctrine/dbal": "~2.2", + "monolog/monolog": "^1.4.1", + "swiftmailer/swiftmailer": "~5", + "symfony/asset": "~2.8|^3.0", + "symfony/browser-kit": "~2.8|^3.0", + "symfony/config": "~2.8|^3.0", + "symfony/css-selector": "~2.8|^3.0", + "symfony/debug": "~2.8|^3.0", + "symfony/doctrine-bridge": "~2.8|^3.0", + "symfony/dom-crawler": "~2.8|^3.0", + "symfony/expression-language": "~2.8|^3.0", + "symfony/finder": "~2.8|^3.0", + "symfony/form": "~2.8|^3.0", + "symfony/intl": "~2.8|^3.0", + "symfony/monolog-bridge": "~2.8|^3.0", + "symfony/options-resolver": "~2.8|^3.0", + "symfony/phpunit-bridge": "^3.2", + "symfony/process": "~2.8|^3.0", + "symfony/security": "~2.8|^3.0", + "symfony/serializer": "~2.8|^3.0", + "symfony/translation": "~2.8|^3.0", + "symfony/twig-bridge": "~2.8|^3.0", + "symfony/validator": "~2.8|^3.0", + "symfony/var-dumper": "~2.8|^3.0", + "symfony/web-link": "^3.3", + "twig/twig": "~1.28|~2.0" + }, + "time": "2017-07-23T07:40:14+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Silex\\": "src/Silex" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "The PHP micro-framework based on the Symfony Components", + "homepage": "http://silex.sensiolabs.org", + "keywords": [ + "microframework" + ] + } +] diff --git a/vendor/pimple/pimple/.gitignore b/vendor/pimple/pimple/.gitignore new file mode 100644 index 00000000..c089b095 --- /dev/null +++ b/vendor/pimple/pimple/.gitignore @@ -0,0 +1,3 @@ +phpunit.xml +composer.lock +/vendor/ diff --git a/vendor/pimple/pimple/.travis.yml b/vendor/pimple/pimple/.travis.yml new file mode 100644 index 00000000..196f7fc1 --- /dev/null +++ b/vendor/pimple/pimple/.travis.yml @@ -0,0 +1,40 @@ +language: php + +env: + matrix: + - PIMPLE_EXT=no + - PIMPLE_EXT=yes + global: + - REPORT_EXIT_STATUS=1 + +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + +before_script: + - composer self-update + - COMPOSER_ROOT_VERSION=dev-master composer install + - if [ "$PIMPLE_EXT" == "yes" ]; then sh -c "cd ext/pimple && phpize && ./configure && make && sudo make install"; fi + - if [ "$PIMPLE_EXT" == "yes" ]; then echo "extension=pimple.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`; fi + +script: + - cd ext/pimple + - if [ "$PIMPLE_EXT" == "yes" ]; then yes n | make test | tee output ; grep -E 'Tests failed +. +0' output; fi + - if [ "$PIMPLE_EXT" == "yes" ]; then export SYMFONY_DEPRECATIONS_HELPER=weak; fi + - cd ../.. + - ./vendor/bin/simple-phpunit + +matrix: + include: + - php: hhvm + dist: trusty + env: PIMPLE_EXT=no + exclude: + - php: 7.0 + env: PIMPLE_EXT=yes + - php: 7.1 + env: PIMPLE_EXT=yes diff --git a/vendor/pimple/pimple/CHANGELOG b/vendor/pimple/pimple/CHANGELOG new file mode 100644 index 00000000..f277b969 --- /dev/null +++ b/vendor/pimple/pimple/CHANGELOG @@ -0,0 +1,55 @@ +* 3.2.2 (2017-07-23) + + * reverted extending a protected closure throws an exception (deprecated it instead) + +* 3.2.1 (2017-07-17) + + * fixed PHP error + +* 3.2.0 (2017-07-17) + + * added a PSR-11 service locator + * added a PSR-11 wrapper + * added ServiceIterator + * fixed extending a protected closure (now throws InvalidServiceIdentifierException) + +* 3.1.0 (2017-07-03) + + * deprecated the C extension + * added support for PSR-11 exceptions + +* 3.0.2 (2015-09-11) + + * refactored the C extension + * minor non-significant changes + +* 3.0.1 (2015-07-30) + + * simplified some code + * fixed a segfault in the C extension + +* 3.0.0 (2014-07-24) + + * removed the Pimple class alias (use Pimple\Container instead) + +* 2.1.1 (2014-07-24) + + * fixed compiler warnings for the C extension + * fixed code when dealing with circular references + +* 2.1.0 (2014-06-24) + + * moved the Pimple to Pimple\Container (with a BC layer -- Pimple is now a + deprecated alias which will be removed in Pimple 3.0) + * added Pimple\ServiceProviderInterface (and Pimple::register()) + +* 2.0.0 (2014-02-10) + + * changed extend to automatically re-assign the extended service and keep it as shared or factory + (to keep BC, extend still returns the extended service) + * changed services to be shared by default (use factory() for factory + services) + +* 1.0.0 + + * initial version diff --git a/vendor/pimple/pimple/LICENSE b/vendor/pimple/pimple/LICENSE new file mode 100644 index 00000000..e02dc5a7 --- /dev/null +++ b/vendor/pimple/pimple/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2009-2017 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/pimple/pimple/README.rst b/vendor/pimple/pimple/README.rst new file mode 100644 index 00000000..d27d8aa3 --- /dev/null +++ b/vendor/pimple/pimple/README.rst @@ -0,0 +1,326 @@ +Pimple +====== + +.. caution:: + + This is the documentation for Pimple 3.x. If you are using Pimple 1.x, read + the `Pimple 1.x documentation`_. Reading the Pimple 1.x code is also a good + way to learn more about how to create a simple Dependency Injection + Container (recent versions of Pimple are more focused on performance). + +Pimple is a small Dependency Injection Container for PHP. + +Installation +------------ + +Before using Pimple in your project, add it to your ``composer.json`` file: + +.. code-block:: bash + + $ ./composer.phar require pimple/pimple "^3.0" + +Usage +----- + +Creating a container is a matter of creating a ``Container`` instance: + +.. code-block:: php + + use Pimple\Container; + + $container = new Container(); + +As many other dependency injection containers, Pimple manages two different +kind of data: **services** and **parameters**. + +Defining Services +~~~~~~~~~~~~~~~~~ + +A service is an object that does something as part of a larger system. Examples +of services: a database connection, a templating engine, or a mailer. Almost +any **global** object can be a service. + +Services are defined by **anonymous functions** that return an instance of an +object: + +.. code-block:: php + + // define some services + $container['session_storage'] = function ($c) { + return new SessionStorage('SESSION_ID'); + }; + + $container['session'] = function ($c) { + return new Session($c['session_storage']); + }; + +Notice that the anonymous function has access to the current container +instance, allowing references to other services or parameters. + +As objects are only created when you get them, the order of the definitions +does not matter. + +Using the defined services is also very easy: + +.. code-block:: php + + // get the session object + $session = $container['session']; + + // the above call is roughly equivalent to the following code: + // $storage = new SessionStorage('SESSION_ID'); + // $session = new Session($storage); + +Defining Factory Services +~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, each time you get a service, Pimple returns the **same instance** +of it. If you want a different instance to be returned for all calls, wrap your +anonymous function with the ``factory()`` method + +.. code-block:: php + + $container['session'] = $container->factory(function ($c) { + return new Session($c['session_storage']); + }); + +Now, each call to ``$container['session']`` returns a new instance of the +session. + +Defining Parameters +~~~~~~~~~~~~~~~~~~~ + +Defining a parameter allows to ease the configuration of your container from +the outside and to store global values: + +.. code-block:: php + + // define some parameters + $container['cookie_name'] = 'SESSION_ID'; + $container['session_storage_class'] = 'SessionStorage'; + +If you change the ``session_storage`` service definition like below: + +.. code-block:: php + + $container['session_storage'] = function ($c) { + return new $c['session_storage_class']($c['cookie_name']); + }; + +You can now easily change the cookie name by overriding the +``session_storage_class`` parameter instead of redefining the service +definition. + +Protecting Parameters +~~~~~~~~~~~~~~~~~~~~~ + +Because Pimple sees anonymous functions as service definitions, you need to +wrap anonymous functions with the ``protect()`` method to store them as +parameters: + +.. code-block:: php + + $container['random_func'] = $container->protect(function () { + return rand(); + }); + +Modifying Services after Definition +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In some cases you may want to modify a service definition after it has been +defined. You can use the ``extend()`` method to define additional code to be +run on your service just after it is created: + +.. code-block:: php + + $container['session_storage'] = function ($c) { + return new $c['session_storage_class']($c['cookie_name']); + }; + + $container->extend('session_storage', function ($storage, $c) { + $storage->...(); + + return $storage; + }); + +The first argument is the name of the service to extend, the second a function +that gets access to the object instance and the container. + +Extending a Container +~~~~~~~~~~~~~~~~~~~~~ + +If you use the same libraries over and over, you might want to reuse some +services from one project to the next one; package your services into a +**provider** by implementing ``Pimple\ServiceProviderInterface``: + +.. code-block:: php + + use Pimple\Container; + + class FooProvider implements Pimple\ServiceProviderInterface + { + public function register(Container $pimple) + { + // register some services and parameters + // on $pimple + } + } + +Then, register the provider on a Container: + +.. code-block:: php + + $pimple->register(new FooProvider()); + +Fetching the Service Creation Function +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When you access an object, Pimple automatically calls the anonymous function +that you defined, which creates the service object for you. If you want to get +raw access to this function, you can use the ``raw()`` method: + +.. code-block:: php + + $container['session'] = function ($c) { + return new Session($c['session_storage']); + }; + + $sessionFunction = $container->raw('session'); + +PSR-11 compatibility +-------------------- + +For historical reasons, the ``Container`` class does not implement the PSR-11 +``ContainerInterface``. However, Pimple provides a helper class that will let +you decouple your code from the Pimple container class. + +The PSR-11 container class +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``Pimple\Psr11\Container`` class lets you access the content of an +underlying Pimple container using ``Psr\Container\ContainerInterface`` +methods: + +.. code-block:: php + + use Pimple\Container; + use Pimple\Psr11\Container as PsrContainer; + + $container = new Container(); + $container['service'] = function ($c) { + return new Service(); + }; + $psr11 = new PsrContainer($container); + + $controller = function (PsrContainer $container) { + $service = $container->get('service'); + }; + $controller($psr11); + +Using the PSR-11 ServiceLocator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes, a service needs access to several other services without being sure +that all of them will actually be used. In those cases, you may want the +instantiation of the services to be lazy. + +The traditional solution is to inject the entire service container to get only +the services really needed. However, this is not recommended because it gives +services a too broad access to the rest of the application and it hides their +actual dependencies. + +The ``ServiceLocator`` is intended to solve this problem by giving access to a +set of predefined services while instantiating them only when actually needed. + +It also allows you to make your services available under a different name than +the one used to register them. For instance, you may want to use an object +that expects an instance of ``EventDispatcherInterface`` to be available under +the name ``event_dispatcher`` while your event dispatcher has been +registered under the name ``dispatcher``: + +.. code-block:: php + + use Monolog\Logger; + use Pimple\Psr11\ServiceLocator; + use Psr\Container\ContainerInterface; + use Symfony\Component\EventDispatcher\EventDispatcher; + + class MyService + { + /** + * "logger" must be an instance of Psr\Log\LoggerInterface + * "event_dispatcher" must be an instance of Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + private $services; + + public function __construct(ContainerInterface $services) + { + $this->services = $services; + } + } + + $container['logger'] = function ($c) { + return new Monolog\Logger(); + }; + $container['dispatcher'] = function () { + return new EventDispatcher(); + }; + + $container['service'] = function ($c) { + $locator = new ServiceLocator($c, array('logger', 'event_dispatcher' => 'dispatcher')); + + return new MyService($locator); + }; + +Referencing a Collection of Services Lazily +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Passing a collection of services instances in an array may prove inefficient +if the class that consumes the collection only needs to iterate over it at a +later stage, when one of its method is called. It can also lead to problems +if there is a circular dependency between one of the services stored in the +collection and the class that consumes it. + +The ``ServiceIterator`` class helps you solve these issues. It receives a +list of service names during instantiation and will retrieve the services +when iterated over: + +.. code-block:: php + + use Pimple\Container; + use Pimple\ServiceIterator; + + class AuthorizationService + { + private $voters; + + public function __construct($voters) + { + $this->voters = $voters; + } + + public function canAccess($resource) + { + foreach ($this->voters as $voter) { + if (true === $voter->canAccess($resource) { + return true; + } + } + + return false; + } + } + + $container = new Container(); + + $container['voter1'] = function ($c) { + return new SomeVoter(); + } + $container['voter2'] = function ($c) { + return new SomeOtherVoter($c['auth']); + } + $container['auth'] = function ($c) { + return new AuthorizationService(new ServiceIterator($c, array('voter1', 'voter2')); + } + +.. _Pimple 1.x documentation: https://github.com/silexphp/Pimple/tree/1.1 diff --git a/vendor/pimple/pimple/composer.json b/vendor/pimple/pimple/composer.json new file mode 100644 index 00000000..dabf190a --- /dev/null +++ b/vendor/pimple/pimple/composer.json @@ -0,0 +1,29 @@ +{ + "name": "pimple/pimple", + "type": "library", + "description": "Pimple, a simple Dependency Injection Container", + "keywords": ["dependency injection", "container"], + "homepage": "http://pimple.sensiolabs.org", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "require": { + "php": ">=5.3.0", + "psr/container": "^1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.2" + }, + "autoload": { + "psr-0": { "Pimple": "src/" } + }, + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + } +} diff --git a/vendor/pimple/pimple/ext/pimple/.gitignore b/vendor/pimple/pimple/ext/pimple/.gitignore new file mode 100644 index 00000000..1861088a --- /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/pimple/pimple/ext/pimple/README.md b/vendor/pimple/pimple/ext/pimple/README.md new file mode 100644 index 00000000..7b39eb29 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/README.md @@ -0,0 +1,12 @@ +This is Pimple 2 implemented in C + +* PHP >= 5.3 +* Not tested under Windows, might work + +Install +======= + + > phpize + > ./configure + > make + > make install diff --git a/vendor/pimple/pimple/ext/pimple/config.m4 b/vendor/pimple/pimple/ext/pimple/config.m4 new file mode 100644 index 00000000..3a6e9aae --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/config.m4 @@ -0,0 +1,63 @@ +dnl $Id$ +dnl config.m4 for extension pimple + +dnl Comments in this file start with the string 'dnl'. +dnl Remove where necessary. This file will not work +dnl without editing. + +dnl If your extension references something external, use with: + +dnl PHP_ARG_WITH(pimple, for pimple support, +dnl Make sure that the comment is aligned: +dnl [ --with-pimple Include pimple support]) + +dnl Otherwise use enable: + +PHP_ARG_ENABLE(pimple, whether to enable pimple support, +dnl Make sure that the comment is aligned: +[ --enable-pimple Enable pimple support]) + +if test "$PHP_PIMPLE" != "no"; then + dnl Write more examples of tests here... + + dnl # --with-pimple -> check with-path + dnl SEARCH_PATH="/usr/local /usr" # you might want to change this + dnl SEARCH_FOR="/include/pimple.h" # you most likely want to change this + dnl if test -r $PHP_PIMPLE/$SEARCH_FOR; then # path given as parameter + dnl PIMPLE_DIR=$PHP_PIMPLE + dnl else # search default path list + dnl AC_MSG_CHECKING([for pimple files in default path]) + dnl for i in $SEARCH_PATH ; do + dnl if test -r $i/$SEARCH_FOR; then + dnl PIMPLE_DIR=$i + dnl AC_MSG_RESULT(found in $i) + dnl fi + dnl done + dnl fi + dnl + dnl if test -z "$PIMPLE_DIR"; then + dnl AC_MSG_RESULT([not found]) + dnl AC_MSG_ERROR([Please reinstall the pimple distribution]) + dnl fi + + dnl # --with-pimple -> add include path + dnl PHP_ADD_INCLUDE($PIMPLE_DIR/include) + + dnl # --with-pimple -> check for lib and symbol presence + dnl LIBNAME=pimple # you may want to change this + dnl LIBSYMBOL=pimple # you most likely want to change this + + dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, + dnl [ + dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $PIMPLE_DIR/lib, PIMPLE_SHARED_LIBADD) + dnl AC_DEFINE(HAVE_PIMPLELIB,1,[ ]) + dnl ],[ + dnl AC_MSG_ERROR([wrong pimple lib version or lib not found]) + dnl ],[ + dnl -L$PIMPLE_DIR/lib -lm + dnl ]) + dnl + dnl PHP_SUBST(PIMPLE_SHARED_LIBADD) + + PHP_NEW_EXTENSION(pimple, pimple.c, $ext_shared) +fi diff --git a/vendor/pimple/pimple/ext/pimple/config.w32 b/vendor/pimple/pimple/ext/pimple/config.w32 new file mode 100644 index 00000000..39857b32 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/config.w32 @@ -0,0 +1,13 @@ +// $Id$ +// vim:ft=javascript + +// If your extension references something external, use ARG_WITH +// ARG_WITH("pimple", "for pimple support", "no"); + +// Otherwise, use ARG_ENABLE +// ARG_ENABLE("pimple", "enable pimple support", "no"); + +if (PHP_PIMPLE != "no") { + EXTENSION("pimple", "pimple.c"); +} + diff --git a/vendor/pimple/pimple/ext/pimple/php_pimple.h b/vendor/pimple/pimple/ext/pimple/php_pimple.h new file mode 100644 index 00000000..258f3eea --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/php_pimple.h @@ -0,0 +1,137 @@ + +/* + * This file is part of Pimple. + * + * Copyright (c) 2014 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. + */ + +#ifndef PHP_PIMPLE_H +#define PHP_PIMPLE_H + +extern zend_module_entry pimple_module_entry; +#define phpext_pimple_ptr &pimple_module_entry + +#ifdef PHP_WIN32 +# define PHP_PIMPLE_API __declspec(dllexport) +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_PIMPLE_API __attribute__ ((visibility("default"))) +#else +# define PHP_PIMPLE_API +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +#define PIMPLE_VERSION "3.2.2-DEV" + +#define PIMPLE_NS "Pimple" +#define PSR_CONTAINER_NS "Psr\\Container" +#define PIMPLE_EXCEPTION_NS "Pimple\\Exception" + +#define PIMPLE_DEFAULT_ZVAL_CACHE_NUM 5 +#define PIMPLE_DEFAULT_ZVAL_VALUES_NUM 10 + +#define PIMPLE_DEPRECATE do { \ + int er = EG(error_reporting); \ + EG(error_reporting) = 0;\ + php_error(E_DEPRECATED, "The Pimple C extension is deprecated since version 3.1 and will be removed in 4.0."); \ + EG(error_reporting) = er; \ +} while (0); + +zend_module_entry *get_module(void); + +PHP_MINIT_FUNCTION(pimple); +PHP_MINFO_FUNCTION(pimple); + +PHP_METHOD(FrozenServiceException, __construct); +PHP_METHOD(InvalidServiceIdentifierException, __construct); +PHP_METHOD(UnknownIdentifierException, __construct); + +PHP_METHOD(Pimple, __construct); +PHP_METHOD(Pimple, factory); +PHP_METHOD(Pimple, protect); +PHP_METHOD(Pimple, raw); +PHP_METHOD(Pimple, extend); +PHP_METHOD(Pimple, keys); +PHP_METHOD(Pimple, register); +PHP_METHOD(Pimple, offsetSet); +PHP_METHOD(Pimple, offsetUnset); +PHP_METHOD(Pimple, offsetGet); +PHP_METHOD(Pimple, offsetExists); + +PHP_METHOD(PimpleClosure, invoker); + +typedef struct _pimple_bucket_value { + zval *value; /* Must be the first element */ + zval *raw; + zend_object_handle handle_num; + enum { + PIMPLE_IS_PARAM = 0, + PIMPLE_IS_SERVICE = 2 + } type; + zend_bool initialized; + zend_fcall_info_cache fcc; +} pimple_bucket_value; + +typedef struct _pimple_object { + zend_object zobj; + HashTable values; + HashTable factories; + HashTable protected; +} pimple_object; + +typedef struct _pimple_closure_object { + zend_object zobj; + zval *callable; + zval *factory; +} pimple_closure_object; + +static const char sensiolabs_logo[] = ""; + +static void pimple_exception_call_parent_constructor(zval *this_ptr, const char *format, const char *arg1 TSRMLS_DC); + +static int pimple_zval_to_pimpleval(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC); +static int pimple_zval_is_valid_callback(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC); + +static void pimple_bucket_dtor(pimple_bucket_value *bucket); +static void pimple_free_bucket(pimple_bucket_value *bucket); + +static zval *pimple_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC); +static void pimple_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC); +static int pimple_object_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC); +static void pimple_object_unset_dimension(zval *object, zval *offset TSRMLS_DC); +static zend_object_value pimple_object_create(zend_class_entry *ce TSRMLS_DC); +static void pimple_free_object_storage(pimple_object *obj TSRMLS_DC); + +static void pimple_closure_free_object_storage(pimple_closure_object *obj TSRMLS_DC); +static zend_object_value pimple_closure_object_create(zend_class_entry *ce TSRMLS_DC); +static zend_function *pimple_closure_get_constructor(zval * TSRMLS_DC); +static int pimple_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, union _zend_function **fptr_ptr, zval **zobj_ptr TSRMLS_DC); + +#ifdef ZTS +#define PIMPLE_G(v) TSRMG(pimple_globals_id, zend_pimple_globals *, v) +#else +#define PIMPLE_G(v) (pimple_globals.v) +#endif + +#endif /* PHP_PIMPLE_H */ + diff --git a/vendor/pimple/pimple/ext/pimple/pimple.c b/vendor/pimple/pimple/ext/pimple/pimple.c new file mode 100644 index 00000000..c80499b3 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/pimple.c @@ -0,0 +1,1114 @@ + +/* + * This file is part of Pimple. + * + * Copyright (c) 2014 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_pimple.h" +#include "pimple_compat.h" +#include "zend_interfaces.h" +#include "zend.h" +#include "Zend/zend_closures.h" +#include "ext/spl/spl_exceptions.h" +#include "Zend/zend_exceptions.h" +#include "main/php_output.h" +#include "SAPI.h" + +static zend_class_entry *pimple_ce_PsrContainerInterface; +static zend_class_entry *pimple_ce_PsrContainerExceptionInterface; +static zend_class_entry *pimple_ce_PsrNotFoundExceptionInterface; + +static zend_class_entry *pimple_ce_ExpectedInvokableException; +static zend_class_entry *pimple_ce_FrozenServiceException; +static zend_class_entry *pimple_ce_InvalidServiceIdentifierException; +static zend_class_entry *pimple_ce_UnknownIdentifierException; + +static zend_class_entry *pimple_ce; +static zend_object_handlers pimple_object_handlers; +static zend_class_entry *pimple_closure_ce; +static zend_class_entry *pimple_serviceprovider_ce; +static zend_object_handlers pimple_closure_object_handlers; +static zend_internal_function pimple_closure_invoker_function; + +#define FETCH_DIM_HANDLERS_VARS pimple_object *pimple_obj = NULL; \ + ulong index; \ + pimple_obj = (pimple_object *)zend_object_store_get_object(object TSRMLS_CC); \ + +#define PIMPLE_OBJECT_HANDLE_INHERITANCE_OBJECT_HANDLERS do { \ + if (ce != pimple_ce) { \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetget"), (void **)&function); \ + if (function->common.scope != ce) { /* if the function is not defined in this actual class */ \ + pimple_object_handlers.read_dimension = pimple_object_read_dimension; /* then overwrite the handler to use custom one */ \ + } \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetset"), (void **)&function); \ + if (function->common.scope != ce) { \ + pimple_object_handlers.write_dimension = pimple_object_write_dimension; \ + } \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetexists"), (void **)&function); \ + if (function->common.scope != ce) { \ + pimple_object_handlers.has_dimension = pimple_object_has_dimension; \ + } \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetunset"), (void **)&function); \ + if (function->common.scope != ce) { \ + pimple_object_handlers.unset_dimension = pimple_object_unset_dimension; \ + } \ + } else { \ + pimple_object_handlers.read_dimension = pimple_object_read_dimension; \ + pimple_object_handlers.write_dimension = pimple_object_write_dimension; \ + pimple_object_handlers.has_dimension = pimple_object_has_dimension; \ + pimple_object_handlers.unset_dimension = pimple_object_unset_dimension; \ + }\ + } while(0); + +#define PIMPLE_CALL_CB do { \ + zend_fcall_info_argn(&fci TSRMLS_CC, 1, &object); \ + fci.size = sizeof(fci); \ + fci.object_ptr = retval->fcc.object_ptr; \ + fci.function_name = retval->value; \ + fci.no_separation = 1; \ + fci.retval_ptr_ptr = &retval_ptr_ptr; \ +\ + zend_call_function(&fci, &retval->fcc TSRMLS_CC); \ + efree(fci.params); \ + if (EG(exception)) { \ + return EG(uninitialized_zval_ptr); \ + } \ + } while(0); + + +/* Psr\Container\ContainerInterface */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_pimple_PsrContainerInterface_get, 0, 0, 1) +ZEND_ARG_INFO(0, id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_pimple_PsrContainerInterface_has, 0, 0, 1) +ZEND_ARG_INFO(0, id) +ZEND_END_ARG_INFO() + +static const zend_function_entry pimple_ce_PsrContainerInterface_functions[] = { + PHP_ABSTRACT_ME(ContainerInterface, get, arginfo_pimple_PsrContainerInterface_get) + PHP_ABSTRACT_ME(ContainerInterface, has, arginfo_pimple_PsrContainerInterface_has) + PHP_FE_END +}; + +/* Psr\Container\ContainerExceptionInterface */ +static const zend_function_entry pimple_ce_PsrContainerExceptionInterface_functions[] = { + PHP_FE_END +}; + +/* Psr\Container\NotFoundExceptionInterface */ +static const zend_function_entry pimple_ce_PsrNotFoundExceptionInterface_functions[] = { + PHP_FE_END +}; + +/* Pimple\Exception\FrozenServiceException */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_FrozenServiceException___construct, 0, 0, 1) +ZEND_ARG_INFO(0, id) +ZEND_END_ARG_INFO() + +static const zend_function_entry pimple_ce_FrozenServiceException_functions[] = { + PHP_ME(FrozenServiceException, __construct, arginfo_FrozenServiceException___construct, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +/* Pimple\Exception\InvalidServiceIdentifierException */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_InvalidServiceIdentifierException___construct, 0, 0, 1) +ZEND_ARG_INFO(0, id) +ZEND_END_ARG_INFO() + +static const zend_function_entry pimple_ce_InvalidServiceIdentifierException_functions[] = { + PHP_ME(InvalidServiceIdentifierException, __construct, arginfo_InvalidServiceIdentifierException___construct, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +/* Pimple\Exception\UnknownIdentifierException */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_UnknownIdentifierException___construct, 0, 0, 1) +ZEND_ARG_INFO(0, id) +ZEND_END_ARG_INFO() + +static const zend_function_entry pimple_ce_UnknownIdentifierException_functions[] = { + PHP_ME(UnknownIdentifierException, __construct, arginfo_UnknownIdentifierException___construct, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +/* Pimple\Container */ +ZEND_BEGIN_ARG_INFO_EX(arginfo___construct, 0, 0, 0) +ZEND_ARG_ARRAY_INFO(0, value, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetset, 0, 0, 2) +ZEND_ARG_INFO(0, offset) +ZEND_ARG_INFO(0, value) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetget, 0, 0, 1) +ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetexists, 0, 0, 1) +ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetunset, 0, 0, 1) +ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_factory, 0, 0, 1) +ZEND_ARG_INFO(0, callable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_protect, 0, 0, 1) +ZEND_ARG_INFO(0, callable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_raw, 0, 0, 1) +ZEND_ARG_INFO(0, id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_extend, 0, 0, 2) +ZEND_ARG_INFO(0, id) +ZEND_ARG_INFO(0, callable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_keys, 0, 0, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_register, 0, 0, 1) +ZEND_ARG_OBJ_INFO(0, provider, Pimple\\ServiceProviderInterface, 0) +ZEND_ARG_ARRAY_INFO(0, values, 1) +ZEND_END_ARG_INFO() + +static const zend_function_entry pimple_ce_functions[] = { + PHP_ME(Pimple, __construct, arginfo___construct, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, factory, arginfo_factory, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, protect, arginfo_protect, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, raw, arginfo_raw, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, extend, arginfo_extend, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, keys, arginfo_keys, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, register, arginfo_register, ZEND_ACC_PUBLIC) + + PHP_ME(Pimple, offsetSet, arginfo_offsetset, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, offsetGet, arginfo_offsetget, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, offsetExists, arginfo_offsetexists, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, offsetUnset, arginfo_offsetunset, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +/* Pimple\ServiceProviderInterface */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_serviceprovider_register, 0, 0, 1) +ZEND_ARG_OBJ_INFO(0, pimple, Pimple\\Container, 0) +ZEND_END_ARG_INFO() + +static const zend_function_entry pimple_serviceprovider_iface_ce_functions[] = { + PHP_ABSTRACT_ME(ServiceProviderInterface, register, arginfo_serviceprovider_register) + PHP_FE_END +}; + +/* parent::__construct(sprintf("Something with %s", $arg1)) */ +static void pimple_exception_call_parent_constructor(zval *this_ptr, const char *format, const char *arg1 TSRMLS_DC) +{ + zend_class_entry *ce = Z_OBJCE_P(this_ptr); + char *message = NULL; + int message_len; + zval *constructor_arg; + + message_len = spprintf(&message, 0, format, arg1); + ALLOC_INIT_ZVAL(constructor_arg); + ZVAL_STRINGL(constructor_arg, message, message_len, 1); + + zend_call_method_with_1_params(&this_ptr, ce, &ce->parent->constructor, "__construct", NULL, constructor_arg); + + efree(message); + zval_ptr_dtor(&constructor_arg); +} + +/** + * Pass a single string parameter to exception constructor and throw + */ +static void pimple_throw_exception_string(zend_class_entry *ce, const char *message, zend_uint message_len TSRMLS_DC) +{ + zval *exception, *param; + + ALLOC_INIT_ZVAL(exception); + object_init_ex(exception, ce); + + ALLOC_INIT_ZVAL(param); + ZVAL_STRINGL(param, message, message_len, 1); + + zend_call_method_with_1_params(&exception, ce, &ce->constructor, "__construct", NULL, param); + + zend_throw_exception_object(exception TSRMLS_CC); + + zval_ptr_dtor(¶m); +} + +static void pimple_closure_free_object_storage(pimple_closure_object *obj TSRMLS_DC) +{ + zend_object_std_dtor(&obj->zobj TSRMLS_CC); + if (obj->factory) { + zval_ptr_dtor(&obj->factory); + } + if (obj->callable) { + zval_ptr_dtor(&obj->callable); + } + efree(obj); +} + +static void pimple_free_object_storage(pimple_object *obj TSRMLS_DC) +{ + zend_hash_destroy(&obj->factories); + zend_hash_destroy(&obj->protected); + zend_hash_destroy(&obj->values); + zend_object_std_dtor(&obj->zobj TSRMLS_CC); + efree(obj); +} + +static void pimple_free_bucket(pimple_bucket_value *bucket) +{ + if (bucket->raw) { + zval_ptr_dtor(&bucket->raw); + } +} + +static zend_object_value pimple_closure_object_create(zend_class_entry *ce TSRMLS_DC) +{ + zend_object_value retval; + pimple_closure_object *pimple_closure_obj = NULL; + + pimple_closure_obj = ecalloc(1, sizeof(pimple_closure_object)); + ZEND_OBJ_INIT(&pimple_closure_obj->zobj, ce); + + pimple_closure_object_handlers.get_constructor = pimple_closure_get_constructor; + retval.handlers = &pimple_closure_object_handlers; + retval.handle = zend_objects_store_put(pimple_closure_obj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) pimple_closure_free_object_storage, NULL TSRMLS_CC); + + return retval; +} + +static zend_function *pimple_closure_get_constructor(zval *obj TSRMLS_DC) +{ + zend_error(E_ERROR, "Pimple\\ContainerClosure is an internal class and cannot be instantiated"); + + return NULL; +} + +static int pimple_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, union _zend_function **fptr_ptr, zval **zobj_ptr TSRMLS_DC) +{ + *zobj_ptr = obj; + *ce_ptr = Z_OBJCE_P(obj); + *fptr_ptr = (zend_function *)&pimple_closure_invoker_function; + + return SUCCESS; +} + +static zend_object_value pimple_object_create(zend_class_entry *ce TSRMLS_DC) +{ + zend_object_value retval; + pimple_object *pimple_obj = NULL; + zend_function *function = NULL; + + pimple_obj = emalloc(sizeof(pimple_object)); + ZEND_OBJ_INIT(&pimple_obj->zobj, ce); + + PIMPLE_OBJECT_HANDLE_INHERITANCE_OBJECT_HANDLERS + + retval.handlers = &pimple_object_handlers; + retval.handle = zend_objects_store_put(pimple_obj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) pimple_free_object_storage, NULL TSRMLS_CC); + + zend_hash_init(&pimple_obj->factories, PIMPLE_DEFAULT_ZVAL_CACHE_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0); + zend_hash_init(&pimple_obj->protected, PIMPLE_DEFAULT_ZVAL_CACHE_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0); + zend_hash_init(&pimple_obj->values, PIMPLE_DEFAULT_ZVAL_VALUES_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0); + + return retval; +} + +static void pimple_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + pimple_bucket_value pimple_value = {0}, *found_value = NULL; + ulong hash; + + pimple_zval_to_pimpleval(value, &pimple_value TSRMLS_CC); + + if (!offset) {/* $p[] = 'foo' when not overloaded */ + zend_hash_next_index_insert(&pimple_obj->values, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL); + Z_ADDREF_P(value); + return; + } + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + hash = zend_hash_func(Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + zend_hash_quick_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, hash, (void **)&found_value); + if (found_value && found_value->type == PIMPLE_IS_SERVICE && found_value->initialized == 1) { + pimple_free_bucket(&pimple_value); + pimple_throw_exception_string(pimple_ce_FrozenServiceException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC); + return; + } + if (zend_hash_quick_update(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, hash, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL) == FAILURE) { + pimple_free_bucket(&pimple_value); + return; + } + Z_ADDREF_P(value); + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + zend_hash_index_find(&pimple_obj->values, index, (void **)&found_value); + if (found_value && found_value->type == PIMPLE_IS_SERVICE && found_value->initialized == 1) { + pimple_free_bucket(&pimple_value); + convert_to_string(offset); + pimple_throw_exception_string(pimple_ce_FrozenServiceException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC); + return; + } + if (zend_hash_index_update(&pimple_obj->values, index, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL) == FAILURE) { + pimple_free_bucket(&pimple_value); + return; + } + Z_ADDREF_P(value); + break; + case IS_NULL: /* $p[] = 'foo' when overloaded */ + zend_hash_next_index_insert(&pimple_obj->values, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL); + Z_ADDREF_P(value); + break; + default: + pimple_free_bucket(&pimple_value); + zend_error(E_WARNING, "Unsupported offset type"); + } +} + +static void pimple_object_unset_dimension(zval *object, zval *offset TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + zend_symtable_del(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + zend_symtable_del(&pimple_obj->factories, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + zend_symtable_del(&pimple_obj->protected, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + zend_hash_index_del(&pimple_obj->values, index); + zend_hash_index_del(&pimple_obj->factories, index); + zend_hash_index_del(&pimple_obj->protected, index); + break; + default: + zend_error(E_WARNING, "Unsupported offset type"); + } +} + +static int pimple_object_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + pimple_bucket_value *retval = NULL; + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void **)&retval) == SUCCESS) { + switch (check_empty) { + case 0: /* isset */ + return 1; /* Differs from PHP behavior (Z_TYPE_P(retval->value) != IS_NULL;) */ + case 1: /* empty */ + default: + return zend_is_true(retval->value); + } + } + return 0; + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pimple_obj->values, index, (void **)&retval) == SUCCESS) { + switch (check_empty) { + case 0: /* isset */ + return 1; /* Differs from PHP behavior (Z_TYPE_P(retval->value) != IS_NULL;)*/ + case 1: /* empty */ + default: + return zend_is_true(retval->value); + } + } + return 0; + break; + default: + zend_error(E_WARNING, "Unsupported offset type"); + return 0; + } +} + +static zval *pimple_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + pimple_bucket_value *retval = NULL; + zend_fcall_info fci = {0}; + zval *retval_ptr_ptr = NULL; + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void **)&retval) == FAILURE) { + pimple_throw_exception_string(pimple_ce_UnknownIdentifierException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC); + + return EG(uninitialized_zval_ptr); + } + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pimple_obj->values, index, (void **)&retval) == FAILURE) { + return EG(uninitialized_zval_ptr); + } + break; + case IS_NULL: /* $p[][3] = 'foo' first dim access */ + return EG(uninitialized_zval_ptr); + break; + default: + zend_error(E_WARNING, "Unsupported offset type"); + return EG(uninitialized_zval_ptr); + } + + if(retval->type == PIMPLE_IS_PARAM) { + return retval->value; + } + + if (zend_hash_index_exists(&pimple_obj->protected, retval->handle_num)) { + /* Service is protected, return the value every time */ + return retval->value; + } + + if (zend_hash_index_exists(&pimple_obj->factories, retval->handle_num)) { + /* Service is a factory, call it every time and never cache its result */ + PIMPLE_CALL_CB + Z_DELREF_P(retval_ptr_ptr); /* fetch dim addr will increment refcount */ + return retval_ptr_ptr; + } + + if (retval->initialized == 1) { + /* Service has already been called, return its cached value */ + return retval->value; + } + + ALLOC_INIT_ZVAL(retval->raw); + MAKE_COPY_ZVAL(&retval->value, retval->raw); + + PIMPLE_CALL_CB + + retval->initialized = 1; + zval_ptr_dtor(&retval->value); + retval->value = retval_ptr_ptr; + + return retval->value; +} + +static int pimple_zval_is_valid_callback(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC) +{ + if (Z_TYPE_P(_zval) != IS_OBJECT) { + return FAILURE; + } + + if (_pimple_bucket_value->fcc.called_scope) { + return SUCCESS; + } + + if (Z_OBJ_HANDLER_P(_zval, get_closure) && Z_OBJ_HANDLER_P(_zval, get_closure)(_zval, &_pimple_bucket_value->fcc.calling_scope, &_pimple_bucket_value->fcc.function_handler, &_pimple_bucket_value->fcc.object_ptr TSRMLS_CC) == SUCCESS) { + _pimple_bucket_value->fcc.called_scope = _pimple_bucket_value->fcc.calling_scope; + return SUCCESS; + } else { + return FAILURE; + } +} + +static int pimple_zval_to_pimpleval(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC) +{ + _pimple_bucket_value->value = _zval; + + if (Z_TYPE_P(_zval) != IS_OBJECT) { + return PIMPLE_IS_PARAM; + } + + if (pimple_zval_is_valid_callback(_zval, _pimple_bucket_value TSRMLS_CC) == SUCCESS) { + _pimple_bucket_value->type = PIMPLE_IS_SERVICE; + _pimple_bucket_value->handle_num = Z_OBJ_HANDLE_P(_zval); + } + + return PIMPLE_IS_SERVICE; +} + +static void pimple_bucket_dtor(pimple_bucket_value *bucket) +{ + zval_ptr_dtor(&bucket->value); + pimple_free_bucket(bucket); +} + +PHP_METHOD(FrozenServiceException, __construct) +{ + char *id = NULL; + int id_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &id, &id_len) == FAILURE) { + return; + } + pimple_exception_call_parent_constructor(getThis(), "Cannot override frozen service \"%s\".", id TSRMLS_CC); +} + +PHP_METHOD(InvalidServiceIdentifierException, __construct) +{ + char *id = NULL; + int id_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &id, &id_len) == FAILURE) { + return; + } + pimple_exception_call_parent_constructor(getThis(), "Identifier \"%s\" does not contain an object definition.", id TSRMLS_CC); +} + +PHP_METHOD(UnknownIdentifierException, __construct) +{ + char *id = NULL; + int id_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &id, &id_len) == FAILURE) { + return; + } + pimple_exception_call_parent_constructor(getThis(), "Identifier \"%s\" is not defined.", id TSRMLS_CC); +} + +PHP_METHOD(Pimple, protect) +{ + zval *protected = NULL; + pimple_object *pobj = NULL; + pimple_bucket_value bucket = {0}; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &protected) == FAILURE) { + return; + } + + if (pimple_zval_is_valid_callback(protected, &bucket TSRMLS_CC) == FAILURE) { + pimple_free_bucket(&bucket); + zend_throw_exception(pimple_ce_ExpectedInvokableException, "Callable is not a Closure or invokable object.", 0 TSRMLS_CC); + return; + } + + pimple_zval_to_pimpleval(protected, &bucket TSRMLS_CC); + pobj = (pimple_object *)zend_object_store_get_object(getThis() TSRMLS_CC); + + if (zend_hash_index_update(&pobj->protected, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL) == SUCCESS) { + Z_ADDREF_P(protected); + RETURN_ZVAL(protected, 1 , 0); + } else { + pimple_free_bucket(&bucket); + } + RETURN_FALSE; +} + +PHP_METHOD(Pimple, raw) +{ + zval *offset = NULL; + pimple_object *pobj = NULL; + pimple_bucket_value *value = NULL; + ulong index; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + pobj = zend_object_store_get_object(getThis() TSRMLS_CC); + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pobj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void *)&value) == FAILURE) { + pimple_throw_exception_string(pimple_ce_UnknownIdentifierException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC); + RETURN_NULL(); + } + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pobj->values, index, (void *)&value) == FAILURE) { + RETURN_NULL(); + } + break; + case IS_NULL: + default: + zend_error(E_WARNING, "Unsupported offset type"); + } + + if (value->raw) { + RETVAL_ZVAL(value->raw, 1, 0); + } else { + RETVAL_ZVAL(value->value, 1, 0); + } +} + +PHP_METHOD(Pimple, extend) +{ + zval *offset = NULL, *callable = NULL, *pimple_closure_obj = NULL; + pimple_bucket_value bucket = {0}, *value = NULL; + pimple_object *pobj = NULL; + pimple_closure_object *pcobj = NULL; + ulong index; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &offset, &callable) == FAILURE) { + return; + } + + pobj = zend_object_store_get_object(getThis() TSRMLS_CC); + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pobj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void *)&value) == FAILURE) { + pimple_throw_exception_string(pimple_ce_UnknownIdentifierException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC); + RETURN_NULL(); + } + + if (value->type != PIMPLE_IS_SERVICE) { + pimple_throw_exception_string(pimple_ce_InvalidServiceIdentifierException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC); + RETURN_NULL(); + } + if (zend_hash_index_exists(&pobj->protected, value->handle_num)) { + int er = EG(error_reporting); + EG(error_reporting) = 0; + php_error(E_DEPRECATED, "How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure \"%s\" should be protected?", Z_STRVAL_P(offset)); + EG(error_reporting) = er; + } + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pobj->values, index, (void *)&value) == FAILURE) { + convert_to_string(offset); + pimple_throw_exception_string(pimple_ce_UnknownIdentifierException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC); + RETURN_NULL(); + } + if (value->type != PIMPLE_IS_SERVICE) { + convert_to_string(offset); + pimple_throw_exception_string(pimple_ce_InvalidServiceIdentifierException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC); + RETURN_NULL(); + } + if (zend_hash_index_exists(&pobj->protected, value->handle_num)) { + int er = EG(error_reporting); + EG(error_reporting) = 0; + php_error(E_DEPRECATED, "How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure \"%ld\" should be protected?", index); + EG(error_reporting) = er; + } + break; + case IS_NULL: + default: + zend_error(E_WARNING, "Unsupported offset type"); + } + + if (pimple_zval_is_valid_callback(callable, &bucket TSRMLS_CC) == FAILURE) { + pimple_free_bucket(&bucket); + zend_throw_exception(pimple_ce_ExpectedInvokableException, "Extension service definition is not a Closure or invokable object.", 0 TSRMLS_CC); + RETURN_NULL(); + } + pimple_free_bucket(&bucket); + + ALLOC_INIT_ZVAL(pimple_closure_obj); + object_init_ex(pimple_closure_obj, pimple_closure_ce); + + pcobj = zend_object_store_get_object(pimple_closure_obj TSRMLS_CC); + pcobj->callable = callable; + pcobj->factory = value->value; + Z_ADDREF_P(callable); + Z_ADDREF_P(value->value); + + if (zend_hash_index_exists(&pobj->factories, value->handle_num)) { + pimple_zval_to_pimpleval(pimple_closure_obj, &bucket TSRMLS_CC); + zend_hash_index_del(&pobj->factories, value->handle_num); + zend_hash_index_update(&pobj->factories, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL); + Z_ADDREF_P(pimple_closure_obj); + } + + pimple_object_write_dimension(getThis(), offset, pimple_closure_obj TSRMLS_CC); + + RETVAL_ZVAL(pimple_closure_obj, 1, 1); +} + +PHP_METHOD(Pimple, keys) +{ + HashPosition pos; + pimple_object *pobj = NULL; + zval **value = NULL; + zval *endval = NULL; + char *str_index = NULL; + int str_len; + ulong num_index; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + pobj = zend_object_store_get_object(getThis() TSRMLS_CC); + array_init_size(return_value, zend_hash_num_elements(&pobj->values)); + + zend_hash_internal_pointer_reset_ex(&pobj->values, &pos); + + while(zend_hash_get_current_data_ex(&pobj->values, (void **)&value, &pos) == SUCCESS) { + MAKE_STD_ZVAL(endval); + switch (zend_hash_get_current_key_ex(&pobj->values, &str_index, (uint *)&str_len, &num_index, 0, &pos)) { + case HASH_KEY_IS_STRING: + ZVAL_STRINGL(endval, str_index, str_len - 1, 1); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &endval, sizeof(zval *), NULL); + break; + case HASH_KEY_IS_LONG: + ZVAL_LONG(endval, num_index); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &endval, sizeof(zval *), NULL); + break; + } + zend_hash_move_forward_ex(&pobj->values, &pos); + } +} + +PHP_METHOD(Pimple, factory) +{ + zval *factory = NULL; + pimple_object *pobj = NULL; + pimple_bucket_value bucket = {0}; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &factory) == FAILURE) { + return; + } + + if (pimple_zval_is_valid_callback(factory, &bucket TSRMLS_CC) == FAILURE) { + pimple_free_bucket(&bucket); + zend_throw_exception(pimple_ce_ExpectedInvokableException, "Service definition is not a Closure or invokable object.", 0 TSRMLS_CC); + return; + } + + pimple_zval_to_pimpleval(factory, &bucket TSRMLS_CC); + pobj = (pimple_object *)zend_object_store_get_object(getThis() TSRMLS_CC); + + if (zend_hash_index_update(&pobj->factories, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL) == SUCCESS) { + Z_ADDREF_P(factory); + RETURN_ZVAL(factory, 1 , 0); + } else { + pimple_free_bucket(&bucket); + } + + RETURN_FALSE; +} + +PHP_METHOD(Pimple, offsetSet) +{ + zval *offset = NULL, *value = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &offset, &value) == FAILURE) { + return; + } + + pimple_object_write_dimension(getThis(), offset, value TSRMLS_CC); +} + +PHP_METHOD(Pimple, offsetGet) +{ + zval *offset = NULL, *retval = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + retval = pimple_object_read_dimension(getThis(), offset, 0 TSRMLS_CC); + + RETVAL_ZVAL(retval, 1, 0); +} + +PHP_METHOD(Pimple, offsetUnset) +{ + zval *offset = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + pimple_object_unset_dimension(getThis(), offset TSRMLS_CC); +} + +PHP_METHOD(Pimple, offsetExists) +{ + zval *offset = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + RETVAL_BOOL(pimple_object_has_dimension(getThis(), offset, 1 TSRMLS_CC)); +} + +PHP_METHOD(Pimple, register) +{ + zval *provider; + zval **data; + zval *retval = NULL; + zval key; + + HashTable *array = NULL; + HashPosition pos; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|h", &provider, pimple_serviceprovider_ce, &array) == FAILURE) { + return; + } + + RETVAL_ZVAL(getThis(), 1, 0); + + zend_call_method_with_1_params(&provider, Z_OBJCE_P(provider), NULL, "register", &retval, getThis()); + + if (retval) { + zval_ptr_dtor(&retval); + } + + if (!array) { + return; + } + + zend_hash_internal_pointer_reset_ex(array, &pos); + + while(zend_hash_get_current_data_ex(array, (void **)&data, &pos) == SUCCESS) { + zend_hash_get_current_key_zval_ex(array, &key, &pos); + pimple_object_write_dimension(getThis(), &key, *data TSRMLS_CC); + zend_hash_move_forward_ex(array, &pos); + } +} + +PHP_METHOD(Pimple, __construct) +{ + zval *values = NULL, **pData = NULL, offset; + HashPosition pos; + char *str_index = NULL; + zend_uint str_length; + ulong num_index; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a!", &values) == FAILURE) { + return; + } + + PIMPLE_DEPRECATE + + if (!values) { + return; + } + + zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(values), &pos); + while (zend_hash_has_more_elements_ex(Z_ARRVAL_P(values), &pos) == SUCCESS) { + zend_hash_get_current_data_ex(Z_ARRVAL_P(values), (void **)&pData, &pos); + zend_hash_get_current_key_ex(Z_ARRVAL_P(values), &str_index, &str_length, &num_index, 0, &pos); + INIT_ZVAL(offset); + if (zend_hash_get_current_key_type_ex(Z_ARRVAL_P(values), &pos) == HASH_KEY_IS_LONG) { + ZVAL_LONG(&offset, num_index); + } else { + ZVAL_STRINGL(&offset, str_index, (str_length - 1), 0); + } + pimple_object_write_dimension(getThis(), &offset, *pData TSRMLS_CC); + zend_hash_move_forward_ex(Z_ARRVAL_P(values), &pos); + } +} + +/* + * This is PHP code snippet handling extend()s calls : + + $extended = function ($c) use ($callable, $factory) { + return $callable($factory($c), $c); + }; + + */ +PHP_METHOD(PimpleClosure, invoker) +{ + pimple_closure_object *pcobj = NULL; + zval *arg = NULL, *retval = NULL, *newretval = NULL; + zend_fcall_info fci = {0}; + zval **args[2]; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arg) == FAILURE) { + return; + } + + pcobj = zend_object_store_get_object(getThis() TSRMLS_CC); + + fci.function_name = pcobj->factory; + args[0] = &arg; + zend_fcall_info_argp(&fci TSRMLS_CC, 1, args); + fci.retval_ptr_ptr = &retval; + fci.size = sizeof(fci); + + if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE || EG(exception)) { + efree(fci.params); + return; /* Should here return default zval */ + } + + efree(fci.params); + memset(&fci, 0, sizeof(fci)); + fci.size = sizeof(fci); + + fci.function_name = pcobj->callable; + args[0] = &retval; + args[1] = &arg; + zend_fcall_info_argp(&fci TSRMLS_CC, 2, args); + fci.retval_ptr_ptr = &newretval; + + if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE || EG(exception)) { + efree(fci.params); + zval_ptr_dtor(&retval); + return; + } + + efree(fci.params); + zval_ptr_dtor(&retval); + + RETVAL_ZVAL(newretval, 1 ,1); +} + +PHP_MINIT_FUNCTION(pimple) +{ + zend_class_entry tmp_ce_PsrContainerInterface, tmp_ce_PsrContainerExceptionInterface, tmp_ce_PsrNotFoundExceptionInterface; + zend_class_entry tmp_ce_ExpectedInvokableException, tmp_ce_FrozenServiceException, tmp_ce_InvalidServiceIdentifierException, tmp_ce_UnknownIdentifierException; + zend_class_entry tmp_pimple_ce, tmp_pimple_closure_ce, tmp_pimple_serviceprovider_iface_ce; + + /* Psr\Container namespace */ + INIT_NS_CLASS_ENTRY(tmp_ce_PsrContainerInterface, PSR_CONTAINER_NS, "ContainerInterface", pimple_ce_PsrContainerInterface_functions); + INIT_NS_CLASS_ENTRY(tmp_ce_PsrContainerExceptionInterface, PSR_CONTAINER_NS, "ContainerExceptionInterface", pimple_ce_PsrContainerExceptionInterface_functions); + INIT_NS_CLASS_ENTRY(tmp_ce_PsrNotFoundExceptionInterface, PSR_CONTAINER_NS, "NotFoundExceptionInterface", pimple_ce_PsrNotFoundExceptionInterface_functions); + + pimple_ce_PsrContainerInterface = zend_register_internal_interface(&tmp_ce_PsrContainerInterface TSRMLS_CC); + pimple_ce_PsrContainerExceptionInterface = zend_register_internal_interface(&tmp_ce_PsrContainerExceptionInterface TSRMLS_CC); + pimple_ce_PsrNotFoundExceptionInterface = zend_register_internal_interface(&tmp_ce_PsrNotFoundExceptionInterface TSRMLS_CC); + + zend_class_implements(pimple_ce_PsrNotFoundExceptionInterface TSRMLS_CC, 1, pimple_ce_PsrContainerExceptionInterface); + + /* Pimple\Exception namespace */ + INIT_NS_CLASS_ENTRY(tmp_ce_ExpectedInvokableException, PIMPLE_EXCEPTION_NS, "ExpectedInvokableException", NULL); + INIT_NS_CLASS_ENTRY(tmp_ce_FrozenServiceException, PIMPLE_EXCEPTION_NS, "FrozenServiceException", pimple_ce_FrozenServiceException_functions); + INIT_NS_CLASS_ENTRY(tmp_ce_InvalidServiceIdentifierException, PIMPLE_EXCEPTION_NS, "InvalidServiceIdentifierException", pimple_ce_InvalidServiceIdentifierException_functions); + INIT_NS_CLASS_ENTRY(tmp_ce_UnknownIdentifierException, PIMPLE_EXCEPTION_NS, "UnknownIdentifierException", pimple_ce_UnknownIdentifierException_functions); + + pimple_ce_ExpectedInvokableException = zend_register_internal_class_ex(&tmp_ce_ExpectedInvokableException, spl_ce_InvalidArgumentException, NULL TSRMLS_CC); + pimple_ce_FrozenServiceException = zend_register_internal_class_ex(&tmp_ce_FrozenServiceException, spl_ce_RuntimeException, NULL TSRMLS_CC); + pimple_ce_InvalidServiceIdentifierException = zend_register_internal_class_ex(&tmp_ce_InvalidServiceIdentifierException, spl_ce_InvalidArgumentException, NULL TSRMLS_CC); + pimple_ce_UnknownIdentifierException = zend_register_internal_class_ex(&tmp_ce_UnknownIdentifierException, spl_ce_InvalidArgumentException, NULL TSRMLS_CC); + + zend_class_implements(pimple_ce_ExpectedInvokableException TSRMLS_CC, 1, pimple_ce_PsrContainerExceptionInterface); + zend_class_implements(pimple_ce_FrozenServiceException TSRMLS_CC, 1, pimple_ce_PsrContainerExceptionInterface); + zend_class_implements(pimple_ce_InvalidServiceIdentifierException TSRMLS_CC, 1, pimple_ce_PsrContainerExceptionInterface); + zend_class_implements(pimple_ce_UnknownIdentifierException TSRMLS_CC, 1, pimple_ce_PsrNotFoundExceptionInterface); + + /* Pimple namespace */ + INIT_NS_CLASS_ENTRY(tmp_pimple_ce, PIMPLE_NS, "Container", pimple_ce_functions); + INIT_NS_CLASS_ENTRY(tmp_pimple_closure_ce, PIMPLE_NS, "ContainerClosure", NULL); + INIT_NS_CLASS_ENTRY(tmp_pimple_serviceprovider_iface_ce, PIMPLE_NS, "ServiceProviderInterface", pimple_serviceprovider_iface_ce_functions); + + tmp_pimple_ce.create_object = pimple_object_create; + tmp_pimple_closure_ce.create_object = pimple_closure_object_create; + + pimple_ce = zend_register_internal_class(&tmp_pimple_ce TSRMLS_CC); + zend_class_implements(pimple_ce TSRMLS_CC, 1, zend_ce_arrayaccess); + + pimple_closure_ce = zend_register_internal_class(&tmp_pimple_closure_ce TSRMLS_CC); + pimple_closure_ce->ce_flags |= ZEND_ACC_FINAL_CLASS; + + pimple_serviceprovider_ce = zend_register_internal_interface(&tmp_pimple_serviceprovider_iface_ce TSRMLS_CC); + + memcpy(&pimple_closure_object_handlers, zend_get_std_object_handlers(), sizeof(*zend_get_std_object_handlers())); + pimple_object_handlers = std_object_handlers; + pimple_closure_object_handlers.get_closure = pimple_closure_get_closure; + + pimple_closure_invoker_function.function_name = "Pimple closure internal invoker"; + pimple_closure_invoker_function.fn_flags |= ZEND_ACC_CLOSURE; + pimple_closure_invoker_function.handler = ZEND_MN(PimpleClosure_invoker); + pimple_closure_invoker_function.num_args = 1; + pimple_closure_invoker_function.required_num_args = 1; + pimple_closure_invoker_function.scope = pimple_closure_ce; + pimple_closure_invoker_function.type = ZEND_INTERNAL_FUNCTION; + pimple_closure_invoker_function.module = &pimple_module_entry; + + return SUCCESS; +} + +PHP_MINFO_FUNCTION(pimple) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "SensioLabs Pimple C support", "enabled"); + php_info_print_table_row(2, "Pimple supported version", PIMPLE_VERSION); + php_info_print_table_end(); + + php_info_print_box_start(0); + php_write((void *)ZEND_STRL("SensioLabs Pimple C support developed by Julien Pauli") TSRMLS_CC); + if (!sapi_module.phpinfo_as_text) { + php_write((void *)ZEND_STRL(sensiolabs_logo) TSRMLS_CC); + } + php_info_print_box_end(); +} + +zend_module_entry pimple_module_entry = { + STANDARD_MODULE_HEADER, + "pimple", + NULL, + PHP_MINIT(pimple), + NULL, + NULL, + NULL, + PHP_MINFO(pimple), + PIMPLE_VERSION, + STANDARD_MODULE_PROPERTIES +}; + +#ifdef COMPILE_DL_PIMPLE +ZEND_GET_MODULE(pimple) +#endif diff --git a/vendor/pimple/pimple/ext/pimple/pimple_compat.h b/vendor/pimple/pimple/ext/pimple/pimple_compat.h new file mode 100644 index 00000000..d234e174 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/pimple_compat.h @@ -0,0 +1,81 @@ + +/* + * This file is part of Pimple. + * + * Copyright (c) 2014 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. + */ + +#ifndef PIMPLE_COMPAT_H_ +#define PIMPLE_COMPAT_H_ + +#include "Zend/zend_extensions.h" /* for ZEND_EXTENSION_API_NO */ + +#define PHP_5_0_X_API_NO 220040412 +#define PHP_5_1_X_API_NO 220051025 +#define PHP_5_2_X_API_NO 220060519 +#define PHP_5_3_X_API_NO 220090626 +#define PHP_5_4_X_API_NO 220100525 +#define PHP_5_5_X_API_NO 220121212 +#define PHP_5_6_X_API_NO 220131226 + +#define IS_PHP_56 ZEND_EXTENSION_API_NO == PHP_5_6_X_API_NO +#define IS_AT_LEAST_PHP_56 ZEND_EXTENSION_API_NO >= PHP_5_6_X_API_NO + +#define IS_PHP_55 ZEND_EXTENSION_API_NO == PHP_5_5_X_API_NO +#define IS_AT_LEAST_PHP_55 ZEND_EXTENSION_API_NO >= PHP_5_5_X_API_NO + +#define IS_PHP_54 ZEND_EXTENSION_API_NO == PHP_5_4_X_API_NO +#define IS_AT_LEAST_PHP_54 ZEND_EXTENSION_API_NO >= PHP_5_4_X_API_NO + +#define IS_PHP_53 ZEND_EXTENSION_API_NO == PHP_5_3_X_API_NO +#define IS_AT_LEAST_PHP_53 ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO + +#if IS_PHP_53 +#define object_properties_init(obj, ce) do { \ + zend_hash_copy(obj->properties, &ce->default_properties, zval_copy_property_ctor(ce), NULL, sizeof(zval *)); \ + } while (0); +#endif + +#define ZEND_OBJ_INIT(obj, ce) do { \ + zend_object_std_init(obj, ce TSRMLS_CC); \ + object_properties_init((obj), (ce)); \ + } while(0); + +#if IS_PHP_53 || IS_PHP_54 +static void zend_hash_get_current_key_zval_ex(const HashTable *ht, zval *key, HashPosition *pos) { + Bucket *p; + + p = pos ? (*pos) : ht->pInternalPointer; + + if (!p) { + Z_TYPE_P(key) = IS_NULL; + } else if (p->nKeyLength) { + Z_TYPE_P(key) = IS_STRING; + Z_STRVAL_P(key) = estrndup(p->arKey, p->nKeyLength - 1); + Z_STRLEN_P(key) = p->nKeyLength - 1; + } else { + Z_TYPE_P(key) = IS_LONG; + Z_LVAL_P(key) = p->h; + } +} +#endif + +#endif /* PIMPLE_COMPAT_H_ */ diff --git a/vendor/pimple/pimple/ext/pimple/tests/001.phpt b/vendor/pimple/pimple/ext/pimple/tests/001.phpt new file mode 100644 index 00000000..0809ea23 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/001.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test for read_dim/write_dim handlers +--SKIPIF-- + +--FILE-- + + +--EXPECTF-- +foo +42 +foo2 +foo99 +baz +strstr \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/002.phpt b/vendor/pimple/pimple/ext/pimple/tests/002.phpt new file mode 100644 index 00000000..7b56d2c1 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/002.phpt @@ -0,0 +1,15 @@ +--TEST-- +Test for constructor +--SKIPIF-- + +--FILE-- +'foo')); +var_dump($p[42]); +?> +--EXPECT-- +NULL +string(3) "foo" diff --git a/vendor/pimple/pimple/ext/pimple/tests/003.phpt b/vendor/pimple/pimple/ext/pimple/tests/003.phpt new file mode 100644 index 00000000..a22cfa35 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/003.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test empty dimensions +--SKIPIF-- + +--FILE-- + +--EXPECT-- +int(42) +string(3) "bar" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/004.phpt b/vendor/pimple/pimple/ext/pimple/tests/004.phpt new file mode 100644 index 00000000..1e1d2513 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/004.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test has/unset dim handlers +--SKIPIF-- + +--FILE-- + +--EXPECT-- +int(42) +NULL +bool(true) +bool(false) +bool(true) +bool(true) \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/005.phpt b/vendor/pimple/pimple/ext/pimple/tests/005.phpt new file mode 100644 index 00000000..0479ee05 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/005.phpt @@ -0,0 +1,27 @@ +--TEST-- +Test simple class inheritance +--SKIPIF-- + +--FILE-- +someAttr; +?> +--EXPECT-- +string(3) "hit" +foo +fooAttr \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/006.phpt b/vendor/pimple/pimple/ext/pimple/tests/006.phpt new file mode 100644 index 00000000..cfe8a119 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/006.phpt @@ -0,0 +1,51 @@ +--TEST-- +Test complex class inheritance +--SKIPIF-- + +--FILE-- + 'bar', 88 => 'baz'); + +$p = new TestPimple($defaultValues); +$p[42] = 'foo'; +var_dump($p[42]); +var_dump($p[0]); +?> +--EXPECT-- +string(13) "hit offsetset" +string(27) "hit offsetget in TestPimple" +string(25) "hit offsetget in MyPimple" +string(3) "foo" +string(27) "hit offsetget in TestPimple" +string(25) "hit offsetget in MyPimple" +string(3) "baz" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/007.phpt b/vendor/pimple/pimple/ext/pimple/tests/007.phpt new file mode 100644 index 00000000..5aac6838 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/007.phpt @@ -0,0 +1,22 @@ +--TEST-- +Test for read_dim/write_dim handlers +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +foo +42 \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/008.phpt b/vendor/pimple/pimple/ext/pimple/tests/008.phpt new file mode 100644 index 00000000..db7eeec4 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/008.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test frozen services +--SKIPIF-- + +--FILE-- + +--EXPECTF-- diff --git a/vendor/pimple/pimple/ext/pimple/tests/009.phpt b/vendor/pimple/pimple/ext/pimple/tests/009.phpt new file mode 100644 index 00000000..bb05ea29 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/009.phpt @@ -0,0 +1,13 @@ +--TEST-- +Test service is called as callback, and only once +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/010.phpt b/vendor/pimple/pimple/ext/pimple/tests/010.phpt new file mode 100644 index 00000000..badce014 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/010.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test service is called as callback for every callback type +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +callme +called +Foo::bar +array(2) { + [0]=> + string(3) "Foo" + [1]=> + string(3) "bar" +} \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/011.phpt b/vendor/pimple/pimple/ext/pimple/tests/011.phpt new file mode 100644 index 00000000..6682ab8e --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/011.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test service callback throwing an exception +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +all right! \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/012.phpt b/vendor/pimple/pimple/ext/pimple/tests/012.phpt new file mode 100644 index 00000000..4c6ac486 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/012.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test service factory +--SKIPIF-- + +--FILE-- +factory($f = function() { var_dump('called-1'); return 'ret-1';}); + +$p[] = $f; + +$p[] = function () { var_dump('called-2'); return 'ret-2'; }; + +var_dump($p[0]); +var_dump($p[0]); +var_dump($p[1]); +var_dump($p[1]); +?> +--EXPECTF-- +string(8) "called-1" +string(5) "ret-1" +string(8) "called-1" +string(5) "ret-1" +string(8) "called-2" +string(5) "ret-2" +string(5) "ret-2" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/013.phpt b/vendor/pimple/pimple/ext/pimple/tests/013.phpt new file mode 100644 index 00000000..f419958c --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/013.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test keys() +--SKIPIF-- + +--FILE-- +keys()); + +$p['foo'] = 'bar'; +$p[] = 'foo'; + +var_dump($p->keys()); + +unset($p['foo']); + +var_dump($p->keys()); +?> +--EXPECTF-- +array(0) { +} +array(2) { + [0]=> + string(3) "foo" + [1]=> + int(0) +} +array(1) { + [0]=> + int(0) +} \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/014.phpt b/vendor/pimple/pimple/ext/pimple/tests/014.phpt new file mode 100644 index 00000000..ac937213 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/014.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test raw() +--SKIPIF-- + +--FILE-- +raw('foo')); +var_dump($p[42]); + +unset($p['foo']); + +try { + $p->raw('foo'); + echo "expected exception"; +} catch (InvalidArgumentException $e) { } +--EXPECTF-- +string(8) "called-2" +string(5) "ret-2" +object(Closure)#%i (0) { +} +string(8) "called-2" +string(5) "ret-2" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/015.phpt b/vendor/pimple/pimple/ext/pimple/tests/015.phpt new file mode 100644 index 00000000..314f008a --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/015.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test protect() +--SKIPIF-- + +--FILE-- +protect($f); + +var_dump($p['foo']); +--EXPECTF-- +object(Closure)#%i (0) { +} \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/016.phpt b/vendor/pimple/pimple/ext/pimple/tests/016.phpt new file mode 100644 index 00000000..e55edb0a --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/016.phpt @@ -0,0 +1,24 @@ +--TEST-- +Test extend() +--SKIPIF-- + +--FILE-- +extend(12, function ($w) { var_dump($w); return 'bar'; }); /* $callable in code above */ + +var_dump($c('param')); +--EXPECTF-- +string(5) "param" +string(3) "foo" +string(3) "bar" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/017.phpt b/vendor/pimple/pimple/ext/pimple/tests/017.phpt new file mode 100644 index 00000000..bac23ce0 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/017.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test extend() with exception in service extension +--SKIPIF-- + +--FILE-- +extend(12, function ($w) { throw new BadMethodCallException; }); + +try { + $p[12]; + echo "Exception expected"; +} catch (BadMethodCallException $e) { } +--EXPECTF-- diff --git a/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt b/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt new file mode 100644 index 00000000..8f881d6e --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test extend() with exception in service factory +--SKIPIF-- + +--FILE-- +extend(12, function ($w) { return 'foobar'; }); + +try { + $p[12]; + echo "Exception expected"; +} catch (BadMethodCallException $e) { } +--EXPECTF-- diff --git a/vendor/pimple/pimple/ext/pimple/tests/018.phpt b/vendor/pimple/pimple/ext/pimple/tests/018.phpt new file mode 100644 index 00000000..27c12a14 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/018.phpt @@ -0,0 +1,23 @@ +--TEST-- +Test register() +--SKIPIF-- + +--FILE-- +register(new Foo, array(42 => 'bar')); + +var_dump($p[42]); +--EXPECTF-- +object(Pimple\Container)#1 (0) { +} +string(3) "bar" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/019.phpt b/vendor/pimple/pimple/ext/pimple/tests/019.phpt new file mode 100644 index 00000000..28a9aeca --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/019.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test register() returns static and is a fluent interface +--SKIPIF-- + +--FILE-- +register(new Foo)); +--EXPECTF-- +bool(true) diff --git a/vendor/pimple/pimple/ext/pimple/tests/bench.phpb b/vendor/pimple/pimple/ext/pimple/tests/bench.phpb new file mode 100644 index 00000000..8f983e65 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/bench.phpb @@ -0,0 +1,51 @@ +factory($factory); + +$p['factory'] = $factory; + +echo $p['factory']; +echo $p['factory']; +echo $p['factory']; + +} + +echo microtime(true) - $time; diff --git a/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb b/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb new file mode 100644 index 00000000..aec541f0 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb @@ -0,0 +1,25 @@ + diff --git a/vendor/pimple/pimple/phpunit.xml.dist b/vendor/pimple/pimple/phpunit.xml.dist new file mode 100644 index 00000000..5c8d487f --- /dev/null +++ b/vendor/pimple/pimple/phpunit.xml.dist @@ -0,0 +1,14 @@ + + + + + + ./src/Pimple/Tests + + + diff --git a/vendor/pimple/pimple/src/Pimple/Container.php b/vendor/pimple/pimple/src/Pimple/Container.php new file mode 100644 index 00000000..e761964b --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Container.php @@ -0,0 +1,298 @@ +factories = new \SplObjectStorage(); + $this->protected = new \SplObjectStorage(); + + foreach ($values as $key => $value) { + $this->offsetSet($key, $value); + } + } + + /** + * Sets a parameter or an object. + * + * Objects must be defined as Closures. + * + * Allowing any PHP callable leads to difficult to debug problems + * as function names (strings) are callable (creating a function with + * the same name as an existing parameter would break your container). + * + * @param string $id The unique identifier for the parameter or object + * @param mixed $value The value of the parameter or a closure to define an object + * + * @throws FrozenServiceException Prevent override of a frozen service + */ + public function offsetSet($id, $value) + { + if (isset($this->frozen[$id])) { + throw new FrozenServiceException($id); + } + + $this->values[$id] = $value; + $this->keys[$id] = true; + } + + /** + * Gets a parameter or an object. + * + * @param string $id The unique identifier for the parameter or object + * + * @return mixed The value of the parameter or an object + * + * @throws UnknownIdentifierException If the identifier is not defined + */ + public function offsetGet($id) + { + if (!isset($this->keys[$id])) { + throw new UnknownIdentifierException($id); + } + + if ( + isset($this->raw[$id]) + || !is_object($this->values[$id]) + || isset($this->protected[$this->values[$id]]) + || !method_exists($this->values[$id], '__invoke') + ) { + return $this->values[$id]; + } + + if (isset($this->factories[$this->values[$id]])) { + return $this->values[$id]($this); + } + + $raw = $this->values[$id]; + $val = $this->values[$id] = $raw($this); + $this->raw[$id] = $raw; + + $this->frozen[$id] = true; + + return $val; + } + + /** + * Checks if a parameter or an object is set. + * + * @param string $id The unique identifier for the parameter or object + * + * @return bool + */ + public function offsetExists($id) + { + return isset($this->keys[$id]); + } + + /** + * Unsets a parameter or an object. + * + * @param string $id The unique identifier for the parameter or object + */ + public function offsetUnset($id) + { + if (isset($this->keys[$id])) { + if (is_object($this->values[$id])) { + unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]); + } + + unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]); + } + } + + /** + * Marks a callable as being a factory service. + * + * @param callable $callable A service definition to be used as a factory + * + * @return callable The passed callable + * + * @throws ExpectedInvokableException Service definition has to be a closure or an invokable object + */ + public function factory($callable) + { + if (!method_exists($callable, '__invoke')) { + throw new ExpectedInvokableException('Service definition is not a Closure or invokable object.'); + } + + $this->factories->attach($callable); + + return $callable; + } + + /** + * Protects a callable from being interpreted as a service. + * + * This is useful when you want to store a callable as a parameter. + * + * @param callable $callable A callable to protect from being evaluated + * + * @return callable The passed callable + * + * @throws ExpectedInvokableException Service definition has to be a closure or an invokable object + */ + public function protect($callable) + { + if (!method_exists($callable, '__invoke')) { + throw new ExpectedInvokableException('Callable is not a Closure or invokable object.'); + } + + $this->protected->attach($callable); + + return $callable; + } + + /** + * Gets a parameter or the closure defining an object. + * + * @param string $id The unique identifier for the parameter or object + * + * @return mixed The value of the parameter or the closure defining an object + * + * @throws UnknownIdentifierException If the identifier is not defined + */ + public function raw($id) + { + if (!isset($this->keys[$id])) { + throw new UnknownIdentifierException($id); + } + + if (isset($this->raw[$id])) { + return $this->raw[$id]; + } + + return $this->values[$id]; + } + + /** + * Extends an object definition. + * + * Useful when you want to extend an existing object definition, + * without necessarily loading that object. + * + * @param string $id The unique identifier for the object + * @param callable $callable A service definition to extend the original + * + * @return callable The wrapped callable + * + * @throws UnknownIdentifierException If the identifier is not defined + * @throws FrozenServiceException If the service is frozen + * @throws InvalidServiceIdentifierException If the identifier belongs to a parameter + * @throws ExpectedInvokableException If the extension callable is not a closure or an invokable object + */ + public function extend($id, $callable) + { + if (!isset($this->keys[$id])) { + throw new UnknownIdentifierException($id); + } + + if (isset($this->frozen[$id])) { + throw new FrozenServiceException($id); + } + + if (!is_object($this->values[$id]) || !method_exists($this->values[$id], '__invoke')) { + throw new InvalidServiceIdentifierException($id); + } + + if (isset($this->protected[$this->values[$id]])) { + @trigger_error(sprintf('How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure "%s" should be protected?', $id), E_USER_DEPRECATED); + } + + if (!is_object($callable) || !method_exists($callable, '__invoke')) { + throw new ExpectedInvokableException('Extension service definition is not a Closure or invokable object.'); + } + + $factory = $this->values[$id]; + + $extended = function ($c) use ($callable, $factory) { + return $callable($factory($c), $c); + }; + + if (isset($this->factories[$factory])) { + $this->factories->detach($factory); + $this->factories->attach($extended); + } + + return $this[$id] = $extended; + } + + /** + * Returns all defined value names. + * + * @return array An array of value names + */ + public function keys() + { + return array_keys($this->values); + } + + /** + * Registers a service provider. + * + * @param ServiceProviderInterface $provider A ServiceProviderInterface instance + * @param array $values An array of values that customizes the provider + * + * @return static + */ + public function register(ServiceProviderInterface $provider, array $values = array()) + { + $provider->register($this); + + foreach ($values as $key => $value) { + $this[$key] = $value; + } + + return $this; + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php b/vendor/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php new file mode 100644 index 00000000..7228421b --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php @@ -0,0 +1,38 @@ + + */ +class ExpectedInvokableException extends \InvalidArgumentException implements ContainerExceptionInterface +{ +} diff --git a/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php b/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php new file mode 100644 index 00000000..64b02659 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php @@ -0,0 +1,45 @@ + + */ +class FrozenServiceException extends \RuntimeException implements ContainerExceptionInterface +{ + /** + * @param string $id Identifier of the frozen service + */ + public function __construct($id) + { + parent::__construct(sprintf('Cannot override frozen service "%s".', $id)); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php b/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php new file mode 100644 index 00000000..9df9c663 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php @@ -0,0 +1,45 @@ + + */ +class InvalidServiceIdentifierException extends \InvalidArgumentException implements NotFoundExceptionInterface +{ + /** + * @param string $id The invalid identifier + */ + public function __construct($id) + { + parent::__construct(sprintf('Identifier "%s" does not contain an object definition.', $id)); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php b/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php new file mode 100644 index 00000000..28413189 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php @@ -0,0 +1,45 @@ + + */ +class UnknownIdentifierException extends \InvalidArgumentException implements NotFoundExceptionInterface +{ + /** + * @param string $id The unknown identifier + */ + public function __construct($id) + { + parent::__construct(sprintf('Identifier "%s" is not defined.', $id)); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Psr11/Container.php b/vendor/pimple/pimple/src/Pimple/Psr11/Container.php new file mode 100644 index 00000000..cadbfffa --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Psr11/Container.php @@ -0,0 +1,55 @@ + + */ +final class Container implements ContainerInterface +{ + private $pimple; + + public function __construct(PimpleContainer $pimple) + { + $this->pimple = $pimple; + } + + public function get($id) + { + return $this->pimple[$id]; + } + + public function has($id) + { + return isset($this->pimple[$id]); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php b/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php new file mode 100644 index 00000000..61e49848 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php @@ -0,0 +1,75 @@ + + */ +class ServiceLocator implements ContainerInterface +{ + private $container; + private $aliases = array(); + + /** + * @param PimpleContainer $container The Container instance used to locate services + * @param array $ids Array of service ids that can be located. String keys can be used to define aliases + */ + public function __construct(PimpleContainer $container, array $ids) + { + $this->container = $container; + + foreach ($ids as $key => $id) { + $this->aliases[is_int($key) ? $id : $key] = $id; + } + } + + /** + * {@inheritdoc} + */ + public function get($id) + { + if (!isset($this->aliases[$id])) { + throw new UnknownIdentifierException($id); + } + + return $this->container[$this->aliases[$id]]; + } + + /** + * {@inheritdoc} + */ + public function has($id) + { + return isset($this->aliases[$id]) && isset($this->container[$this->aliases[$id]]); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/ServiceIterator.php b/vendor/pimple/pimple/src/Pimple/ServiceIterator.php new file mode 100644 index 00000000..744271dd --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/ServiceIterator.php @@ -0,0 +1,69 @@ + + */ +final class ServiceIterator implements \Iterator +{ + private $container; + private $ids; + + public function __construct(Container $container, array $ids) + { + $this->container = $container; + $this->ids = $ids; + } + + public function rewind() + { + reset($this->ids); + } + + public function current() + { + return $this->container[current($this->ids)]; + } + + public function key() + { + return current($this->ids); + } + + public function next() + { + next($this->ids); + } + + public function valid() + { + return null !== key($this->ids); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php b/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php new file mode 100644 index 00000000..c004594b --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php @@ -0,0 +1,46 @@ +value = $value; + + return $service; + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php new file mode 100644 index 00000000..33cd4e54 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php @@ -0,0 +1,34 @@ +factory(function () { + return new Service(); + }); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php new file mode 100644 index 00000000..d71b184d --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php @@ -0,0 +1,35 @@ + + */ +class Service +{ + public $value; +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php b/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php new file mode 100644 index 00000000..8e5c4c73 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php @@ -0,0 +1,76 @@ + + */ +class PimpleServiceProviderInterfaceTest extends \PHPUnit_Framework_TestCase +{ + public function testProvider() + { + $pimple = new Container(); + + $pimpleServiceProvider = new Fixtures\PimpleServiceProvider(); + $pimpleServiceProvider->register($pimple); + + $this->assertEquals('value', $pimple['param']); + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); + + $serviceOne = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } + + public function testProviderWithRegisterMethod() + { + $pimple = new Container(); + + $pimple->register(new Fixtures\PimpleServiceProvider(), array( + 'anotherParameter' => 'anotherValue', + )); + + $this->assertEquals('value', $pimple['param']); + $this->assertEquals('anotherValue', $pimple['anotherParameter']); + + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); + + $serviceOne = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php b/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php new file mode 100644 index 00000000..acb66e00 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php @@ -0,0 +1,589 @@ + + */ +class PimpleTest extends \PHPUnit_Framework_TestCase +{ + public function testWithString() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + + $this->assertEquals('value', $pimple['param']); + } + + public function testWithClosure() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); + } + + public function testServicesShouldBeDifferent() + { + $pimple = new Container(); + $pimple['service'] = $pimple->factory(function () { + return new Fixtures\Service(); + }); + + $serviceOne = $pimple['service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } + + public function testShouldPassContainerAsParameter() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $pimple['container'] = function ($container) { + return $container; + }; + + $this->assertNotSame($pimple, $pimple['service']); + $this->assertSame($pimple, $pimple['container']); + } + + public function testIsset() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + $pimple['null'] = null; + + $this->assertTrue(isset($pimple['param'])); + $this->assertTrue(isset($pimple['service'])); + $this->assertTrue(isset($pimple['null'])); + $this->assertFalse(isset($pimple['non_existent'])); + } + + public function testConstructorInjection() + { + $params = array('param' => 'value'); + $pimple = new Container($params); + + $this->assertSame($params['param'], $pimple['param']); + } + + /** + * @expectedException \Pimple\Exception\UnknownIdentifierException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testOffsetGetValidatesKeyIsPresent() + { + $pimple = new Container(); + echo $pimple['foo']; + } + + /** + * @group legacy + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testLegacyOffsetGetValidatesKeyIsPresent() + { + $pimple = new Container(); + echo $pimple['foo']; + } + + public function testOffsetGetHonorsNullValues() + { + $pimple = new Container(); + $pimple['foo'] = null; + $this->assertNull($pimple['foo']); + } + + public function testUnset() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + unset($pimple['param'], $pimple['service']); + $this->assertFalse(isset($pimple['param'])); + $this->assertFalse(isset($pimple['service'])); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testShare($service) + { + $pimple = new Container(); + $pimple['shared_service'] = $service; + + $serviceOne = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertSame($serviceOne, $serviceTwo); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testProtect($service) + { + $pimple = new Container(); + $pimple['protected'] = $pimple->protect($service); + + $this->assertSame($service, $pimple['protected']); + } + + public function testGlobalFunctionNameAsParameterValue() + { + $pimple = new Container(); + $pimple['global_function'] = 'strlen'; + $this->assertSame('strlen', $pimple['global_function']); + } + + public function testRaw() + { + $pimple = new Container(); + $pimple['service'] = $definition = $pimple->factory(function () { return 'foo'; }); + $this->assertSame($definition, $pimple->raw('service')); + } + + public function testRawHonorsNullValues() + { + $pimple = new Container(); + $pimple['foo'] = null; + $this->assertNull($pimple->raw('foo')); + } + + public function testFluentRegister() + { + $pimple = new Container(); + $this->assertSame($pimple, $pimple->register($this->getMockBuilder('Pimple\ServiceProviderInterface')->getMock())); + } + + /** + * @expectedException \Pimple\Exception\UnknownIdentifierException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testRawValidatesKeyIsPresent() + { + $pimple = new Container(); + $pimple->raw('foo'); + } + + /** + * @group legacy + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testLegacyRawValidatesKeyIsPresent() + { + $pimple = new Container(); + $pimple->raw('foo'); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testExtend($service) + { + $pimple = new Container(); + $pimple['shared_service'] = function () { + return new Fixtures\Service(); + }; + $pimple['factory_service'] = $pimple->factory(function () { + return new Fixtures\Service(); + }); + + $pimple->extend('shared_service', $service); + $serviceOne = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + $serviceTwo = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + $this->assertSame($serviceOne, $serviceTwo); + $this->assertSame($serviceOne->value, $serviceTwo->value); + + $pimple->extend('factory_service', $service); + $serviceOne = $pimple['factory_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + $serviceTwo = $pimple['factory_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + $this->assertNotSame($serviceOne, $serviceTwo); + $this->assertNotSame($serviceOne->value, $serviceTwo->value); + } + + public function testExtendDoesNotLeakWithFactories() + { + if (extension_loaded('pimple')) { + $this->markTestSkipped('Pimple extension does not support this test'); + } + $pimple = new Container(); + + $pimple['foo'] = $pimple->factory(function () { return; }); + $pimple['foo'] = $pimple->extend('foo', function ($foo, $pimple) { return; }); + unset($pimple['foo']); + + $p = new \ReflectionProperty($pimple, 'values'); + $p->setAccessible(true); + $this->assertEmpty($p->getValue($pimple)); + + $p = new \ReflectionProperty($pimple, 'factories'); + $p->setAccessible(true); + $this->assertCount(0, $p->getValue($pimple)); + } + + /** + * @expectedException \Pimple\Exception\UnknownIdentifierException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testExtendValidatesKeyIsPresent() + { + $pimple = new Container(); + $pimple->extend('foo', function () {}); + } + + /** + * @group legacy + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testLegacyExtendValidatesKeyIsPresent() + { + $pimple = new Container(); + $pimple->extend('foo', function () {}); + } + + public function testKeys() + { + $pimple = new Container(); + $pimple['foo'] = 123; + $pimple['bar'] = 123; + + $this->assertEquals(array('foo', 'bar'), $pimple->keys()); + } + + /** @test */ + public function settingAnInvokableObjectShouldTreatItAsFactory() + { + $pimple = new Container(); + $pimple['invokable'] = new Fixtures\Invokable(); + + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['invokable']); + } + + /** @test */ + public function settingNonInvokableObjectShouldTreatItAsParameter() + { + $pimple = new Container(); + $pimple['non_invokable'] = new Fixtures\NonInvokable(); + + $this->assertInstanceOf('Pimple\Tests\Fixtures\NonInvokable', $pimple['non_invokable']); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \Pimple\Exception\ExpectedInvokableException + * @expectedExceptionMessage Service definition is not a Closure or invokable object. + */ + public function testFactoryFailsForInvalidServiceDefinitions($service) + { + $pimple = new Container(); + $pimple->factory($service); + } + + /** + * @group legacy + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Service definition is not a Closure or invokable object. + */ + public function testLegacyFactoryFailsForInvalidServiceDefinitions($service) + { + $pimple = new Container(); + $pimple->factory($service); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \Pimple\Exception\ExpectedInvokableException + * @expectedExceptionMessage Callable is not a Closure or invokable object. + */ + public function testProtectFailsForInvalidServiceDefinitions($service) + { + $pimple = new Container(); + $pimple->protect($service); + } + + /** + * @group legacy + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Callable is not a Closure or invokable object. + */ + public function testLegacyProtectFailsForInvalidServiceDefinitions($service) + { + $pimple = new Container(); + $pimple->protect($service); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \Pimple\Exception\InvalidServiceIdentifierException + * @expectedExceptionMessage Identifier "foo" does not contain an object definition. + */ + public function testExtendFailsForKeysNotContainingServiceDefinitions($service) + { + $pimple = new Container(); + $pimple['foo'] = $service; + $pimple->extend('foo', function () {}); + } + + /** + * @group legacy + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" does not contain an object definition. + */ + public function testLegacyExtendFailsForKeysNotContainingServiceDefinitions($service) + { + $pimple = new Container(); + $pimple['foo'] = $service; + $pimple->extend('foo', function () {}); + } + + /** + * @group legacy + * @expectedDeprecation How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure "foo" should be protected? + */ + public function testExtendingProtectedClosureDeprecation() + { + $pimple = new Container(); + $pimple['foo'] = $pimple->protect(function () { + return 'bar'; + }); + + $pimple->extend('foo', function ($value) { + return $value.'-baz'; + }); + + $this->assertSame('bar-baz', $pimple['foo']); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \Pimple\Exception\ExpectedInvokableException + * @expectedExceptionMessage Extension service definition is not a Closure or invokable object. + */ + public function testExtendFailsForInvalidServiceDefinitions($service) + { + $pimple = new Container(); + $pimple['foo'] = function () {}; + $pimple->extend('foo', $service); + } + + /** + * @group legacy + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Extension service definition is not a Closure or invokable object. + */ + public function testLegacyExtendFailsForInvalidServiceDefinitions($service) + { + $pimple = new Container(); + $pimple['foo'] = function () {}; + $pimple->extend('foo', $service); + } + + /** + * @expectedException \Pimple\Exception\FrozenServiceException + * @expectedExceptionMessage Cannot override frozen service "foo". + */ + public function testExtendFailsIfFrozenServiceIsNonInvokable() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return new Fixtures\NonInvokable(); + }; + $foo = $pimple['foo']; + + $pimple->extend('foo', function () {}); + } + + /** + * @expectedException \Pimple\Exception\FrozenServiceException + * @expectedExceptionMessage Cannot override frozen service "foo". + */ + public function testExtendFailsIfFrozenServiceIsInvokable() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return new Fixtures\Invokable(); + }; + $foo = $pimple['foo']; + + $pimple->extend('foo', function () {}); + } + + /** + * Provider for invalid service definitions. + */ + public function badServiceDefinitionProvider() + { + return array( + array(123), + array(new Fixtures\NonInvokable()), + ); + } + + /** + * Provider for service definitions. + */ + public function serviceDefinitionProvider() + { + return array( + array(function ($value) { + $service = new Fixtures\Service(); + $service->value = $value; + + return $service; + }), + array(new Fixtures\Invokable()), + ); + } + + public function testDefiningNewServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + $pimple['bar'] = function () { + return 'bar'; + }; + $this->assertSame('bar', $pimple['bar']); + } + + /** + * @expectedException \Pimple\Exception\FrozenServiceException + * @expectedExceptionMessage Cannot override frozen service "foo". + */ + public function testOverridingServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + $pimple['foo'] = function () { + return 'bar'; + }; + } + + /** + * @group legacy + * @expectedException \RuntimeException + * @expectedExceptionMessage Cannot override frozen service "foo". + */ + public function testLegacyOverridingServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + $pimple['foo'] = function () { + return 'bar'; + }; + } + + public function testRemovingServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + unset($pimple['foo']); + $pimple['foo'] = function () { + return 'bar'; + }; + $this->assertSame('bar', $pimple['foo']); + } + + public function testExtendingService() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) { + return "$foo.bar"; + }); + $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) { + return "$foo.baz"; + }); + $this->assertSame('foo.bar.baz', $pimple['foo']); + } + + public function testExtendingServiceAfterOtherServiceFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $pimple['bar'] = function () { + return 'bar'; + }; + $foo = $pimple['foo']; + + $pimple['bar'] = $pimple->extend('bar', function ($bar, $app) { + return "$bar.baz"; + }); + $this->assertSame('bar.baz', $pimple['bar']); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php new file mode 100644 index 00000000..7ca2d7ff --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php @@ -0,0 +1,77 @@ +assertSame($pimple['service'], $psr->get('service')); + } + + /** + * @expectedException \Psr\Container\NotFoundExceptionInterface + * @expectedExceptionMessage Identifier "service" is not defined. + */ + public function testGetThrowsExceptionIfServiceIsNotFound() + { + $pimple = new Container(); + $psr = new PsrContainer($pimple); + + $psr->get('service'); + } + + public function testHasReturnsTrueIfServiceExists() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Service(); + }; + $psr = new PsrContainer($pimple); + + $this->assertTrue($psr->has('service')); + } + + public function testHasReturnsFalseIfServiceDoesNotExist() + { + $pimple = new Container(); + $psr = new PsrContainer($pimple); + + $this->assertFalse($psr->has('service')); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php new file mode 100644 index 00000000..c9a08125 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php @@ -0,0 +1,134 @@ + + */ +class ServiceLocatorTest extends TestCase +{ + public function testCanAccessServices() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $locator = new ServiceLocator($pimple, array('service')); + + $this->assertSame($pimple['service'], $locator->get('service')); + } + + public function testCanAccessAliasedServices() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $locator = new ServiceLocator($pimple, array('alias' => 'service')); + + $this->assertSame($pimple['service'], $locator->get('alias')); + } + + /** + * @expectedException \Pimple\Exception\UnknownIdentifierException + * @expectedExceptionMessage Identifier "service" is not defined. + */ + public function testCannotAccessAliasedServiceUsingRealIdentifier() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $locator = new ServiceLocator($pimple, array('alias' => 'service')); + + $service = $locator->get('service'); + } + + /** + * @expectedException \Pimple\Exception\UnknownIdentifierException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testGetValidatesServiceCanBeLocated() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $locator = new ServiceLocator($pimple, array('alias' => 'service')); + + $service = $locator->get('foo'); + } + + /** + * @expectedException \Pimple\Exception\UnknownIdentifierException + * @expectedExceptionMessage Identifier "invalid" is not defined. + */ + public function testGetValidatesTargetServiceExists() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $locator = new ServiceLocator($pimple, array('alias' => 'invalid')); + + $service = $locator->get('alias'); + } + + public function testHasValidatesServiceCanBeLocated() + { + $pimple = new Container(); + $pimple['service1'] = function () { + return new Fixtures\Service(); + }; + $pimple['service2'] = function () { + return new Fixtures\Service(); + }; + $locator = new ServiceLocator($pimple, array('service1')); + + $this->assertTrue($locator->has('service1')); + $this->assertFalse($locator->has('service2')); + } + + public function testHasChecksIfTargetServiceExists() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $locator = new ServiceLocator($pimple, array('foo' => 'service', 'bar' => 'invalid')); + + $this->assertTrue($locator->has('foo')); + $this->assertFalse($locator->has('bar')); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php b/vendor/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php new file mode 100644 index 00000000..5dd52f0d --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php @@ -0,0 +1,52 @@ +assertSame(array('service1' => $pimple['service1'], 'service2' => $pimple['service2']), iterator_to_array($iterator)); + } +} diff --git a/vendor/psr/container/.gitignore b/vendor/psr/container/.gitignore new file mode 100644 index 00000000..b2395aa0 --- /dev/null +++ b/vendor/psr/container/.gitignore @@ -0,0 +1,3 @@ +composer.lock +composer.phar +/vendor/ diff --git a/vendor/psr/container/LICENSE b/vendor/psr/container/LICENSE new file mode 100644 index 00000000..2877a489 --- /dev/null +++ b/vendor/psr/container/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2016 container-interop +Copyright (c) 2016 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/container/README.md b/vendor/psr/container/README.md new file mode 100644 index 00000000..084f6df5 --- /dev/null +++ b/vendor/psr/container/README.md @@ -0,0 +1,5 @@ +# PSR Container + +This repository holds all interfaces/classes/traits related to [PSR-11](https://github.com/container-interop/fig-standards/blob/master/proposed/container.md). + +Note that this is not a container implementation of its own. See the specification for more details. diff --git a/vendor/psr/container/composer.json b/vendor/psr/container/composer.json new file mode 100644 index 00000000..b8ee0126 --- /dev/null +++ b/vendor/psr/container/composer.json @@ -0,0 +1,27 @@ +{ + "name": "psr/container", + "type": "library", + "description": "Common Container Interface (PHP FIG PSR-11)", + "keywords": ["psr", "psr-11", "container", "container-interop", "container-interface"], + "homepage": "https://github.com/php-fig/container", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/container/src/ContainerExceptionInterface.php b/vendor/psr/container/src/ContainerExceptionInterface.php new file mode 100644 index 00000000..d35c6b4d --- /dev/null +++ b/vendor/psr/container/src/ContainerExceptionInterface.php @@ -0,0 +1,13 @@ +log(LogLevel::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 void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::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 void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::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 void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 00000000..67f852d1 --- /dev/null +++ b/vendor/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/vendor/psr/log/Psr/Log/LoggerInterface.php b/vendor/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 00000000..5ea72438 --- /dev/null +++ b/vendor/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,123 @@ +log(LogLevel::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 void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::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 void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::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 void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/vendor/psr/log/Psr/Log/NullLogger.php b/vendor/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 00000000..d8cd682c --- /dev/null +++ b/vendor/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,28 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php new file mode 100644 index 00000000..a0391a52 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php @@ -0,0 +1,140 @@ + ". + * + * Example ->error('Foo') would yield "error Foo". + * + * @return string[] + */ + abstract public function getLogs(); + + public function testImplements() + { + $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + $level.' message of level '.$level.' with context: Bob', + $level.' message of level '.$level.' with context: Bob', + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + /** + * @expectedException \Psr\Log\InvalidArgumentException + */ + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + if (method_exists($this, 'createPartialMock')) { + $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); + } else { + $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); + } + $dummy->expects($this->once()) + ->method('__toString') + ->will($this->returnValue('DUMMY')); + + $this->getLogger()->warning($dummy); + + $expected = array('warning DUMMY'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextCanContainAnything() + { + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => new DummyTest), + 'object' => new \DateTime, + 'resource' => fopen('php://memory', 'r'), + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!' + ); + $this->assertEquals($expected, $this->getLogs()); + } +} + +class DummyTest +{ + public function __toString() + { + } +} diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md new file mode 100644 index 00000000..574bc1cb --- /dev/null +++ b/vendor/psr/log/README.md @@ -0,0 +1,45 @@ +PSR Log +======= + +This repository holds all interfaces/classes/traits related to +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). + +Note that this is not a logger of its own. It is merely an interface that +describes a logger. See the specification for more details. + +Usage +----- + +If you need a logger, you can use the interface like this: + +```php +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + // do something useful + } +} +``` + +You can then pick one of the implementations of the interface to get a logger. + +If you want to implement the interface, you can require this package and +implement `Psr\Log\LoggerInterface` in your code. Please read the +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +for details. diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json new file mode 100644 index 00000000..87934d70 --- /dev/null +++ b/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/silex/silex/.gitignore b/vendor/silex/silex/.gitignore new file mode 100644 index 00000000..3d4ff050 --- /dev/null +++ b/vendor/silex/silex/.gitignore @@ -0,0 +1,5 @@ +/phpunit.xml +/vendor +/build +/composer.lock + diff --git a/vendor/silex/silex/.travis.yml b/vendor/silex/silex/.travis.yml new file mode 100644 index 00000000..a2b7fe8c --- /dev/null +++ b/vendor/silex/silex/.travis.yml @@ -0,0 +1,59 @@ +language: php + +sudo: false + +env: + global: + - SYMFONY_DEPRECATIONS_HELPER=weak + +cache: + directories: + - $HOME/.composer/cache/files + - .phpunit + +before_install: + - if [[ $TRAVIS_PHP_VERSION != hhvm ]]; then phpenv config-rm xdebug.ini; fi + +before_script: + # Twig 1.x + - if [[ $TWIG_VERSION != 2.0 ]]; then sed -i 's/~1.8|~2.0/~1.8/g' composer.json; fi + + # Symfony 2.8 + - if [[ $SYMFONY_DEPS_VERSION = 2.8 ]]; then sed -i 's/~2\.8|^3\.0/2.8.*@dev/g' composer.json; fi + # Symfony 3.0 + - if [[ $SYMFONY_DEPS_VERSION = 3.0 ]]; then sed -i 's/~2\.8|^3\.0/3.0.*@dev/g' composer.json; fi + # Symfony 3.1 + - if [[ $SYMFONY_DEPS_VERSION = 3.1 ]]; then sed -i 's/~2\.8|^3\.0/3.1.*@dev/g' composer.json; fi + # Symfony 3.2 + - if [[ $SYMFONY_DEPS_VERSION = 3.2 ]]; then sed -i 's/~2\.8|^3\.0/3.2.*@dev/g' composer.json; fi + # Symfony 3.3 + - | + if [[ $SYMFONY_DEPS_VERSION = 3.3 ]]; then + sed -i 's/~2\.8|^3\.0/3.3.*@dev/g' composer.json; + composer require --dev --no-update symfony/web-link:3.3.* + fi + + - composer update --no-suggest + +script: ./vendor/bin/simple-phpunit + +matrix: + include: + - php: 5.5 + - php: 5.6 + env: TWIG_VERSION=2.0 + - php: 5.6 + env: SYMFONY_DEPS_VERSION=2.8 + - php: 5.6 + env: SYMFONY_DEPS_VERSION=3.0 + - php: 5.6 + env: SYMFONY_DEPS_VERSION=3.1 + - php: 5.6 + env: SYMFONY_DEPS_VERSION=3.2 + - php: 5.6 + env: SYMFONY_DEPS_VERSION=3.3 + - php: 5.6 + - php: 7.0 + - php: 7.1 + - php: hhvm + dist: trusty diff --git a/vendor/silex/silex/LICENSE b/vendor/silex/silex/LICENSE new file mode 100644 index 00000000..b420d719 --- /dev/null +++ b/vendor/silex/silex/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-2017 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/silex/silex/README.rst b/vendor/silex/silex/README.rst new file mode 100644 index 00000000..b79e47b6 --- /dev/null +++ b/vendor/silex/silex/README.rst @@ -0,0 +1,64 @@ +Silex, a simple Web Framework +============================= + +Silex is a PHP micro-framework to develop websites based on `Symfony +components`_: + +.. code-block:: php + + get('/hello/{name}', function ($name) use ($app) { + return 'Hello '.$app->escape($name); + }); + + $app->run(); + +Silex works with PHP 5.5.9 or later. + +Installation +------------ + +The recommended way to install Silex is through `Composer`_: + +.. code-block:: bash + + composer require silex/silex "~2.0" + +Alternatively, you can download the `silex.zip`_ file and extract it. + +More Information +---------------- + +Read the `documentation`_ for more information and `changelog +`_ for upgrading information. + +Tests +----- + +To run the test suite, you need `Composer`_ and `PHPUnit`_: + +.. code-block:: bash + + composer install + phpunit + +Community +--------- + +Check out #silex-php on irc.freenode.net. + +License +------- + +Silex is licensed under the MIT license. + +.. _Symfony components: http://symfony.com +.. _Composer: http://getcomposer.org +.. _PHPUnit: https://phpunit.de +.. _silex.zip: http://silex.sensiolabs.org/download +.. _documentation: http://silex.sensiolabs.org/documentation diff --git a/vendor/silex/silex/composer.json b/vendor/silex/silex/composer.json new file mode 100644 index 00000000..935c5181 --- /dev/null +++ b/vendor/silex/silex/composer.json @@ -0,0 +1,72 @@ +{ + "name": "silex/silex", + "description": "The PHP micro-framework based on the Symfony Components", + "keywords": ["microframework"], + "homepage": "http://silex.sensiolabs.org", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0", + "symfony/event-dispatcher": "~2.8|^3.0", + "symfony/http-foundation": "~2.8|^3.0", + "symfony/http-kernel": "~2.8|^3.0", + "symfony/routing": "~2.8|^3.0" + }, + "require-dev": { + "symfony/asset": "~2.8|^3.0", + "symfony/expression-language": "~2.8|^3.0", + "symfony/security": "~2.8|^3.0", + "symfony/config": "~2.8|^3.0", + "symfony/form": "~2.8|^3.0", + "symfony/browser-kit": "~2.8|^3.0", + "symfony/css-selector": "~2.8|^3.0", + "symfony/debug": "~2.8|^3.0", + "symfony/dom-crawler": "~2.8|^3.0", + "symfony/finder": "~2.8|^3.0", + "symfony/intl": "~2.8|^3.0", + "symfony/monolog-bridge": "~2.8|^3.0", + "symfony/doctrine-bridge": "~2.8|^3.0", + "symfony/options-resolver": "~2.8|^3.0", + "symfony/phpunit-bridge": "^3.2", + "symfony/process": "~2.8|^3.0", + "symfony/serializer": "~2.8|^3.0", + "symfony/translation": "~2.8|^3.0", + "symfony/twig-bridge": "~2.8|^3.0", + "symfony/validator": "~2.8|^3.0", + "symfony/var-dumper": "~2.8|^3.0", + "twig/twig": "~1.28|~2.0", + "doctrine/dbal": "~2.2", + "swiftmailer/swiftmailer": "~5", + "monolog/monolog": "^1.4.1", + "symfony/web-link": "^3.3" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35 || >= 5.0, <5.4.3" + }, + "replace": { + "silex/api": "self.version", + "silex/providers": "self.version" + }, + "autoload": { + "psr-4": { "Silex\\": "src/Silex" } + }, + "autoload-dev" : { + "psr-4": { "Silex\\Tests\\" : "tests/Silex/Tests" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/silex/silex/doc/changelog.rst b/vendor/silex/silex/doc/changelog.rst new file mode 100644 index 00000000..a2917561 --- /dev/null +++ b/vendor/silex/silex/doc/changelog.rst @@ -0,0 +1,387 @@ +Changelog +========= + +2.2.0 (2017-07-23) +------------------ + +* added json manifest version strategy support +* fixed EsiFragment constructor +* fixed RedirectableUrlMatcher compatibility with Symfony +* fixed compatibility with Pimple 3.2 +* fixed WebTestCase compatibility with PHPUnit 6+ + +2.1.0 (2017-05-03) +------------------ + +* added more options to security.firewalls +* added WebLink component integration +* added parameters to configure the Twig core extension behavior +* fixed deprecation notices with symfony/twig-bridge 3.2+ in TwigServiceProvider +* added FormRegistry as a service to enable the extension point +* removed the build scripts +* fixed some deprecation warnings +* added support for registering Swiftmailer plugins + +2.0.4 (2016-11-06) +------------------ + +* fixed twig.app_variable definition +* added support for latest versions of Twig 1.x and 2.0 (Twig runtime loaders) +* added support for Symfony 2.3 + +2.0.3 (2016-08-22) +------------------ + +* fixed lazy evaluation of 'monolog.use_error_handler' +* fixed PHP7 type hint on controllers + +2.0.2 (2016-06-14) +------------------ + +* fixed Symfony 3.1 deprecations + +2.0.1 (2016-05-27) +------------------ + +* fixed the silex form extension registration to allow overriding default ones +* removed support for the obsolete Locale Symfony component (uses the Intl one now) +* added support for Symfony 3.1 + +2.0.0 (2016-05-18) +------------------ + +* decoupled the exception handler from HttpKernelServiceProvider +* Switched to BCrypt as the default encoder in the security provider +* added full support for RequestMatcher +* added support for Symfony Guard +* added support for callables in CallbackResolver +* added FormTrait::namedForm() +* added support for delivery_addresses, delivery_whitelist, and sender_address +* added support to register form types / form types extensions / form types guessers as services +* added support for callable in mounts (allow nested route collection to be built easily) +* added support for conditions on routes +* added support for the Symfony VarDumper Component +* added a global Twig variable (an AppVariable instance) +* [BC BREAK] CSRF has been moved to a standalone provider (``form.secret`` is not available anymore) +* added support for the Symfony HttpFoundation Twig bridge extension +* added support for the Symfony Asset Component +* bumped minimum version of Symfony to 2.8 +* bumped minimum version of PHP to 5.5.0 +* Updated Pimple to 3.0 +* Updated session listeners to extends HttpKernel ones +* [BC BREAK] Locale management has been moved to LocaleServiceProvider which must be registered + if you want Silex to manage your locale (must also be registered for the translation service provider) +* [BC BREAK] Provider interfaces moved to Silex\Api namespace, published as + separate package via subtree split +* [BC BREAK] ServiceProviderInterface split in to EventListenerProviderInterface + and BootableProviderInterface +* [BC BREAK] Service Provider support files moved under Silex\Provider + namespace, allowing publishing as separate package via sub-tree split +* ``monolog.exception.logger_filter`` option added to Monolog service provider +* [BC BREAK] ``$app['request']`` service removed, use ``$app['request_stack']`` instead + +1.3.6 (2016-XX-XX) +------------------ + +* n/a + +1.3.5 (2016-01-06) +------------------ + +* fixed typo in SecurityServiceProvider + +1.3.4 (2015-09-15) +------------------ + +* fixed some new deprecations +* fixed translation registration for the validators + +1.3.3 (2015-09-08) +------------------ + +* added support for Symfony 3.0 and Twig 2.0 +* fixed some Form deprecations +* removed deprecated method call in the exception handler +* fixed Swiftmailer spool flushing when spool is not enabled + +1.3.2 (2015-08-24) +------------------ + +* no changes + +1.3.1 (2015-08-04) +------------------ + +* added missing support for the Expression constraint +* fixed the possibility to override translations for validator error messages +* fixed sub-mounts with same name clash +* fixed session logout handler when a firewall is stateless + +1.3.0 (2015-06-05) +------------------ + +* added a `$app['user']` to get the current user (security provider) +* added view handlers +* added support for the OPTIONS HTTP method +* added caching for the Translator provider +* deprecated `$app['exception_handler']->disable()` in favor of `unset($app['exception_handler'])` +* made Silex compatible with Symfony 2.7 an 2.8 (and keep compatibility with Symfony 2.3, 2.5, and 2.6) +* removed deprecated TwigCoreExtension class (register the new HttpFragmentServiceProvider instead) +* bumped minimum version of PHP to 5.3.9 + +1.2.5 (2015-06-04) +------------------ + +* no code changes (last version of the 1.2 branch) + +1.2.4 (2015-04-11) +------------------ + +* fixed the exception message when mounting a collection that doesn't return a ControllerCollection +* fixed Symfony dependencies (Silex 1.2 is not compatible with Symfony 2.7) + +1.2.3 (2015-01-20) +------------------ + +* fixed remember me listener +* fixed translation files loading when they do not exist +* allowed global after middlewares to return responses like route specific ones + +1.2.2 (2014-09-26) +------------------ + +* fixed Translator locale management +* added support for the $app argument in application middlewares (to make it consistent with route middlewares) +* added form.types to the Form provider + +1.2.1 (2014-07-01) +------------------ + +* added support permissions in the Monolog provider +* fixed Switfmailer spool where the event dispatcher is different from the other ones +* fixed locale when changing it on the translator itself + +1.2.0 (2014-03-29) +------------------ + +* Allowed disabling the boot logic of MonologServiceProvider +* Reverted "convert attributes on the request that actually exist" +* [BC BREAK] Routes are now always added in the order of their registration (even for mounted routes) +* Added run() on Route to be able to define the controller code +* Deprecated TwigCoreExtension (register the new HttpFragmentServiceProvider instead) +* Added HttpFragmentServiceProvider +* Allowed a callback to be a method call on a service (before, after, finish, error, on Application; convert, before, after on Controller) + +1.1.3 (2013-XX-XX) +------------------ + +* Fixed translator locale management + +1.1.2 (2013-10-30) +------------------ + +* Added missing "security.hide_user_not_found" support in SecurityServiceProvider +* Fixed event listeners that are registered after the boot via the on() method + +1.0.2 (2013-10-30) +------------------ + +* Fixed SecurityServiceProvider to use null as a fake controller so that routes can be dumped + +1.1.1 (2013-10-11) +------------------ + +* Removed or replaced deprecated Symfony code +* Updated code to take advantages of 2.3 new features +* Only convert attributes on the request that actually exist. + +1.1.0 (2013-07-04) +------------------ + +* Support for any ``Psr\Log\LoggerInterface`` as opposed to the monolog-bridge + one. +* Made dispatcher proxy methods ``on``, ``before``, ``after`` and ``error`` + lazy, so that they will not instantiate the dispatcher early. +* Dropped support for 2.1 and 2.2 versions of Symfony. + +1.0.1 (2013-07-04) +------------------ + +* Fixed RedirectableUrlMatcher::redirect() when Silex is configured to use a logger +* Make ``DoctrineServiceProvider`` multi-db support lazy. + +1.0.0 (2013-05-03) +------------------ + +* **2013-04-12**: Added support for validators as services. + +* **2013-04-01**: Added support for host matching with symfony 2.2:: + + $app->match('/', function() { + // app-specific action + })->host('example.com'); + + $app->match('/', function ($user) { + // user-specific action + })->host('{user}.example.com'); + +* **2013-03-08**: Added support for form type extensions and guessers as + services. + +* **2013-03-08**: Added support for remember-me via the + ``RememberMeServiceProvider``. + +* **2013-02-07**: Added ``Application::sendFile()`` to ease sending + ``BinaryFileResponse``. + +* **2012-11-05**: Filters have been renamed to application middlewares in the + documentation. + +* **2012-11-05**: The ``before()``, ``after()``, ``error()``, and ``finish()`` + listener priorities now set the priority of the underlying Symfony event + instead of a custom one before. + +* **2012-11-05**: Removing the default exception handler should now be done + via its ``disable()`` method: + + Before: + + unset($app['exception_handler']); + + After: + + $app['exception_handler']->disable(); + +* **2012-07-15**: removed the ``monolog.configure`` service. Use the + ``extend`` method instead: + + Before:: + + $app['monolog.configure'] = $app->protect(function ($monolog) use ($app) { + // do something + }); + + After:: + + $app['monolog'] = $app->share($app->extend('monolog', function($monolog, $app) { + // do something + + return $monolog; + })); + + +* **2012-06-17**: ``ControllerCollection`` now takes a required route instance + as a constructor argument. + + Before:: + + $controllers = new ControllerCollection(); + + After:: + + $controllers = new ControllerCollection(new Route()); + + // or even better + $controllers = $app['controllers_factory']; + +* **2012-06-17**: added application traits for PHP 5.4 + +* **2012-06-16**: renamed ``request.default_locale`` to ``locale`` + +* **2012-06-16**: Removed the ``translator.loader`` service. See documentation + for how to use XLIFF or YAML-based translation files. + +* **2012-06-15**: removed the ``twig.configure`` service. Use the ``extend`` + method instead: + + Before:: + + $app['twig.configure'] = $app->protect(function ($twig) use ($app) { + // do something + }); + + After:: + + $app['twig'] = $app->share($app->extend('twig', function($twig, $app) { + // do something + + return $twig; + })); + +* **2012-06-13**: Added a route ``before`` middleware + +* **2012-06-13**: Renamed the route ``middleware`` to ``before`` + +* **2012-06-13**: Added an extension for the Symfony Security component + +* **2012-05-31**: Made the ``BrowserKit``, ``CssSelector``, ``DomCrawler``, + ``Finder`` and ``Process`` components optional dependencies. Projects that + depend on them (e.g. through functional tests) should add those dependencies + to their ``composer.json``. + +* **2012-05-26**: added ``boot()`` to ``ServiceProviderInterface``. + +* **2012-05-26**: Removed ``SymfonyBridgesServiceProvider``. It is now implicit + by checking the existence of the bridge. + +* **2012-05-26**: Removed the ``translator.messages`` parameter (use + ``translator.domains`` instead). + +* **2012-05-24**: Removed the ``autoloader`` service (use composer instead). + The ``*.class_path`` settings on all the built-in providers have also been + removed in favor of Composer. + +* **2012-05-21**: Changed error() to allow handling specific exceptions. + +* **2012-05-20**: Added a way to define settings on a controller collection. + +* **2012-05-20**: The Request instance is not available anymore from the + Application after it has been handled. + +* **2012-04-01**: Added ``finish`` filters. + +* **2012-03-20**: Added ``json`` helper:: + + $data = array('some' => 'data'); + $response = $app->json($data); + +* **2012-03-11**: Added route middlewares. + +* **2012-03-02**: Switched to use Composer for dependency management. + +* **2012-02-27**: Updated to Symfony 2.1 session handling. + +* **2012-01-02**: Introduced support for streaming responses. + +* **2011-09-22**: ``ExtensionInterface`` has been renamed to + ``ServiceProviderInterface``. All built-in extensions have been renamed + accordingly (for instance, ``Silex\Extension\TwigExtension`` has been + renamed to ``Silex\Provider\TwigServiceProvider``). + +* **2011-09-22**: The way reusable applications work has changed. The + ``mount()`` method now takes an instance of ``ControllerCollection`` instead + of an ``Application`` one. + + Before:: + + $app = new Application(); + $app->get('/bar', function() { return 'foo'; }); + + return $app; + + After:: + + $app = new ControllerCollection(); + $app->get('/bar', function() { return 'foo'; }); + + return $app; + +* **2011-08-08**: The controller method configuration is now done on the Controller itself + + Before:: + + $app->match('/', function () { echo 'foo'; }, 'GET|POST'); + + After:: + + $app->match('/', function () { echo 'foo'; })->method('GET|POST'); diff --git a/vendor/silex/silex/doc/conf.py b/vendor/silex/silex/doc/conf.py new file mode 100644 index 00000000..dfe355c7 --- /dev/null +++ b/vendor/silex/silex/doc/conf.py @@ -0,0 +1,17 @@ +import sys, os +from sphinx.highlighting import lexers +from pygments.lexers.web import PhpLexer + +sys.path.append(os.path.abspath('_exts')) + +extensions = [] +master_doc = 'index' +highlight_language = 'php' + +project = u'Silex' +copyright = u'2010 Fabien Potencier' + +version = '0' +release = '0.0.0' + +lexers['php'] = PhpLexer(startinline=True) diff --git a/vendor/silex/silex/doc/contributing.rst b/vendor/silex/silex/doc/contributing.rst new file mode 100644 index 00000000..34a339d8 --- /dev/null +++ b/vendor/silex/silex/doc/contributing.rst @@ -0,0 +1,34 @@ +Contributing +============ + +We are open to contributions to the Silex code. If you find a bug or want to +contribute a provider, just follow these steps: + +* Fork `the Silex repository `_; + +* Make your feature addition or bug fix; + +* Add tests for it; + +* Optionally, add some documentation; + +* `Send a pull request + `_, to the correct + target branch (1.3 for bug fixes, master for new features). + +.. note:: + + Any code you contribute must be licensed under the MIT + License. + +Writing Documentation +===================== + +The documentation is written in `reStructuredText +`_ and can be generated using `sphinx +`_. + +.. code-block:: bash + + $ cd doc + $ sphinx-build -b html . build diff --git a/vendor/silex/silex/doc/cookbook/error_handler.rst b/vendor/silex/silex/doc/cookbook/error_handler.rst new file mode 100644 index 00000000..235c263a --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/error_handler.rst @@ -0,0 +1,38 @@ +Converting Errors to Exceptions +=============================== + +Silex catches exceptions that are thrown from within a request/response cycle. +However, it does *not* catch PHP errors and notices. This recipe tells you how +to catch them by converting them to exceptions. + +Registering the ErrorHandler +---------------------------- + +The ``Symfony/Debug`` package has an ``ErrorHandler`` class that solves this +problem. It converts all errors to exceptions, and exceptions are then caught +by Silex. + +Register it by calling the static ``register`` method:: + + use Symfony\Component\Debug\ErrorHandler; + + ErrorHandler::register(); + +It is recommended that you do this as early as possible. + +Handling fatal errors +--------------------- + +To handle fatal errors, you can additionally register a global +``ExceptionHandler``:: + + use Symfony\Component\Debug\ExceptionHandler; + + ExceptionHandler::register(); + +In production you may want to disable the debug output by passing ``false`` as +the ``$debug`` argument:: + + use Symfony\Component\Debug\ExceptionHandler; + + ExceptionHandler::register(false); diff --git a/vendor/silex/silex/doc/cookbook/form_no_csrf.rst b/vendor/silex/silex/doc/cookbook/form_no_csrf.rst new file mode 100644 index 00000000..e9bf595e --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/form_no_csrf.rst @@ -0,0 +1,36 @@ +Disabling CSRF Protection on a Form using the FormExtension +=========================================================== + +The *FormExtension* provides a service for building form in your application +with the Symfony Form component. When the :doc:`CSRF Service Provider +` is registered, the *FormExtension* uses the CSRF Protection +avoiding Cross-site request forgery, a method by which a malicious user +attempts to make your legitimate users unknowingly submit data that they don't +intend to submit. + +You can find more details about CSRF Protection and CSRF token in the +`Symfony Book +`_. + +In some cases (for example, when embedding a form in an html email) you might +want not to use this protection. The easiest way to avoid this is to +understand that it is possible to give specific options to your form builder +through the ``createBuilder()`` function. + +Example +------- + +.. code-block:: php + + $form = $app['form.factory']->createBuilder('form', null, array('csrf_protection' => false)); + +That's it, your form could be submitted from everywhere without CSRF Protection. + +Going further +------------- + +This specific example showed how to change the ``csrf_protection`` in the +``$options`` parameter of the ``createBuilder()`` function. More of them could +be passed through this parameter, it is as simple as using the Symfony +``getDefaultOptions()`` method in your form classes. `See more here +`_. diff --git a/vendor/silex/silex/doc/cookbook/guard_authentication.rst b/vendor/silex/silex/doc/cookbook/guard_authentication.rst new file mode 100644 index 00000000..8774f686 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/guard_authentication.rst @@ -0,0 +1,183 @@ +How to Create a Custom Authentication System with Guard +======================================================= + +Whether you need to build a traditional login form, an API token +authentication system or you need to integrate with some proprietary +single-sign-on system, the Guard component can make it easy... and fun! + +In this example, you'll build an API token authentication system and +learn how to work with Guard. + +Step 1) Create the Authenticator Class +-------------------------------------- + +Suppose you have an API where your clients will send an X-AUTH-TOKEN +header on each request. This token is composed of the username followed +by a password, separated by a colon (e.g. ``X-AUTH-TOKEN: coolguy:awesomepassword``). +Your job is to read this, find the associated user (if any) and check +the password. + +To create a custom authentication system, just create a class and make +it implement GuardAuthenticatorInterface. Or, extend the simpler +AbstractGuardAuthenticator. This requires you to implement six methods: + +.. code-block:: php + + encoderFactory = $encoderFactory; + } + + public function getCredentials(Request $request) + { + // Checks if the credential header is provided + if (!$token = $request->headers->get('X-AUTH-TOKEN')) { + return; + } + + // Parse the header or ignore it if the format is incorrect. + if (false === strpos($token, ':')) { + return; + } + list($username, $secret) = explode(':', $token, 2); + + return array( + 'username' => $username, + 'secret' => $secret, + ); + } + + public function getUser($credentials, UserProviderInterface $userProvider) + { + return $userProvider->loadUserByUsername($credentials['username']); + } + + public function checkCredentials($credentials, UserInterface $user) + { + // check credentials - e.g. make sure the password is valid + // return true to cause authentication success + + $encoder = $this->encoderFactory->getEncoder($user); + + return $encoder->isPasswordValid( + $user->getPassword(), + $credentials['secret'], + $user->getSalt() + ); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) + { + // on success, let the request continue + return; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception) + { + $data = array( + 'message' => strtr($exception->getMessageKey(), $exception->getMessageData()), + + // or to translate this message + // $this->translator->trans($exception->getMessageKey(), $exception->getMessageData()) + ); + + return new JsonResponse($data, 403); + } + + /** + * Called when authentication is needed, but it's not sent + */ + public function start(Request $request, AuthenticationException $authException = null) + { + $data = array( + // you might translate this message + 'message' => 'Authentication Required', + ); + + return new JsonResponse($data, 401); + } + + public function supportsRememberMe() + { + return false; + } + } + + +Step 2) Configure the Authenticator +----------------------------------- + +To finish this, register the class as a service: + +.. code-block:: php + + $app['app.token_authenticator'] = function ($app) { + return new App\Security\TokenAuthenticator($app['security.encoder_factory']); + }; + + +Finally, configure your `security.firewalls` key to use this authenticator: + +.. code-block:: php + + $app['security.firewalls'] = array( + 'main' => array( + 'guard' => array( + 'authenticators' => array( + 'app.token_authenticator' + ), + + // Using more than 1 authenticator, you must specify + // which one is used as entry point. + // 'entry_point' => 'app.token_authenticator', + ), + // configure where your users come from. Hardcode them, or load them from somewhere + // http://silex.sensiolabs.org/doc/providers/security.html#defining-a-custom-user-provider + 'users' => array( + //raw password = foo + 'victoria' => array('ROLE_USER', '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a'), + ), + // 'anonymous' => true + ), + ); + +.. note:: + You can use many authenticators, they are executed by the order + they are configured. + +You did it! You now have a fully-working API token authentication +system. If your homepage required ROLE_USER, then you could test it +under different conditions: + +.. code-block:: bash + + # test with no token + curl http://localhost:8000/ + # {"message":"Authentication Required"} + + # test with a bad token + curl -H "X-AUTH-TOKEN: alan" http://localhost:8000/ + # {"message":"Username could not be found."} + + # test with a working token + curl -H "X-AUTH-TOKEN: victoria:foo" http://localhost:8000/ + # the homepage controller is executed: the page loads normally + +For more details read the Symfony cookbook entry on +`How to Create a Custom Authentication System with Guard `_. diff --git a/vendor/silex/silex/doc/cookbook/index.rst b/vendor/silex/silex/doc/cookbook/index.rst new file mode 100644 index 00000000..53b10fe2 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/index.rst @@ -0,0 +1,40 @@ +Cookbook +======== + +The cookbook section contains recipes for solving specific problems. + +.. toctree:: + :maxdepth: 1 + :hidden: + + json_request_body + session_storage + form_no_csrf + validator_yaml + sub_requests + error_handler + multiple_loggers + guard_authentication + +Recipes +------- + +* :doc:`Accepting a JSON Request Body ` A common need when + building a restful API is the ability to accept a JSON encoded entity from + the request body. + +* :doc:`Using PdoSessionStorage to store Sessions in the Database + `. + +* :doc:`Disabling the CSRF Protection on a Form using the FormExtension + `. + +* :doc:`Using YAML to configure Validation `. + +* :doc:`Making sub-Requests `. + +* :doc:`Converting Errors to Exceptions `. + +* :doc:`Using multiple Monolog Loggers `. + +* :doc:`How to Create a Custom Authentication System with Guard `. diff --git a/vendor/silex/silex/doc/cookbook/json_request_body.rst b/vendor/silex/silex/doc/cookbook/json_request_body.rst new file mode 100644 index 00000000..47159008 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/json_request_body.rst @@ -0,0 +1,95 @@ +Accepting a JSON Request Body +============================= + +A common need when building a restful API is the ability to accept a JSON +encoded entity from the request body. + +An example for such an API could be a blog post creation. + +Example API +----------- + +In this example we will create an API for creating a blog post. The following +is a spec of how we want it to work. + +Request +~~~~~~~ + +In the request we send the data for the blog post as a JSON object. We also +indicate that using the ``Content-Type`` header: + +.. code-block:: text + + POST /blog/posts + Accept: application/json + Content-Type: application/json + Content-Length: 57 + + {"title":"Hello World!","body":"This is my first post!"} + +Response +~~~~~~~~ + +The server responds with a 201 status code, telling us that the post was +created. It tells us the ``Content-Type`` of the response, which is also +JSON: + +.. code-block:: text + + HTTP/1.1 201 Created + Content-Type: application/json + Content-Length: 65 + Connection: close + + {"id":"1","title":"Hello World!","body":"This is my first post!"} + +Parsing the request body +------------------------ + +The request body should only be parsed as JSON if the ``Content-Type`` header +begins with ``application/json``. Since we want to do this for every request, +the easiest solution is to use an application before middleware. + +We simply use ``json_decode`` to parse the content of the request and then +replace the request data on the ``$request`` object:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\ParameterBag; + + $app->before(function (Request $request) { + if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) { + $data = json_decode($request->getContent(), true); + $request->request->replace(is_array($data) ? $data : array()); + } + }); + +Controller implementation +------------------------- + +Our controller will create a new blog post from the data provided and will +return the post object, including its ``id``, as JSON:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + $app->post('/blog/posts', function (Request $request) use ($app) { + $post = array( + 'title' => $request->request->get('title'), + 'body' => $request->request->get('body'), + ); + + $post['id'] = createPost($post); + + return $app->json($post, 201); + }); + +Manual testing +-------------- + +In order to manually test our API, we can use the ``curl`` command line +utility, which allows sending HTTP requests: + +.. code-block:: bash + + $ curl http://blog.lo/blog/posts -d '{"title":"Hello World!","body":"This is my first post!"}' -H 'Content-Type: application/json' + {"id":"1","title":"Hello World!","body":"This is my first post!"} diff --git a/vendor/silex/silex/doc/cookbook/multiple_loggers.rst b/vendor/silex/silex/doc/cookbook/multiple_loggers.rst new file mode 100644 index 00000000..cb103953 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/multiple_loggers.rst @@ -0,0 +1,69 @@ +Using multiple Monolog Loggers +============================== + +Having separate instances of Monolog for different parts of your system is +often desirable and allows you to configure them independently, allowing for fine +grained control of where your logging goes and in what detail. + +This simple example allows you to quickly configure several monolog instances, +using the bundled handler, but each with a different channel. + +.. code-block:: php + + $app['monolog.factory'] = $app->protect(function ($name) use ($app) { + $log = new $app['monolog.logger.class']($name); + $log->pushHandler($app['monolog.handler']); + + return $log; + }); + + foreach (array('auth', 'payments', 'stats') as $channel) { + $app['monolog.'.$channel] = function ($app) use ($channel) { + return $app['monolog.factory']($channel); + }; + } + +As your application grows, or your logging needs for certain areas of the +system become apparent, it should be straightforward to then configure that +particular service separately, including your customizations. + +.. code-block:: php + + use Monolog\Handler\StreamHandler; + + $app['monolog.payments'] = function ($app) { + $log = new $app['monolog.logger.class']('payments'); + $handler = new StreamHandler($app['monolog.payments.logfile'], $app['monolog.payment.level']); + $log->pushHandler($handler); + + return $log; + }; + +Alternatively, you could attempt to make the factory more complicated, and rely +on some conventions, such as checking for an array of handlers registered with +the container with the channel name, defaulting to the bundled handler. + +.. code-block:: php + + use Monolog\Handler\StreamHandler; + use Monolog\Logger; + + $app['monolog.factory'] = $app->protect(function ($name) use ($app) { + $log = new $app['monolog.logger.class']($name); + + $handlers = isset($app['monolog.'.$name.'.handlers']) + ? $app['monolog.'.$name.'.handlers'] + : array($app['monolog.handler']); + + foreach ($handlers as $handler) { + $log->pushHandler($handler); + } + + return $log; + }); + + $app['monolog.payments.handlers'] = function ($app) { + return array( + new StreamHandler(__DIR__.'/../payments.log', Logger::DEBUG), + ); + }; diff --git a/vendor/silex/silex/doc/cookbook/session_storage.rst b/vendor/silex/silex/doc/cookbook/session_storage.rst new file mode 100644 index 00000000..29328b49 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/session_storage.rst @@ -0,0 +1,89 @@ +Using PdoSessionStorage to store Sessions in the Database +========================================================= + +By default, the :doc:`SessionServiceProvider ` writes +session information in files using Symfony NativeFileSessionStorage. Most +medium to large websites use a database to store sessions instead of files, +because databases are easier to use and scale in a multi-webserver environment. + +Symfony's `NativeSessionStorage +`_ +has multiple storage handlers and one of them uses PDO to store sessions, +`PdoSessionHandler +`_. +To use it, replace the ``session.storage.handler`` service in your application +like explained below. + +With a dedicated PDO service +---------------------------- + +.. code-block:: php + + use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + + $app->register(new Silex\Provider\SessionServiceProvider()); + + $app['pdo.dsn'] = 'mysql:dbname=mydatabase'; + $app['pdo.user'] = 'myuser'; + $app['pdo.password'] = 'mypassword'; + + $app['session.db_options'] = array( + 'db_table' => 'session', + 'db_id_col' => 'session_id', + 'db_data_col' => 'session_value', + 'db_time_col' => 'session_time', + ); + + $app['pdo'] = function () use ($app) { + return new PDO( + $app['pdo.dsn'], + $app['pdo.user'], + $app['pdo.password'] + ); + }; + + $app['session.storage.handler'] = function () use ($app) { + return new PdoSessionHandler( + $app['pdo'], + $app['session.db_options'] + ); + }; + +Using the DoctrineServiceProvider +--------------------------------- + +When using the :doc:`DoctrineServiceProvider ` You don't +have to make another database connection, simply pass the getWrappedConnection method. + +.. code-block:: php + + use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + + $app->register(new Silex\Provider\SessionServiceProvider()); + + $app['session.storage.handler'] = function () use ($app) { + return new PdoSessionHandler( + $app['db']->getWrappedConnection(), + array( + 'db_table' => 'session', + 'db_id_col' => 'session_id', + 'db_data_col' => 'session_value', + 'db_lifetime_col' => 'session_lifetime', + 'db_time_col' => 'session_time', + ) + ); + }; + +Database structure +------------------ + +PdoSessionStorage needs a database table with 3 columns: + +* ``session_id``: ID column (VARCHAR(255) or larger) +* ``session_value``: Value column (TEXT or CLOB) +* ``session_lifetime``: Lifetime column (INTEGER) +* ``session_time``: Time column (INTEGER) + +You can find examples of SQL statements to create the session table in the +`Symfony cookbook +`_ diff --git a/vendor/silex/silex/doc/cookbook/sub_requests.rst b/vendor/silex/silex/doc/cookbook/sub_requests.rst new file mode 100644 index 00000000..95d39136 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/sub_requests.rst @@ -0,0 +1,137 @@ +Making sub-Requests +=================== + +Since Silex is based on the ``HttpKernelInterface``, it allows you to simulate +requests against your application. This means that you can embed a page within +another, it also allows you to forward a request which is essentially an +internal redirect that does not change the URL. + +Basics +------ + +You can make a sub-request by calling the ``handle`` method on the +``Application``. This method takes three arguments: + +* ``$request``: An instance of the ``Request`` class which represents the + HTTP request. + +* ``$type``: Must be either ``HttpKernelInterface::MASTER_REQUEST`` or + ``HttpKernelInterface::SUB_REQUEST``. Certain listeners are only executed for + the master request, so it's important that this is set to ``SUB_REQUEST``. + +* ``$catch``: Catches exceptions and turns them into a response with status code + ``500``. This argument defaults to ``true``. For sub-requests you will most + likely want to set it to ``false``. + +By calling ``handle``, you can make a sub-request manually. Here's an example:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpKernel\HttpKernelInterface; + + $subRequest = Request::create('/'); + $response = $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); + +There's some more things that you need to keep in mind though. In most cases +you will want to forward some parts of the current master request to the +sub-request like cookies, server information, or the session. + +Here is a more advanced example that forwards said information (``$request`` +holds the master request):: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpKernel\HttpKernelInterface; + + $subRequest = Request::create('/', 'GET', array(), $request->cookies->all(), array(), $request->server->all()); + if ($request->getSession()) { + $subRequest->setSession($request->getSession()); + } + + $response = $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); + +To forward this response to the client, you can simply return it from a +controller:: + + use Silex\Application; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpKernel\HttpKernelInterface; + + $app->get('/foo', function (Application $app, Request $request) { + $subRequest = Request::create('/', ...); + $response = $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); + + return $response; + }); + +If you want to embed the response as part of a larger page you can call +``Response::getContent``:: + + $header = ...; + $footer = ...; + $body = $response->getContent(); + + return $header.$body.$footer; + +Rendering pages in Twig templates +--------------------------------- + +The :doc:`TwigServiceProvider ` provides a ``render`` +function that you can use in Twig templates. It gives you a convenient way to +embed pages. + +.. code-block:: jinja + + {{ render('/sidebar') }} + +For details, refer to the :doc:`TwigServiceProvider ` docs. + +Edge Side Includes +------------------ + +You can use ESI either through the :doc:`HttpCacheServiceProvider +` or a reverse proxy cache such as Varnish. This also +allows you to embed pages, however it also gives you the benefit of caching +parts of the page. + +Here is an example of how you would embed a page via ESI: + +.. code-block:: jinja + + + +For details, refer to the :doc:`HttpCacheServiceProvider +` docs. + +Dealing with the request base URL +--------------------------------- + +One thing to watch out for is the base URL. If your application is not +hosted at the webroot of your web server, then you may have an URL like +``http://example.org/foo/index.php/articles/42``. + +In this case, ``/foo/index.php`` is your request base path. Silex accounts for +this path prefix in the routing process, it reads it from +``$request->server``. In the context of sub-requests this can lead to issues, +because if you do not prepend the base path the request could mistake a part +of the path you want to match as the base path and cut it off. + +You can prevent that from happening by always prepending the base path when +constructing a request:: + + $url = $request->getUriForPath('/'); + $subRequest = Request::create($url, 'GET', array(), $request->cookies->all(), array(), $request->server->all()); + +This is something to be aware of when making sub-requests by hand. + +Services depending on the Request +--------------------------------- + +The container is a concept that is global to a Silex application, since the +application object **is** the container. Any request that is run against an +application will re-use the same set of services. Since these services are +mutable, code in a master request can affect the sub-requests and vice versa. +Any services depending on the ``request`` service will store the first request +that they get (could be master or sub-request), and keep using it, even if +that request is already over. + +Instead of injecting the ``request`` service, you should always inject the +``request_stack`` one instead. diff --git a/vendor/silex/silex/doc/cookbook/validator_yaml.rst b/vendor/silex/silex/doc/cookbook/validator_yaml.rst new file mode 100644 index 00000000..10a41fcf --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/validator_yaml.rst @@ -0,0 +1,35 @@ +Using YAML to configure Validation +================================== + +Simplicity is at the heart of Silex so there is no out of the box solution to +use YAML files for validation. But this doesn't mean that this is not +possible. Let's see how to do it. + +First, you need to install the YAML Component: + +.. code-block:: bash + + composer require symfony/yaml + +Next, you need to tell the Validation Service that you are not using +``StaticMethodLoader`` to load your class metadata but a YAML file:: + + $app->register(new ValidatorServiceProvider()); + + $app['validator.mapping.class_metadata_factory'] = new Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory( + new Symfony\Component\Validator\Mapping\Loader\YamlFileLoader(__DIR__.'/validation.yml') + ); + +Now, we can replace the usage of the static method and move all the validation +rules to ``validation.yml``: + +.. code-block:: yaml + + # validation.yml + Post: + properties: + title: + - NotNull: ~ + - NotBlank: ~ + body: + - Min: 100 diff --git a/vendor/silex/silex/doc/index.rst b/vendor/silex/silex/doc/index.rst new file mode 100644 index 00000000..d1a851d0 --- /dev/null +++ b/vendor/silex/silex/doc/index.rst @@ -0,0 +1,19 @@ +The Book +======== + +.. toctree:: + :maxdepth: 1 + + intro + usage + middlewares + organizing_controllers + services + providers + testing + cookbook/index + internals + contributing + providers/index + web_servers + changelog diff --git a/vendor/silex/silex/doc/internals.rst b/vendor/silex/silex/doc/internals.rst new file mode 100644 index 00000000..c7ffac8c --- /dev/null +++ b/vendor/silex/silex/doc/internals.rst @@ -0,0 +1,84 @@ +Internals +========= + +This chapter will tell you how Silex works internally. + +Silex +----- + +Application +~~~~~~~~~~~ + +The application is the main interface to Silex. It implements Symfony's +`HttpKernelInterface +`_, +so you can pass a `Request +`_ +to the ``handle`` method and it will return a `Response +`_. + +It extends the ``Pimple`` service container, allowing for flexibility on the +outside as well as the inside. You could replace any service, and you are also +able to read them. + +The application makes strong use of the `EventDispatcher +`_ to hook into the Symfony `HttpKernel +`_ +events. This allows fetching the ``Request``, converting string responses into +``Response`` objects and handling Exceptions. We also use it to dispatch some +custom events like before/after middlewares and errors. + +Controller +~~~~~~~~~~ + +The Symfony `Route +`_ is +actually quite powerful. Routes can be named, which allows for URL generation. +They can also have requirements for the variable parts. In order to allow +setting these through a nice interface, the ``match`` method (which is used by +``get``, ``post``, etc.) returns an instance of the ``Controller``, which +wraps a route. + +ControllerCollection +~~~~~~~~~~~~~~~~~~~~ + +One of the goals of exposing the `RouteCollection +`_ +was to make it mutable, so providers could add stuff to it. The challenge here +is the fact that routes know nothing about their name. The name only has +meaning in context of the ``RouteCollection`` and cannot be changed. + +To solve this challenge we came up with a staging area for routes. The +``ControllerCollection`` holds the controllers until ``flush`` is called, at +which point the routes are added to the ``RouteCollection``. Also, the +controllers are then frozen. This means that they can no longer be modified +and will throw an Exception if you try to do so. + +Unfortunately no good way for flushing implicitly could be found, which is why +flushing is now always explicit. The Application will flush, but if you want +to read the ``ControllerCollection`` before the request takes place, you will +have to call flush yourself. + +The ``Application`` provides a shortcut ``flush`` method for flushing the +``ControllerCollection``. + +.. tip:: + + Instead of creating an instance of ``RouteCollection`` yourself, use the + ``$app['controllers_factory']`` factory instead. + +Symfony +------- + +Following Symfony components are used by Silex: + +* **HttpFoundation**: For ``Request`` and ``Response``. + +* **HttpKernel**: Because we need a heart. + +* **Routing**: For matching defined routes. + +* **EventDispatcher**: For hooking into the HttpKernel. + +For more information, `check out the Symfony website `_. diff --git a/vendor/silex/silex/doc/intro.rst b/vendor/silex/silex/doc/intro.rst new file mode 100644 index 00000000..2ab2bc30 --- /dev/null +++ b/vendor/silex/silex/doc/intro.rst @@ -0,0 +1,50 @@ +Introduction +============ + +Silex is a PHP microframework. It is built on the shoulders of `Symfony`_ and +`Pimple`_ and also inspired by `Sinatra`_. + +Silex aims to be: + +* *Concise*: Silex exposes an intuitive and concise API. + +* *Extensible*: Silex has an extension system based around the Pimple + service-container that makes it easy to tie in third party libraries. + +* *Testable*: Silex uses Symfony's HttpKernel which abstracts request and + response. This makes it very easy to test apps and the framework itself. It + also respects the HTTP specification and encourages its proper use. + +In a nutshell, you define controllers and map them to routes, all in one step. + +Usage +----- + +.. code-block:: php + + get('/hello/{name}', function ($name) use ($app) { + return 'Hello '.$app->escape($name); + }); + + $app->run(); + +All that is needed to get access to the Framework is to include the +autoloader. + +Next, a route for ``/hello/{name}`` that matches for ``GET`` requests is +defined. When the route matches, the function is executed and the return value +is sent back to the client. + +Finally, the app is run. Visit ``/hello/world`` to see the result. It's really +that easy! + +.. _Symfony: http://symfony.com/ +.. _Pimple: http://pimple.sensiolabs.org/ +.. _Sinatra: http://www.sinatrarb.com/ diff --git a/vendor/silex/silex/doc/middlewares.rst b/vendor/silex/silex/doc/middlewares.rst new file mode 100644 index 00000000..c5c17cf0 --- /dev/null +++ b/vendor/silex/silex/doc/middlewares.rst @@ -0,0 +1,162 @@ +Middleware +========== + +Silex allows you to run code, that changes the default Silex behavior, at +different stages during the handling of a request through *middleware*: + +* *Application middleware* is triggered independently of the current handled + request; + +* *Route middleware* is triggered when its associated route is matched. + +Application Middleware +---------------------- + +Application middleware is only run for the "master" Request. + +Before Middleware +~~~~~~~~~~~~~~~~~ + +A *before* application middleware allows you to tweak the Request before the +controller is executed:: + + $app->before(function (Request $request, Application $app) { + // ... + }); + +By default, the middleware is run after the routing and the security. + +If you want your middleware to be run even if an exception is thrown early on +(on a 404 or 403 error for instance), then, you need to register it as an +early event:: + + $app->before(function (Request $request, Application $app) { + // ... + }, Application::EARLY_EVENT); + +In this case, the routing and the security won't have been executed, and so you +won't have access to the locale, the current route, or the security user. + +.. note:: + + The before middleware is an event registered on the Symfony *request* + event. + +After Middleware +~~~~~~~~~~~~~~~~ + +An *after* application middleware allows you to tweak the Response before it +is sent to the client:: + + $app->after(function (Request $request, Response $response) { + // ... + }); + +.. note:: + + The after middleware is an event registered on the Symfony *response* + event. + +Finish Middleware +~~~~~~~~~~~~~~~~~ + +A *finish* application middleware allows you to execute tasks after the +Response has been sent to the client (like sending emails or logging):: + + $app->finish(function (Request $request, Response $response) { + // ... + // Warning: modifications to the Request or Response will be ignored + }); + +.. note:: + + The finish middleware is an event registered on the Symfony *terminate* + event. + +Route Middleware +---------------- + +Route middleware is added to routes or route collections and it is only +triggered when the corresponding route is matched. You can also stack them:: + + $app->get('/somewhere', function () { + // ... + }) + ->before($before1) + ->before($before2) + ->after($after1) + ->after($after2) + ; + +Before Middleware +~~~~~~~~~~~~~~~~~ + +A *before* route middleware is fired just before the route callback, but after +the *before* application middleware:: + + $before = function (Request $request, Application $app) { + // ... + }; + + $app->get('/somewhere', function () { + // ... + }) + ->before($before); + +After Middleware +~~~~~~~~~~~~~~~~ + +An *after* route middleware is fired just after the route callback, but before +the application *after* application middleware:: + + $after = function (Request $request, Response $response, Application $app) { + // ... + }; + + $app->get('/somewhere', function () { + // ... + }) + ->after($after); + +Middleware Priority +------------------- + +You can add as much middleware as you want, in which case they are triggered +in the same order as you added them. + +You can explicitly control the priority of your middleware by passing an +additional argument to the registration methods:: + + $app->before(function (Request $request) { + // ... + }, 32); + +As a convenience, two constants allow you to register an event as early as +possible or as late as possible:: + + $app->before(function (Request $request) { + // ... + }, Application::EARLY_EVENT); + + $app->before(function (Request $request) { + // ... + }, Application::LATE_EVENT); + +Short-circuiting the Controller +------------------------------- + +If a *before* middleware returns a ``Response`` object, the request handling is +short-circuited (the next middleware won't be run, nor the route +callback), and the Response is passed to the *after* middleware right away:: + + $app->before(function (Request $request) { + // redirect the user to the login screen if access to the Resource is protected + if (...) { + return new RedirectResponse('/login'); + } + }); + +.. note:: + + A ``RuntimeException`` is thrown if a before middleware does not return a + Response or ``null``. diff --git a/vendor/silex/silex/doc/organizing_controllers.rst b/vendor/silex/silex/doc/organizing_controllers.rst new file mode 100644 index 00000000..50558cbb --- /dev/null +++ b/vendor/silex/silex/doc/organizing_controllers.rst @@ -0,0 +1,84 @@ +Organizing Controllers +====================== + +When your application starts to define too many controllers, you might want to +group them logically:: + + // define controllers for a blog + $blog = $app['controllers_factory']; + $blog->get('/', function () { + return 'Blog home page'; + }); + // ... + + // define controllers for a forum + $forum = $app['controllers_factory']; + $forum->get('/', function () { + return 'Forum home page'; + }); + + // define "global" controllers + $app->get('/', function () { + return 'Main home page'; + }); + + $app->mount('/blog', $blog); + $app->mount('/forum', $forum); + + // define controllers for a admin + $app->mount('/admin', function ($admin) { + // recursively mount + $admin->mount('/blog', function ($user) { + $user->get('/', function () { + return 'Admin Blog home page'; + }); + }); + }); + +.. note:: + + ``$app['controllers_factory']`` is a factory that returns a new instance + of ``ControllerCollection`` when used. + +``mount()`` prefixes all routes with the given prefix and merges them into the +main Application. So, ``/`` will map to the main home page, ``/blog/`` to the +blog home page, ``/forum/`` to the forum home page, and ``/admin/blog/`` to the +admin blog home page. + +.. caution:: + + When mounting a route collection under ``/blog``, it is not possible to + define a route for the ``/blog`` URL. The shortest possible URL is + ``/blog/``. + +.. note:: + + When calling ``get()``, ``match()``, or any other HTTP methods on the + Application, you are in fact calling them on a default instance of + ``ControllerCollection`` (stored in ``$app['controllers']``). + +Another benefit is the ability to apply settings on a set of controllers very +easily. Building on the example from the middleware section, here is how you +would secure all controllers for the backend collection:: + + $backend = $app['controllers_factory']; + + // ensure that all controllers require logged-in users + $backend->before($mustBeLogged); + +.. tip:: + + For a better readability, you can split each controller collection into a + separate file:: + + // blog.php + $blog = $app['controllers_factory']; + $blog->get('/', function () { return 'Blog home page'; }); + + return $blog; + + // app.php + $app->mount('/blog', include 'blog.php'); + + Instead of requiring a file, you can also create a :ref:`Controller + provider `. diff --git a/vendor/silex/silex/doc/providers.rst b/vendor/silex/silex/doc/providers.rst new file mode 100644 index 00000000..c3d049db --- /dev/null +++ b/vendor/silex/silex/doc/providers.rst @@ -0,0 +1,262 @@ +Providers +========= + +Providers allow the developer to reuse parts of an application into another +one. Silex provides two types of providers defined by two interfaces: +``ServiceProviderInterface`` for services and ``ControllerProviderInterface`` +for controllers. + +Service Providers +----------------- + +Loading providers +~~~~~~~~~~~~~~~~~ + +In order to load and use a service provider, you must register it on the +application:: + + $app = new Silex\Application(); + + $app->register(new Acme\DatabaseServiceProvider()); + +You can also provide some parameters as a second argument. These will be set +**after** the provider is registered, but **before** it is booted:: + + $app->register(new Acme\DatabaseServiceProvider(), array( + 'database.dsn' => 'mysql:host=localhost;dbname=myapp', + 'database.user' => 'root', + 'database.password' => 'secret_root_password', + )); + +Conventions +~~~~~~~~~~~ + +You need to watch out in what order you do certain things when interacting +with providers. Just keep these rules in mind: + +* Overriding existing services must occur **after** the provider is + registered. + + *Reason: If the service already exists, the provider will overwrite it.* + +* You can set parameters any time **after** the provider is registered, but + **before** the service is accessed. + + *Reason: Providers can set default values for parameters. Just like with + services, the provider will overwrite existing values.* + +Included providers +~~~~~~~~~~~~~~~~~~ + +There are a few providers that you get out of the box. All of these are within +the ``Silex\Provider`` namespace: + +* :doc:`AssetServiceProvider ` +* :doc:`CsrfServiceProvider ` +* :doc:`DoctrineServiceProvider ` +* :doc:`FormServiceProvider ` +* :doc:`HttpCacheServiceProvider ` +* :doc:`HttpFragmentServiceProvider ` +* :doc:`LocaleServiceProvider ` +* :doc:`MonologServiceProvider ` +* :doc:`RememberMeServiceProvider ` +* :doc:`SecurityServiceProvider ` +* :doc:`SerializerServiceProvider ` +* :doc:`ServiceControllerServiceProvider ` +* :doc:`SessionServiceProvider ` +* :doc:`SwiftmailerServiceProvider ` +* :doc:`TranslationServiceProvider ` +* :doc:`TwigServiceProvider ` +* :doc:`ValidatorServiceProvider ` +* :doc:`VarDumperServiceProvider ` + +.. note:: + + The Silex core team maintains a `WebProfiler + `_ provider that helps debug + code in the development environment thanks to the Symfony web debug toolbar + and the Symfony profiler. + +Third party providers +~~~~~~~~~~~~~~~~~~~~~ + +Some service providers are developed by the community. Those third-party +providers are listed on `Silex' repository wiki +`_. + +You are encouraged to share yours. + +Creating a provider +~~~~~~~~~~~~~~~~~~~ + +Providers must implement the ``Pimple\ServiceProviderInterface``:: + + interface ServiceProviderInterface + { + public function register(Container $container); + } + +This is very straight forward, just create a new class that implements the +register method. In the ``register()`` method, you can define services on the +application which then may make use of other services and parameters. + +.. tip:: + + The ``Pimple\ServiceProviderInterface`` belongs to the Pimple package, so + take care to only use the API of ``Pimple\Container`` within your + ``register`` method. Not only is this a good practice due to the way Pimple + and Silex work, but may allow your provider to be used outside of Silex. + +Optionally, your service provider can implement the +``Silex\Api\BootableProviderInterface``. A bootable provider must +implement the ``boot()`` method, with which you can configure the application, just +before it handles a request:: + + interface BootableProviderInterface + { + function boot(Application $app); + } + +Another optional interface, is the ``Silex\Api\EventListenerProviderInterface``. +This interface contains the ``subscribe()`` method, which allows your provider to +subscribe event listener with Silex's EventDispatcher, just before it handles a +request:: + + interface EventListenerProviderInterface + { + function subscribe(Container $app, EventDispatcherInterface $dispatcher); + } + +Here is an example of such a provider:: + + namespace Acme; + + use Pimple\Container; + use Pimple\ServiceProviderInterface; + use Silex\Application; + use Silex\Api\BootableProviderInterface; + use Silex\Api\EventListenerProviderInterface; + use Symfony\Component\EventDispatcher\EventDispatcherInterface; + use Symfony\Component\HttpKernel\KernelEvents; + use Symfony\Component\HttpKernel\Event\FilterResponseEvent; + + class HelloServiceProvider implements ServiceProviderInterface, BootableProviderInterface, EventListenerProviderInterface + { + public function register(Container $app) + { + $app['hello'] = $app->protect(function ($name) use ($app) { + $default = $app['hello.default_name'] ? $app['hello.default_name'] : ''; + $name = $name ?: $default; + + return 'Hello '.$app->escape($name); + }); + } + + public function boot(Application $app) + { + // do something + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addListener(KernelEvents::REQUEST, function(FilterResponseEvent $event) use ($app) { + // do something + }); + } + } + +This class provides a ``hello`` service which is a protected closure. It takes +a ``name`` argument and will return ``hello.default_name`` if no name is +given. If the default is also missing, it will use an empty string. + +You can now use this provider as follows:: + + use Symfony\Component\HttpFoundation\Request; + + $app = new Silex\Application(); + + $app->register(new Acme\HelloServiceProvider(), array( + 'hello.default_name' => 'Igor', + )); + + $app->get('/hello', function (Request $request) use ($app) { + $name = $request->get('name'); + + return $app['hello']($name); + }); + +In this example we are getting the ``name`` parameter from the query string, +so the request path would have to be ``/hello?name=Fabien``. + +.. _controller-providers: + +Controller Providers +-------------------- + +Loading providers +~~~~~~~~~~~~~~~~~ + +In order to load and use a controller provider, you must "mount" its +controllers under a path:: + + $app = new Silex\Application(); + + $app->mount('/blog', new Acme\BlogControllerProvider()); + +All controllers defined by the provider will now be available under the +``/blog`` path. + +Creating a provider +~~~~~~~~~~~~~~~~~~~ + +Providers must implement the ``Silex\Api\ControllerProviderInterface``:: + + interface ControllerProviderInterface + { + public function connect(Application $app); + } + +Here is an example of such a provider:: + + namespace Acme; + + use Silex\Application; + use Silex\Api\ControllerProviderInterface; + + class HelloControllerProvider implements ControllerProviderInterface + { + public function connect(Application $app) + { + // creates a new controller based on the default route + $controllers = $app['controllers_factory']; + + $controllers->get('/', function (Application $app) { + return $app->redirect('/hello'); + }); + + return $controllers; + } + } + +The ``connect`` method must return an instance of ``ControllerCollection``. +``ControllerCollection`` is the class where all controller related methods are +defined (like ``get``, ``post``, ``match``, ...). + +.. tip:: + + The ``Application`` class acts in fact as a proxy for these methods. + +You can use this provider as follows:: + + $app = new Silex\Application(); + + $app->mount('/blog', new Acme\HelloControllerProvider()); + +In this example, the ``/blog/`` path now references the controller defined in +the provider. + +.. tip:: + + You can also define a provider that implements both the service and the + controller provider interface and package in the same class the services + needed to make your controllers work. diff --git a/vendor/silex/silex/doc/providers/asset.rst b/vendor/silex/silex/doc/providers/asset.rst new file mode 100644 index 00000000..72c3d703 --- /dev/null +++ b/vendor/silex/silex/doc/providers/asset.rst @@ -0,0 +1,67 @@ +Asset +===== + +The *AssetServiceProvider* provides a way to manage URL generation and +versioning of web assets such as CSS stylesheets, JavaScript files and image +files. + +Parameters +---------- + +* **assets.version**: Default version for assets. + +* **assets.format_version** (optional): Default format for assets. + +* **assets.named_packages** (optional): Named packages. Keys are the package + names and values the configuration (supported keys are ``version``, + ``version_format``, ``base_urls``, and ``base_path``). + +Services +-------- + +* **assets.packages**: The asset service. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\AssetServiceProvider(), array( + 'assets.version' => 'v1', + 'assets.version_format' => '%s?version=%s', + 'assets.named_packages' => array( + 'css' => array('version' => 'css2', 'base_path' => '/whatever-makes-sense'), + 'images' => array('base_urls' => array('https://img.example.com')), + ), + )); + +.. note:: + + Add the Symfony Asset Component as a dependency: + + .. code-block:: bash + + composer require symfony/asset + + If you want to use assets in your Twig templates, you must also install the + Symfony Twig Bridge: + + .. code-block:: bash + + composer require symfony/twig-bridge + +Usage +----- + +The AssetServiceProvider is mostly useful with the Twig provider: + +.. code-block:: jinja + + {{ asset('/css/foo.png') }} + {{ asset('/css/foo.css', 'css') }} + {{ asset('/img/foo.png', 'images') }} + + {{ asset_version('/css/foo.png') }} + +For more information, check out the `Asset Component documentation +`_. diff --git a/vendor/silex/silex/doc/providers/csrf.rst b/vendor/silex/silex/doc/providers/csrf.rst new file mode 100644 index 00000000..a055c112 --- /dev/null +++ b/vendor/silex/silex/doc/providers/csrf.rst @@ -0,0 +1,53 @@ +CSRF +==== + +The *CsrfServiceProvider* provides a service for building forms in your +application with the Symfony Form component. + +Parameters +---------- + +* none + +Services +-------- + +* **csrf.token_manager**: An instance of an implementation of the + `CsrfTokenManagerInterface + `_, + +Registering +----------- + +.. code-block:: php + + use Silex\Provider\CsrfServiceProvider; + + $app->register(new CsrfServiceProvider()); + +.. note:: + + Add the Symfony's `Security CSRF Component + `_ as a + dependency: + + .. code-block:: bash + + composer require symfony/security-csrf + +Usage +----- + +When the CSRF Service Provider is registered, all forms created via the Form +Service Provider are protected against CSRF by default. + +You can also use the CSRF protection without using the Symfony Form component. +If, for example, you're doing a DELETE action, create a CSRF token to use in +your code:: + + use Symfony\Component\Security\Csrf\CsrfToken; + $csrfToken = $app['csrf.token_manager']->getToken('token_id'); //'TOKEN' + +Then check it:: + + $app['csrf.token_manager']->isTokenValid(new CsrfToken('token_id', 'TOKEN')); diff --git a/vendor/silex/silex/doc/providers/doctrine.rst b/vendor/silex/silex/doc/providers/doctrine.rst new file mode 100644 index 00000000..0ef167b7 --- /dev/null +++ b/vendor/silex/silex/doc/providers/doctrine.rst @@ -0,0 +1,137 @@ +Doctrine +======== + +The *DoctrineServiceProvider* provides integration with the `Doctrine DBAL +`_ for easy database access +(Doctrine ORM integration is **not** supplied). + +Parameters +---------- + +* **db.options**: Array of Doctrine DBAL options. + + These options are available: + + * **driver**: The database driver to use, defaults to ``pdo_mysql``. + Can be any of: ``pdo_mysql``, ``pdo_sqlite``, ``pdo_pgsql``, + ``pdo_oci``, ``oci8``, ``ibm_db2``, ``pdo_ibm``, ``pdo_sqlsrv``. + + * **dbname**: The name of the database to connect to. + + * **host**: The host of the database to connect to. Defaults to + localhost. + + * **user**: The user of the database to connect to. Defaults to + root. + + * **password**: The password of the database to connect to. + + * **charset**: Only relevant for ``pdo_mysql``, and ``pdo_oci/oci8``, + specifies the charset used when connecting to the database. + + * **path**: Only relevant for ``pdo_sqlite``, specifies the path to + the SQLite database. + + * **port**: Only relevant for ``pdo_mysql``, ``pdo_pgsql``, and ``pdo_oci/oci8``, + specifies the port of the database to connect to. + + These and additional options are described in detail in the `Doctrine DBAL + configuration documentation `_. + +Services +-------- + +* **db**: The database connection, instance of + ``Doctrine\DBAL\Connection``. + +* **db.config**: Configuration object for Doctrine. Defaults to + an empty ``Doctrine\DBAL\Configuration``. + +* **db.event_manager**: Event Manager for Doctrine. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\DoctrineServiceProvider(), array( + 'db.options' => array( + 'driver' => 'pdo_sqlite', + 'path' => __DIR__.'/app.db', + ), + )); + +.. note:: + + Add the Doctrine DBAL as a dependency: + + .. code-block:: bash + + composer require "doctrine/dbal:~2.2" + +Usage +----- + +The Doctrine provider provides a ``db`` service. Here is a usage +example:: + + $app->get('/blog/{id}', function ($id) use ($app) { + $sql = "SELECT * FROM posts WHERE id = ?"; + $post = $app['db']->fetchAssoc($sql, array((int) $id)); + + return "

{$post['title']}

". + "

{$post['body']}

"; + }); + +Using multiple databases +------------------------ + +The Doctrine provider can allow access to multiple databases. In order to +configure the data sources, replace the **db.options** with **dbs.options**. +**dbs.options** is an array of configurations where keys are connection names +and values are options:: + + $app->register(new Silex\Provider\DoctrineServiceProvider(), array( + 'dbs.options' => array ( + 'mysql_read' => array( + 'driver' => 'pdo_mysql', + 'host' => 'mysql_read.someplace.tld', + 'dbname' => 'my_database', + 'user' => 'my_username', + 'password' => 'my_password', + 'charset' => 'utf8mb4', + ), + 'mysql_write' => array( + 'driver' => 'pdo_mysql', + 'host' => 'mysql_write.someplace.tld', + 'dbname' => 'my_database', + 'user' => 'my_username', + 'password' => 'my_password', + 'charset' => 'utf8mb4', + ), + ), + )); + +The first registered connection is the default and can simply be accessed as +you would if there was only one connection. Given the above configuration, +these two lines are equivalent:: + + $app['db']->fetchAll('SELECT * FROM table'); + + $app['dbs']['mysql_read']->fetchAll('SELECT * FROM table'); + +Using multiple connections:: + + $app->get('/blog/{id}', function ($id) use ($app) { + $sql = "SELECT * FROM posts WHERE id = ?"; + $post = $app['dbs']['mysql_read']->fetchAssoc($sql, array((int) $id)); + + $sql = "UPDATE posts SET value = ? WHERE id = ?"; + $app['dbs']['mysql_write']->executeUpdate($sql, array('newValue', (int) $id)); + + return "

{$post['title']}

". + "

{$post['body']}

"; + }); + +For more information, consult the `Doctrine DBAL documentation +`_. diff --git a/vendor/silex/silex/doc/providers/form.rst b/vendor/silex/silex/doc/providers/form.rst new file mode 100644 index 00000000..6818b858 --- /dev/null +++ b/vendor/silex/silex/doc/providers/form.rst @@ -0,0 +1,216 @@ +Form +==== + +The *FormServiceProvider* provides a service for building forms in +your application with the Symfony Form component. + +Parameters +---------- + +* none + +Services +-------- + +* **form.factory**: An instance of `FormFactory + `_, + that is used to build a form. + +Registering +----------- + +.. code-block:: php + + use Silex\Provider\FormServiceProvider; + + $app->register(new FormServiceProvider()); + +.. note:: + + If you don't want to create your own form layout, it's fine: a default one + will be used. But you will have to register the :doc:`translation provider + ` as the default form layout requires it:: + + $app->register(new Silex\Provider\TranslationServiceProvider(), array( + 'translator.domains' => array(), + )); + + If you want to use validation with forms, do not forget to register the + :doc:`Validator provider `. + +.. note:: + + Add the Symfony Form Component as a dependency: + + .. code-block:: bash + + composer require symfony/form + + If you are going to use the validation extension with forms, you must also + add a dependency to the ``symfony/validator`` and ``symfony/config`` + components: + + .. code-block:: bash + + composer require symfony/validator symfony/config + + If you want to use forms in your Twig templates, you can also install the + Symfony Twig Bridge. Make sure to install, if you didn't do that already, + the Translation component in order for the bridge to work: + + .. code-block:: bash + + composer require symfony/twig-bridge + +Usage +----- + +The FormServiceProvider provides a ``form.factory`` service. Here is a usage +example:: + + use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + use Symfony\Component\Form\Extension\Core\Type\FormType; + use Symfony\Component\Form\Extension\Core\Type\SubmitType; + + $app->match('/form', function (Request $request) use ($app) { + // some default data for when the form is displayed the first time + $data = array( + 'name' => 'Your name', + 'email' => 'Your email', + ); + + $form = $app['form.factory']->createBuilder(FormType::class, $data) + ->add('name') + ->add('email') + ->add('billing_plan', ChoiceType::class, array( + 'choices' => array('free' => 1, 'small business' => 2, 'corporate' => 3), + 'expanded' => true, + )) + ->add('submit', SubmitType::class, [ + 'label' => 'Save', + ]) + ->getForm(); + + $form->handleRequest($request); + + if ($form->isValid()) { + $data = $form->getData(); + + // do something with the data + + // redirect somewhere + return $app->redirect('...'); + } + + // display the form + return $app['twig']->render('index.twig', array('form' => $form->createView())); + }); + +And here is the ``index.twig`` form template (requires ``symfony/twig-bridge``): + +.. code-block:: jinja + +
+ {{ form_widget(form) }} + + +
+ +If you are using the validator provider, you can also add validation to your +form by adding constraints on the fields:: + + use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + use Symfony\Component\Form\Extension\Core\Type\FormType; + use Symfony\Component\Form\Extension\Core\Type\SubmitType; + use Symfony\Component\Form\Extension\Core\Type\TextType; + use Symfony\Component\Validator\Constraints as Assert; + + $app->register(new Silex\Provider\ValidatorServiceProvider()); + $app->register(new Silex\Provider\TranslationServiceProvider(), array( + 'translator.domains' => array(), + )); + + $form = $app['form.factory']->createBuilder(FormType::class) + ->add('name', TextType::class, array( + 'constraints' => array(new Assert\NotBlank(), new Assert\Length(array('min' => 5))) + )) + ->add('email', TextType::class, array( + 'constraints' => new Assert\Email() + )) + ->add('billing_plan', ChoiceType::class, array( + 'choices' => array('free' => 1, 'small business' => 2, 'corporate' => 3), + 'expanded' => true, + 'constraints' => new Assert\Choice(array(1, 2, 3)), + )) + ->add('submit', SubmitType::class, [ + 'label' => 'Save', + ]) + ->getForm(); + +You can register form types by extending ``form.types``:: + + $app['your.type.service'] = function ($app) { + return new YourServiceFormType(); + }; + $app->extend('form.types', function ($types) use ($app) { + $types[] = new YourFormType(); + $types[] = 'your.type.service'; + + return $types; + })); + +You can register form extensions by extending ``form.extensions``:: + + $app->extend('form.extensions', function ($extensions) use ($app) { + $extensions[] = new YourTopFormExtension(); + + return $extensions; + }); + + +You can register form type extensions by extending ``form.type.extensions``:: + + $app['your.type.extension.service'] = function ($app) { + return new YourServiceFormTypeExtension(); + }; + $app->extend('form.type.extensions', function ($extensions) use ($app) { + $extensions[] = new YourFormTypeExtension(); + $extensions[] = 'your.type.extension.service'; + + return $extensions; + }); + +You can register form type guessers by extending ``form.type.guessers``:: + + $app['your.type.guesser.service'] = function ($app) { + return new YourServiceFormTypeGuesser(); + }; + $app->extend('form.type.guessers', function ($guessers) use ($app) { + $guessers[] = new YourFormTypeGuesser(); + $guessers[] = 'your.type.guesser.service'; + + return $guessers; + }); + +.. warning:: + + CSRF protection is only available and automatically enabled when the + :doc:`CSRF Service Provider ` is registered. + +Traits +------ + +``Silex\Application\FormTrait`` adds the following shortcuts: + +* **form**: Creates a FormBuilderInterface instance. + +* **namedForm**: Creates a FormBuilderInterface instance (named). + +.. code-block:: php + + $app->form($data); + + $app->namedForm($name, $data, $options, $type); + +For more information, consult the `Symfony Forms documentation +`_. diff --git a/vendor/silex/silex/doc/providers/http_cache.rst b/vendor/silex/silex/doc/providers/http_cache.rst new file mode 100644 index 00000000..8bc98f67 --- /dev/null +++ b/vendor/silex/silex/doc/providers/http_cache.rst @@ -0,0 +1,128 @@ +HTTP Cache +========== + +The *HttpCacheServiceProvider* provides support for the Symfony Reverse +Proxy. + +Parameters +---------- + +* **http_cache.cache_dir**: The cache directory to store the HTTP cache data. + +* **http_cache.options** (optional): An array of options for the `HttpCache + `_ + constructor. + +Services +-------- + +* **http_cache**: An instance of `HttpCache + `_. + +* **http_cache.esi**: An instance of `Esi + `_, + that implements the ESI capabilities to Request and Response instances. + +* **http_cache.store**: An instance of `Store + `_, + that implements all the logic for storing cache metadata (Request and Response + headers). + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\HttpCacheServiceProvider(), array( + 'http_cache.cache_dir' => __DIR__.'/cache/', + )); + +Usage +----- + +Silex already supports any reverse proxy like Varnish out of the box by +setting Response HTTP cache headers:: + + use Symfony\Component\HttpFoundation\Response; + + $app->get('/', function() { + return new Response('Foo', 200, array( + 'Cache-Control' => 's-maxage=5', + )); + }); + +.. tip:: + + If you want Silex to trust the ``X-Forwarded-For*`` headers from your + reverse proxy at address $ip, you will need to whitelist it as documented + in `Trusting Proxies + `_. + + If you would be running Varnish in front of your application on the same machine:: + + use Symfony\Component\HttpFoundation\Request; + + Request::setTrustedProxies(array('127.0.0.1', '::1')); + $app->run(); + +This provider allows you to use the Symfony reverse proxy natively with +Silex applications by using the ``http_cache`` service. The Symfony reverse proxy +acts much like any other proxy would, so you will want to whitelist it:: + + use Symfony\Component\HttpFoundation\Request; + + Request::setTrustedProxies(array('127.0.0.1')); + $app['http_cache']->run(); + +The provider also provides ESI support:: + + $app->get('/', function() { + $response = new Response(<< + + Hello + + + + + EOF + , 200, array( + 'Surrogate-Control' => 'content="ESI/1.0"', + )); + + $response->setTtl(20); + + return $response; + }); + + $app->get('/included', function() { + $response = new Response('Foo'); + $response->setTtl(5); + + return $response; + }); + + $app['http_cache']->run(); + +If your application doesn't use ESI, you can disable it to slightly improve the +overall performance:: + + $app->register(new Silex\Provider\HttpCacheServiceProvider(), array( + 'http_cache.cache_dir' => __DIR__.'/cache/', + 'http_cache.esi' => null, + )); + +.. tip:: + + To help you debug caching issues, set your application ``debug`` to true. + Symfony automatically adds a ``X-Symfony-Cache`` header to each response + with useful information about cache hits and misses. + + If you are *not* using the Symfony Session provider, you might want to set + the PHP ``session.cache_limiter`` setting to an empty value to avoid the + default PHP behavior. + + Finally, check that your Web server does not override your caching strategy. + +For more information, consult the `Symfony HTTP Cache documentation +`_. diff --git a/vendor/silex/silex/doc/providers/http_fragment.rst b/vendor/silex/silex/doc/providers/http_fragment.rst new file mode 100644 index 00000000..8e681853 --- /dev/null +++ b/vendor/silex/silex/doc/providers/http_fragment.rst @@ -0,0 +1,70 @@ +HTTP Fragment +============= + +The *HttpFragmentServiceProvider* provides support for the Symfony fragment +sub-framework, which allows you to embed fragments of HTML in a template. + +Parameters +---------- + +* **fragment.path**: The path to use for the URL generated for ESI and + HInclude URLs (``/_fragment`` by default). + +* **uri_signer.secret**: The secret to use for the URI signer service (used + for the HInclude renderer). + +* **fragment.renderers.hinclude.global_template**: The content or Twig + template to use for the default content when using the HInclude renderer. + +Services +-------- + +* **fragment.handler**: An instance of `FragmentHandler + `_. + +* **fragment.renderers**: An array of fragment renderers (by default, the + inline, ESI, and HInclude renderers are pre-configured). + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\HttpFragmentServiceProvider()); + +Usage +----- + +.. note:: + + This section assumes that you are using Twig for your templates. + +Instead of building a page out of a single request/controller/template, the +fragment framework allows you to build a page from several +controllers/sub-requests/sub-templates by using **fragments**. + +Including "sub-pages" in the main page can be done with the Twig ``render()`` +function: + +.. code-block:: jinja + + The main page content. + + {{ render('/foo') }} + + The main page content resumes here. + +The ``render()`` call is replaced by the content of the ``/foo`` URL +(internally, a sub-request is handled by Silex to render the sub-page). + +Instead of making internal sub-requests, you can also use the ESI (the +sub-request is handled by a reverse proxy) or the HInclude strategies (the +sub-request is handled by a web browser): + +.. code-block:: jinja + + {{ render(url('route_name')) }} + + {{ render_esi(url('route_name')) }} + + {{ render_hinclude(url('route_name')) }} diff --git a/vendor/silex/silex/doc/providers/index.rst b/vendor/silex/silex/doc/providers/index.rst new file mode 100644 index 00000000..8c5a1754 --- /dev/null +++ b/vendor/silex/silex/doc/providers/index.rst @@ -0,0 +1,24 @@ +Built-in Service Providers +========================== + +.. toctree:: + :maxdepth: 1 + + twig + asset + monolog + session + swiftmailer + locale + translation + validator + form + csrf + http_cache + http_fragment + security + remember_me + serializer + service_controller + var_dumper + doctrine diff --git a/vendor/silex/silex/doc/providers/locale.rst b/vendor/silex/silex/doc/providers/locale.rst new file mode 100644 index 00000000..8f6cd675 --- /dev/null +++ b/vendor/silex/silex/doc/providers/locale.rst @@ -0,0 +1,24 @@ +Locale +====== + +The *LocaleServiceProvider* manages the locale of an application. + +Parameters +---------- + +* **locale**: The locale of the user. When set before any request handling, it + defines the default locale (``en`` by default). When a request is being + handled, it is automatically set according to the ``_locale`` request + attribute of the current route. + +Services +-------- + +* n/a + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\LocaleServiceProvider()); diff --git a/vendor/silex/silex/doc/providers/monolog.rst b/vendor/silex/silex/doc/providers/monolog.rst new file mode 100644 index 00000000..645b7106 --- /dev/null +++ b/vendor/silex/silex/doc/providers/monolog.rst @@ -0,0 +1,115 @@ +Monolog +======= + +The *MonologServiceProvider* provides a default logging mechanism through +Jordi Boggiano's `Monolog `_ library. + +It will log requests and errors and allow you to add logging to your +application. This allows you to debug and monitor the behaviour, +even in production. + +Parameters +---------- + +* **monolog.logfile**: File where logs are written to. +* **monolog.bubble**: (optional) Whether the messages that are handled can bubble up the stack or not. +* **monolog.permission**: (optional) File permissions default (null), nothing change. + +* **monolog.level** (optional): Level of logging, defaults + to ``DEBUG``. Must be one of ``Logger::DEBUG``, ``Logger::INFO``, + ``Logger::WARNING``, ``Logger::ERROR``. ``DEBUG`` will log + everything, ``INFO`` will log everything except ``DEBUG``, + etc. + + In addition to the ``Logger::`` constants, it is also possible to supply the + level in string form, for example: ``"DEBUG"``, ``"INFO"``, ``"WARNING"``, + ``"ERROR"``. + +* **monolog.name** (optional): Name of the monolog channel, + defaults to ``myapp``. + +* **monolog.exception.logger_filter** (optional): An anonymous function that + returns an error level for on uncaught exception that should be logged. + +* **monolog.use_error_handler** (optional): Whether errors and uncaught exceptions + should be handled by the Monolog ``ErrorHandler`` class and added to the log. + By default the error handler is enabled unless the application ``debug`` parameter + is set to true. + + Please note that enabling the error handler may silence some errors, + ignoring the PHP ``display_errors`` configuration setting. + +Services +-------- + +* **monolog**: The monolog logger instance. + + Example usage:: + + $app['monolog']->debug('Testing the Monolog logging.'); + +* **monolog.listener**: An event listener to log requests, responses and errors. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\MonologServiceProvider(), array( + 'monolog.logfile' => __DIR__.'/development.log', + )); + +.. note:: + + Add Monolog as a dependency: + + .. code-block:: bash + + composer require monolog/monolog + +Usage +----- + +The MonologServiceProvider provides a ``monolog`` service. You can use it to +add log entries for any logging level through ``debug()``, ``info()``, +``warning()`` and ``error()``:: + + use Symfony\Component\HttpFoundation\Response; + + $app->post('/user', function () use ($app) { + // ... + + $app['monolog']->info(sprintf("User '%s' registered.", $username)); + + return new Response('', 201); + }); + +Customization +------------- + +You can configure Monolog (like adding or changing the handlers) before using +it by extending the ``monolog`` service:: + + $app->extend('monolog', function($monolog, $app) { + $monolog->pushHandler(...); + + return $monolog; + }); + +By default, all requests, responses and errors are logged by an event listener +registered as a service called `monolog.listener`. You can replace or remove +this service if you want to modify or disable the logged information. + +Traits +------ + +``Silex\Application\MonologTrait`` adds the following shortcuts: + +* **log**: Logs a message. + +.. code-block:: php + + $app->log(sprintf("User '%s' registered.", $username)); + +For more information, check out the `Monolog documentation +`_. diff --git a/vendor/silex/silex/doc/providers/remember_me.rst b/vendor/silex/silex/doc/providers/remember_me.rst new file mode 100644 index 00000000..7fdaaaba --- /dev/null +++ b/vendor/silex/silex/doc/providers/remember_me.rst @@ -0,0 +1,69 @@ +Remember Me +=========== + +The *RememberMeServiceProvider* adds "Remember-Me" authentication to the +*SecurityServiceProvider*. + +Parameters +---------- + +n/a + +Services +-------- + +n/a + +.. note:: + + The service provider defines many other services that are used internally + but rarely need to be customized. + +Registering +----------- + +Before registering this service provider, you must register the +*SecurityServiceProvider*:: + + $app->register(new Silex\Provider\SecurityServiceProvider()); + $app->register(new Silex\Provider\RememberMeServiceProvider()); + + $app['security.firewalls'] = array( + 'my-firewall' => array( + 'pattern' => '^/secure$', + 'form' => true, + 'logout' => true, + 'remember_me' => array( + 'key' => 'Choose_A_Unique_Random_Key', + 'always_remember_me' => true, + /* Other options */ + ), + 'users' => array( /* ... */ ), + ), + ); + +Options +------- + +* **key**: A secret key to generate tokens (you should generate a random + string). + +* **name**: Cookie name (default: ``REMEMBERME``). + +* **lifetime**: Cookie lifetime (default: ``31536000`` ~ 1 year). + +* **path**: Cookie path (default: ``/``). + +* **domain**: Cookie domain (default: ``null`` = request domain). + +* **secure**: Cookie is secure (default: ``false``). + +* **httponly**: Cookie is HTTP only (default: ``true``). + +* **always_remember_me**: Enable remember me (default: ``false``). + +* **remember_me_parameter**: Name of the request parameter enabling remember_me + on login. To add the checkbox to the login form. You can find more + information in the `Symfony cookbook + `_ + (default: ``_remember_me``). diff --git a/vendor/silex/silex/doc/providers/security.rst b/vendor/silex/silex/doc/providers/security.rst new file mode 100644 index 00000000..49da8172 --- /dev/null +++ b/vendor/silex/silex/doc/providers/security.rst @@ -0,0 +1,709 @@ +Security +======== + +The *SecurityServiceProvider* manages authentication and authorization for +your applications. + +Parameters +---------- + +* **security.hide_user_not_found** (optional): Defines whether to hide user not + found exception or not. Defaults to ``true``. + +* **security.encoder.bcrypt.cost** (optional): Defines BCrypt password encoder cost. Defaults to 13. + +Services +-------- + +* **security.token_storage**: Gives access to the user token. + +* **security.authorization_checker**: Allows to check authorizations for the + users. + +* **security.authentication_manager**: An instance of + `AuthenticationProviderManager + `_, + responsible for authentication. + +* **security.access_manager**: An instance of `AccessDecisionManager + `_, + responsible for authorization. + +* **security.session_strategy**: Define the session strategy used for + authentication (default to a migration strategy). + +* **security.user_checker**: Checks user flags after authentication. + +* **security.last_error**: Returns the last authentication errors when given a + Request object. + +* **security.encoder_factory**: Defines the encoding strategies for user + passwords (uses ``security.default_encoder``). + +* **security.default_encoder**: The encoder to use by default for all users (BCrypt). + +* **security.encoder.digest**: Digest password encoder. + +* **security.encoder.bcrypt**: BCrypt password encoder. + +* **security.encoder.pbkdf2**: Pbkdf2 password encoder. + +* **user**: Returns the current user + +.. note:: + + The service provider defines many other services that are used internally + but rarely need to be customized. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\SecurityServiceProvider(), array( + 'security.firewalls' => // see below + )); + +.. note:: + + Add the Symfony Security Component as a dependency: + + .. code-block:: bash + + composer require symfony/security + +.. caution:: + + If you're using a form to authenticate users, you need to enable + ``SessionServiceProvider``. + +.. caution:: + + The security features are only available after the Application has been + booted. So, if you want to use it outside of the handling of a request, + don't forget to call ``boot()`` first:: + + $app->boot(); + +Usage +----- + +The Symfony Security component is powerful. To learn more about it, read the +`Symfony Security documentation +`_. + +.. tip:: + + When a security configuration does not behave as expected, enable logging + (with the Monolog extension for instance) as the Security Component logs a + lot of interesting information about what it does and why. + +Below is a list of recipes that cover some common use cases. + +Accessing the current User +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The current user information is stored in a token that is accessible via the +``security`` service:: + + $token = $app['security.token_storage']->getToken(); + +If there is no information about the user, the token is ``null``. If the user +is known, you can get it with a call to ``getUser()``:: + + if (null !== $token) { + $user = $token->getUser(); + } + +The user can be a string, an object with a ``__toString()`` method, or an +instance of `UserInterface +`_. + +Securing a Path with HTTP Authentication +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following configuration uses HTTP basic authentication to secure URLs +under ``/admin/``:: + + $app['security.firewalls'] = array( + 'admin' => array( + 'pattern' => '^/admin', + 'http' => true, + 'users' => array( + // raw password is foo + 'admin' => array('ROLE_ADMIN', '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a'), + ), + ), + ); + +The ``pattern`` is a regular expression on the URL path; the ``http`` setting +tells the security layer to use HTTP basic authentication and the ``users`` +entry defines valid users. + +If you want to restrict the firewall by more than the URL pattern (like the +HTTP method, the client IP, the hostname, or any Request attributes), use an +instance of a `RequestMatcher +`_ +for the ``pattern`` option:: + + use Symfony/Component/HttpFoundation/RequestMatcher; + + $app['security.firewalls'] = array( + 'admin' => array( + 'pattern' => new RequestMatcher('^/admin', 'example.com', 'POST'), + // ... + ), + ); + +Each user is defined with the following information: + +* The role or an array of roles for the user (roles are strings beginning with + ``ROLE_`` and ending with anything you want); + +* The user encoded password. + +.. caution:: + + All users must at least have one role associated with them. + +The default configuration of the extension enforces encoded passwords. To +generate a valid encoded password from a raw password, use the +``security.encoder_factory`` service:: + + // find the encoder for a UserInterface instance + $encoder = $app['security.encoder_factory']->getEncoder($user); + + // compute the encoded password for foo + $password = $encoder->encodePassword('foo', $user->getSalt()); + +When the user is authenticated, the user stored in the token is an instance of +`User +`_ + +.. caution:: + + If you are using php-cgi under Apache, you need to add this configuration + to make things work correctly: + + .. code-block:: apache + + RewriteEngine On + RewriteCond %{HTTP:Authorization} ^(.+)$ + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ app.php [QSA,L] + +Securing a Path with a Form +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Using a form to authenticate users is very similar to the above configuration. +Instead of using the ``http`` setting, use the ``form`` one and define these +two parameters: + +* **login_path**: The login path where the user is redirected when they are + accessing a secured area without being authenticated so that they can enter + their credentials; + +* **check_path**: The check URL used by Symfony to validate the credentials of + the user. + +Here is how to secure all URLs under ``/admin/`` with a form:: + + $app['security.firewalls'] = array( + 'admin' => array( + 'pattern' => '^/admin/', + 'form' => array('login_path' => '/login', 'check_path' => '/admin/login_check'), + 'users' => array( + 'admin' => array('ROLE_ADMIN', '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a'), + ), + ), + ); + +Always keep in mind the following two golden rules: + +* The ``login_path`` path must always be defined **outside** the secured area + (or if it is in the secured area, the ``anonymous`` authentication mechanism + must be enabled -- see below); + +* The ``check_path`` path must always be defined **inside** the secured area. + +For the login form to work, create a controller like the following:: + + use Symfony\Component\HttpFoundation\Request; + + $app->get('/login', function(Request $request) use ($app) { + return $app['twig']->render('login.html', array( + 'error' => $app['security.last_error']($request), + 'last_username' => $app['session']->get('_security.last_username'), + )); + }); + +The ``error`` and ``last_username`` variables contain the last authentication +error and the last username entered by the user in case of an authentication +error. + +Create the associated template: + +.. code-block:: jinja + +
+ {{ error }} + + + +
+ +.. note:: + + The ``admin_login_check`` route is automatically defined by Silex and its + name is derived from the ``check_path`` value (all ``/`` are replaced with + ``_`` and the leading ``/`` is stripped). + +Defining more than one Firewall +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You are not limited to define one firewall per project. + +Configuring several firewalls is useful when you want to secure different +parts of your website with different authentication strategies or for +different users (like using an HTTP basic authentication for the website API +and a form to secure your website administration area). + +It's also useful when you want to secure all URLs except the login form:: + + $app['security.firewalls'] = array( + 'login' => array( + 'pattern' => '^/login$', + ), + 'secured' => array( + 'pattern' => '^.*$', + 'form' => array('login_path' => '/login', 'check_path' => '/login_check'), + 'users' => array( + 'admin' => array('ROLE_ADMIN', '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a'), + ), + ), + ); + +The order of the firewall configurations is significant as the first one to +match wins. The above configuration first ensures that the ``/login`` URL is +not secured (no authentication settings), and then it secures all other URLs. + +.. tip:: + + You can toggle all registered authentication mechanisms for a particular + area on and off with the ``security`` flag:: + + $app['security.firewalls'] = array( + 'api' => array( + 'pattern' => '^/api', + 'security' => $app['debug'] ? false : true, + 'wsse' => true, + + // ... + ), + ); + +Adding a Logout +~~~~~~~~~~~~~~~ + +When using a form for authentication, you can let users log out if you add the +``logout`` setting, where ``logout_path`` must match the main firewall +pattern:: + + $app['security.firewalls'] = array( + 'secured' => array( + 'pattern' => '^/admin/', + 'form' => array('login_path' => '/login', 'check_path' => '/admin/login_check'), + 'logout' => array('logout_path' => '/admin/logout', 'invalidate_session' => true), + + // ... + ), + ); + +A route is automatically generated, based on the configured path (all ``/`` +are replaced with ``_`` and the leading ``/`` is stripped): + +.. code-block:: jinja + +
Logout + +Allowing Anonymous Users +~~~~~~~~~~~~~~~~~~~~~~~~ + +When securing only some parts of your website, the user information are not +available in non-secured areas. To make the user accessible in such areas, +enabled the ``anonymous`` authentication mechanism:: + + $app['security.firewalls'] = array( + 'unsecured' => array( + 'anonymous' => true, + + // ... + ), + ); + +When enabling the anonymous setting, a user will always be accessible from the +security context; if the user is not authenticated, it returns the ``anon.`` +string. + +Checking User Roles +~~~~~~~~~~~~~~~~~~~ + +To check if a user is granted some role, use the ``isGranted()`` method on the +security context:: + + if ($app['security.authorization_checker']->isGranted('ROLE_ADMIN')) { + // ... + } + +You can check roles in Twig templates too: + +.. code-block:: jinja + + {% if is_granted('ROLE_ADMIN') %} + Switch to Fabien + {% endif %} + +You can check if a user is "fully authenticated" (not an anonymous user for +instance) with the special ``IS_AUTHENTICATED_FULLY`` role: + +.. code-block:: jinja + + {% if is_granted('IS_AUTHENTICATED_FULLY') %} + Logout + {% else %} + Login + {% endif %} + +Of course you will need to define a ``login`` route for this to work. + +.. tip:: + + Don't use the ``getRoles()`` method to check user roles. + +.. caution:: + + ``isGranted()`` throws an exception when no authentication information is + available (which is the case on non-secured area). + +Impersonating a User +~~~~~~~~~~~~~~~~~~~~ + +If you want to be able to switch to another user (without knowing the user +credentials), enable the ``switch_user`` authentication strategy:: + + $app['security.firewalls'] = array( + 'unsecured' => array( + 'switch_user' => array('parameter' => '_switch_user', 'role' => 'ROLE_ALLOWED_TO_SWITCH'), + + // ... + ), + ); + +Switching to another user is now a matter of adding the ``_switch_user`` query +parameter to any URL when logged in as a user who has the +``ROLE_ALLOWED_TO_SWITCH`` role: + +.. code-block:: jinja + + {% if is_granted('ROLE_ALLOWED_TO_SWITCH') %} + Switch to user Fabien + {% endif %} + +You can check that you are impersonating a user by checking the special +``ROLE_PREVIOUS_ADMIN``. This is useful for instance to allow the user to +switch back to their primary account: + +.. code-block:: jinja + + {% if is_granted('ROLE_PREVIOUS_ADMIN') %} + You are an admin but you've switched to another user, + exit the switch. + {% endif %} + +Defining a Role Hierarchy +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Defining a role hierarchy allows to automatically grant users some additional +roles:: + + $app['security.role_hierarchy'] = array( + 'ROLE_ADMIN' => array('ROLE_USER', 'ROLE_ALLOWED_TO_SWITCH'), + ); + +With this configuration, all users with the ``ROLE_ADMIN`` role also +automatically have the ``ROLE_USER`` and ``ROLE_ALLOWED_TO_SWITCH`` roles. + +Defining Access Rules +~~~~~~~~~~~~~~~~~~~~~ + +Roles are a great way to adapt the behavior of your website depending on +groups of users, but they can also be used to further secure some areas by +defining access rules:: + + $app['security.access_rules'] = array( + array('^/admin', 'ROLE_ADMIN', 'https'), + array('^.*$', 'ROLE_USER'), + ); + +With the above configuration, users must have the ``ROLE_ADMIN`` to access the +``/admin`` section of the website, and ``ROLE_USER`` for everything else. +Furthermore, the admin section can only be accessible via HTTPS (if that's not +the case, the user will be automatically redirected). + +.. note:: + + The first argument can also be a `RequestMatcher + `_ + instance. + +Defining a custom User Provider +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Using an array of users is simple and useful when securing an admin section of +a personal website, but you can override this default mechanism with you own. + +The ``users`` setting can be defined as a service that returns an instance of +`UserProviderInterface +`_:: + + 'users' => function () use ($app) { + return new UserProvider($app['db']); + }, + +Here is a simple example of a user provider, where Doctrine DBAL is used to +store the users:: + + use Symfony\Component\Security\Core\User\UserProviderInterface; + use Symfony\Component\Security\Core\User\UserInterface; + use Symfony\Component\Security\Core\User\User; + use Symfony\Component\Security\Core\Exception\UnsupportedUserException; + use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; + use Doctrine\DBAL\Connection; + + class UserProvider implements UserProviderInterface + { + private $conn; + + public function __construct(Connection $conn) + { + $this->conn = $conn; + } + + public function loadUserByUsername($username) + { + $stmt = $this->conn->executeQuery('SELECT * FROM users WHERE username = ?', array(strtolower($username))); + + if (!$user = $stmt->fetch()) { + throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username)); + } + + return new User($user['username'], $user['password'], explode(',', $user['roles']), true, true, true, true); + } + + public function refreshUser(UserInterface $user) + { + if (!$user instanceof User) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); + } + + return $this->loadUserByUsername($user->getUsername()); + } + + public function supportsClass($class) + { + return $class === 'Symfony\Component\Security\Core\User\User'; + } + } + +In this example, instances of the default ``User`` class are created for the +users, but you can define your own class; the only requirement is that the +class must implement `UserInterface +`_ + +And here is the code that you can use to create the database schema and some +sample users:: + + use Doctrine\DBAL\Schema\Table; + + $schema = $app['db']->getSchemaManager(); + if (!$schema->tablesExist('users')) { + $users = new Table('users'); + $users->addColumn('id', 'integer', array('unsigned' => true, 'autoincrement' => true)); + $users->setPrimaryKey(array('id')); + $users->addColumn('username', 'string', array('length' => 32)); + $users->addUniqueIndex(array('username')); + $users->addColumn('password', 'string', array('length' => 255)); + $users->addColumn('roles', 'string', array('length' => 255)); + + $schema->createTable($users); + + $app['db']->insert('users', array( + 'username' => 'fabien', + 'password' => '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a', + 'roles' => 'ROLE_USER' + )); + + $app['db']->insert('users', array( + 'username' => 'admin', + 'password' => '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a', + 'roles' => 'ROLE_ADMIN' + )); + } + +.. tip:: + + If you are using the Doctrine ORM, the Symfony bridge for Doctrine + provides a user provider class that is able to load users from your + entities. + +Defining a custom Encoder +~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, Silex uses the ``BCrypt`` algorithm to encode passwords. +Additionally, the password is encoded multiple times. +You can change these defaults by overriding ``security.default_encoder`` +service to return one of the predefined encoders: + +* **security.encoder.digest**: Digest password encoder. + +* **security.encoder.bcrypt**: BCrypt password encoder. + +* **security.encoder.pbkdf2**: Pbkdf2 password encoder. + +.. code-block:: php + + $app['security.default_encoder'] = function ($app) { + return $app['security.encoder.pbkdf2']; + }; + +Or you can define you own, fully customizable encoder:: + + use Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder; + + $app['security.default_encoder'] = function ($app) { + // Plain text (e.g. for debugging) + return new PlaintextPasswordEncoder(); + }; + +.. tip:: + + You can change the default BCrypt encoding cost by overriding ``security.encoder.bcrypt.cost`` + +Defining a custom Authentication Provider +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Symfony Security component provides a lot of ready-to-use authentication +providers (form, HTTP, X509, remember me, ...), but you can add new ones +easily. To register a new authentication provider, create a service named +``security.authentication_listener.factory.XXX`` where ``XXX`` is the name you want to +use in your configuration:: + + $app['security.authentication_listener.factory.wsse'] = $app->protect(function ($name, $options) use ($app) { + // define the authentication provider object + $app['security.authentication_provider.'.$name.'.wsse'] = function () use ($app) { + return new WsseProvider($app['security.user_provider.default'], __DIR__.'/security_cache'); + }; + + // define the authentication listener object + $app['security.authentication_listener.'.$name.'.wsse'] = function () use ($app) { + return new WsseListener($app['security.token_storage'], $app['security.authentication_manager']); + }; + + return array( + // the authentication provider id + 'security.authentication_provider.'.$name.'.wsse', + // the authentication listener id + 'security.authentication_listener.'.$name.'.wsse', + // the entry point id + null, + // the position of the listener in the stack + 'pre_auth' + ); + }); + +You can now use it in your configuration like any other built-in +authentication provider:: + + $app->register(new Silex\Provider\SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'wsse' => true, + + // ... + ), + ), + )); + +Instead of ``true``, you can also define an array of options that customize +the behavior of your authentication factory; it will be passed as the second +argument of your authentication factory (see above). + +This example uses the authentication provider classes as described in the +Symfony `cookbook`_. + + +.. note:: + + The Guard component simplifies the creation of custom authentication + providers. :doc:`How to Create a Custom Authentication System with Guard + ` + +Stateless Authentication +~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, a session cookie is created to persist the security context of +the user. However, if you use certificates, HTTP authentication, WSSE and so +on, the credentials are sent for each request. In that case, you can turn off +persistence by activating the ``stateless`` authentication flag:: + + $app['security.firewalls'] = array( + 'default' => array( + 'stateless' => true, + 'wsse' => true, + + // ... + ), + ); + +Traits +------ + +``Silex\Application\SecurityTrait`` adds the following shortcuts: + +* **encodePassword**: Encode a given password. + +.. code-block:: php + + $encoded = $app->encodePassword($app['user'], 'foo'); + +``Silex\Route\SecurityTrait`` adds the following methods to the controllers: + +* **secure**: Secures a controller for the given roles. + +.. code-block:: php + + $app->get('/', function () { + // do something but only for admins + })->secure('ROLE_ADMIN'); + +.. caution:: + + The ``Silex\Route\SecurityTrait`` must be used with a user defined + ``Route`` class, not the application. + + .. code-block:: php + + use Silex\Route; + + class MyRoute extends Route + { + use Route\SecurityTrait; + } + + .. code-block:: php + + $app['route_class'] = 'MyRoute'; + + +.. _cookbook: http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html diff --git a/vendor/silex/silex/doc/providers/serializer.rst b/vendor/silex/silex/doc/providers/serializer.rst new file mode 100644 index 00000000..be5847de --- /dev/null +++ b/vendor/silex/silex/doc/providers/serializer.rst @@ -0,0 +1,85 @@ +Serializer +========== + +The *SerializerServiceProvider* provides a service for serializing objects. + +Parameters +---------- + +None. + +Services +-------- + +* **serializer**: An instance of `Symfony\\Component\\Serializer\\Serializer + `_. + +* **serializer.encoders**: `Symfony\\Component\\Serializer\\Encoder\\JsonEncoder + `_ + and `Symfony\\Component\\Serializer\\Encoder\\XmlEncoder + `_. + +* **serializer.normalizers**: `Symfony\\Component\\Serializer\\Normalizer\\CustomNormalizer + `_ + and `Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer + `_. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\SerializerServiceProvider()); + +.. note:: + + Add the Symfony's `Serializer Component + `_ as a + dependency: + + .. code-block:: bash + + composer require symfony/serializer + +Usage +----- + +The ``SerializerServiceProvider`` provider provides a ``serializer`` service:: + + use Silex\Application; + use Silex\Provider\SerializerServiceProvider; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + $app = new Application(); + + $app->register(new SerializerServiceProvider()); + + // only accept content types supported by the serializer via the assert method. + $app->get("/pages/{id}.{_format}", function (Request $request, $id) use ($app) { + // assume a page_repository service exists that returns Page objects. The + // object returned has getters and setters exposing the state. + $page = $app['page_repository']->find($id); + $format = $request->getRequestFormat(); + + if (!$page instanceof Page) { + $app->abort("No page found for id: $id"); + } + + return new Response($app['serializer']->serialize($page, $format), 200, array( + "Content-Type" => $request->getMimeType($format) + )); + })->assert("_format", "xml|json") + ->assert("id", "\d+"); + +Using a Cache +------------- + +To use a cache, register a class implementing ``Doctrine\Common\Cache\Cache``:: + + $app->register(new Silex\Provider\SerializerServiceProvider()); + $app['serializer.normalizers'] = function () use ($app) { + return [new \Symfony\Component\Serializer\Normalizer\CustomNormalizer(), + new \Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()), $app['my_custom_cache'])) + ]; + }; diff --git a/vendor/silex/silex/doc/providers/service_controller.rst b/vendor/silex/silex/doc/providers/service_controller.rst new file mode 100644 index 00000000..15bca28d --- /dev/null +++ b/vendor/silex/silex/doc/providers/service_controller.rst @@ -0,0 +1,142 @@ +Service Controllers +=================== + +As your Silex application grows, you may wish to begin organizing your +controllers in a more formal fashion. Silex can use controller classes out of +the box, but with a bit of work, your controllers can be created as services, +giving you the full power of dependency injection and lazy loading. + +.. ::todo Link above to controller classes cookbook + +Why would I want to do this? +---------------------------- + +- Dependency Injection over Service Location + + Using this method, you can inject the actual dependencies required by your + controller and gain total inversion of control, while still maintaining the + lazy loading of your controllers and its dependencies. Because your + dependencies are clearly defined, they are easily mocked, allowing you to test + your controllers in isolation. + +- Framework Independence + + Using this method, your controllers start to become more independent of the + framework you are using. Carefully crafted, your controllers will become + reusable with multiple frameworks. By keeping careful control of your + dependencies, your controllers could easily become compatible with Silex, + Symfony (full stack) and Drupal, to name just a few. + +Parameters +---------- + +There are currently no parameters for the ``ServiceControllerServiceProvider``. + +Services +-------- + +There are no extra services provided, the ``ServiceControllerServiceProvider`` +simply extends the existing **resolver** service. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\ServiceControllerServiceProvider()); + +Usage +----- + +In this slightly contrived example of a blog API, we're going to change the +``/posts.json`` route to use a controller, that is defined as a service. + +.. code-block:: php + + use Silex\Application; + use Demo\Repository\PostRepository; + + $app = new Application(); + + $app['posts.repository'] = function() { + return new PostRepository; + }; + + $app->get('/posts.json', function() use ($app) { + return $app->json($app['posts.repository']->findAll()); + }); + +Rewriting your controller as a service is pretty simple, create a Plain Ol' PHP +Object with your ``PostRepository`` as a dependency, along with an +``indexJsonAction`` method to handle the request. Although not shown in the +example below, you can use type hinting and parameter naming to get the +parameters you need, just like with standard Silex routes. + +If you are a TDD/BDD fan (and you should be), you may notice that this +controller has well defined responsibilities and dependencies, and is easily +tested/specced. You may also notice that the only external dependency is on +``Symfony\Component\HttpFoundation\JsonResponse``, meaning this controller could +easily be used in a Symfony (full stack) application, or potentially with other +applications or frameworks that know how to handle a `Symfony/HttpFoundation +`_ +``Response`` object. + +.. code-block:: php + + namespace Demo\Controller; + + use Demo\Repository\PostRepository; + use Symfony\Component\HttpFoundation\JsonResponse; + + class PostController + { + protected $repo; + + public function __construct(PostRepository $repo) + { + $this->repo = $repo; + } + + public function indexJsonAction() + { + return new JsonResponse($this->repo->findAll()); + } + } + +And lastly, define your controller as a service in the application, along with +your route. The syntax in the route definition is the name of the service, +followed by a single colon (:), followed by the method name. + +.. code-block:: php + + $app['posts.controller'] = function() use ($app) { + return new PostController($app['posts.repository']); + }; + + $app->get('/posts.json', "posts.controller:indexJsonAction"); + +In addition to using classes for service controllers, you can define any +callable as a service in the application to be used for a route. + +.. code-block:: php + + namespace Demo\Controller; + + use Demo\Repository\PostRepository; + use Symfony\Component\HttpFoundation\JsonResponse; + + function postIndexJson(PostRepository $repo) { + return function() use ($repo) { + return new JsonResponse($repo->findAll()); + }; + } + +And when defining your route, the code would look like the following: + +.. code-block:: php + + $app['posts.controller'] = function($app) { + return Demo\Controller\postIndexJson($app['posts.repository']); + }; + + $app->get('/posts.json', 'posts.controller'); diff --git a/vendor/silex/silex/doc/providers/session.rst b/vendor/silex/silex/doc/providers/session.rst new file mode 100644 index 00000000..011b69fe --- /dev/null +++ b/vendor/silex/silex/doc/providers/session.rst @@ -0,0 +1,120 @@ +Session +======= + +The *SessionServiceProvider* provides a service for storing data persistently +between requests. + +Parameters +---------- + +* **session.storage.save_path** (optional): The path for the + ``NativeFileSessionHandler``, defaults to the value of + ``sys_get_temp_dir()``. + +* **session.storage.options**: An array of options that is passed to the + constructor of the ``session.storage`` service. + + In case of the default `NativeSessionStorage + `_, + the most useful options are: + + * **name**: The cookie name (_SESS by default) + * **id**: The session id (null by default) + * **cookie_lifetime**: Cookie lifetime + * **cookie_path**: Cookie path + * **cookie_domain**: Cookie domain + * **cookie_secure**: Cookie secure (HTTPS) + * **cookie_httponly**: Whether the cookie is http only + + However, all of these are optional. Default Sessions life time is 1800 + seconds (30 minutes). To override this, set the ``lifetime`` option. + + For a full list of available options, read the `PHP + `_ official documentation. + +* **session.test**: Whether to simulate sessions or not (useful when writing + functional tests). + +Services +-------- + +* **session**: An instance of Symfony's `Session + `_. + +* **session.storage**: A service that is used for persistence of the session + data. + +* **session.storage.handler**: A service that is used by the + ``session.storage`` for data access. Defaults to a `NativeFileSessionHandler + `_ + storage handler. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\SessionServiceProvider()); + +Using Handlers +-------------- + +The default session handler is ``NativeFileSessionHandler``. However, there are +multiple handlers available for use by setting ``session.storage.handler`` to +an instance of one of the following handler objects: + +* `LegacyPdoSessionHandler `_ +* `MemcacheSessionHandler `_ +* `MemcachedSessionHandler `_ +* `MongoDbSessionHandler `_ +* `NativeFileSessionHandler `_ +* `NativeSessionHandler `_ +* `NullSessionHandler `_ +* `PdoSessionHandler `_ +* `WriteCheckSessionHandler `_ + +Usage +----- + +The Session provider provides a ``session`` service. Here is an example that +authenticates a user and creates a session for them:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + $app->get('/login', function (Request $request) use ($app) { + $username = $request->server->get('PHP_AUTH_USER', false); + $password = $request->server->get('PHP_AUTH_PW'); + + if ('igor' === $username && 'password' === $password) { + $app['session']->set('user', array('username' => $username)); + return $app->redirect('/account'); + } + + $response = new Response(); + $response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', 'site_login')); + $response->setStatusCode(401, 'Please sign in.'); + return $response; + }); + + $app->get('/account', function () use ($app) { + if (null === $user = $app['session']->get('user')) { + return $app->redirect('/login'); + } + + return "Welcome {$user['username']}!"; + }); + + +Custom Session Configurations +----------------------------- + +If your system is using a custom session configuration (such as a redis handler +from a PHP extension) then you need to disable the NativeFileSessionHandler by +setting ``session.storage.handler`` to null. You will have to configure the +``session.save_path`` ini setting yourself in that case. + +.. code-block:: php + + $app['session.storage.handler'] = null; + diff --git a/vendor/silex/silex/doc/providers/swiftmailer.rst b/vendor/silex/silex/doc/providers/swiftmailer.rst new file mode 100644 index 00000000..9297d665 --- /dev/null +++ b/vendor/silex/silex/doc/providers/swiftmailer.rst @@ -0,0 +1,156 @@ +Swiftmailer +=========== + +The *SwiftmailerServiceProvider* provides a service for sending email through +the `Swift Mailer `_ library. + +You can use the ``mailer`` service to send messages easily. By default, it +will attempt to send emails through SMTP. + +Parameters +---------- + +* **swiftmailer.use_spool**: A boolean to specify whether or not to use the + memory spool, defaults to true. + +* **swiftmailer.options**: An array of options for the default SMTP-based + configuration. + + The following options can be set: + + * **host**: SMTP hostname, defaults to 'localhost'. + * **port**: SMTP port, defaults to 25. + * **username**: SMTP username, defaults to an empty string. + * **password**: SMTP password, defaults to an empty string. + * **encryption**: SMTP encryption, defaults to null. Valid values are 'tls', 'ssl', or null (indicating no encryption). + * **auth_mode**: SMTP authentication mode, defaults to null. Valid values are 'plain', 'login', 'cram-md5', or null. + + Example usage:: + + $app['swiftmailer.options'] = array( + 'host' => 'host', + 'port' => '25', + 'username' => 'username', + 'password' => 'password', + 'encryption' => null, + 'auth_mode' => null + ); + +* **swiftmailer.sender_address**: If set, all messages will be delivered with + this address as the "return path" address. + +* **swiftmailer.delivery_addresses**: If not empty, all email messages will be + sent to those addresses instead of being sent to their actual recipients. This + is often useful when developing. + +* **swiftmailer.delivery_whitelist**: Used in combination with + ``delivery_addresses``. If set, emails matching any of these patterns will be + delivered like normal, as well as being sent to ``delivery_addresses``. + +* **swiftmailer.plugins**: Array of SwiftMailer plugins. + + Example usage:: + + $app['swiftmailer.plugins'] = function ($app) { + return array( + new \Swift_Plugins_PopBeforeSmtpPlugin('pop3.example.com'), + ); + }; + +Services +-------- + +* **mailer**: The mailer instance. + + Example usage:: + + $message = \Swift_Message::newInstance(); + + // ... + + $app['mailer']->send($message); + +* **swiftmailer.transport**: The transport used for e-mail + delivery. Defaults to a ``Swift_Transport_EsmtpTransport``. + +* **swiftmailer.transport.buffer**: StreamBuffer used by + the transport. + +* **swiftmailer.transport.authhandler**: Authentication + handler used by the transport. Will try the following + by default: CRAM-MD5, login, plaintext. + +* **swiftmailer.transport.eventdispatcher**: Internal event + dispatcher used by Swiftmailer. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\SwiftmailerServiceProvider()); + +.. note:: + + Add SwiftMailer as a dependency: + + .. code-block:: bash + + composer require swiftmailer/swiftmailer + +Usage +----- + +The Swiftmailer provider provides a ``mailer`` service:: + + use Symfony\Component\HttpFoundation\Request; + + $app->post('/feedback', function (Request $request) use ($app) { + $message = \Swift_Message::newInstance() + ->setSubject('[YourSite] Feedback') + ->setFrom(array('noreply@yoursite.com')) + ->setTo(array('feedback@yoursite.com')) + ->setBody($request->get('message')); + + $app['mailer']->send($message); + + return new Response('Thank you for your feedback!', 201); + }); + +Usage in commands +~~~~~~~~~~~~~~~~~ + +By default, the Swiftmailer provider sends the emails using the ``KernelEvents::TERMINATE`` +event, which is fired after the response has been sent. However, as this event +isn't fired for console commands, your emails won't be sent. + +For that reason, if you send emails using a command console, it is recommended +that you disable the use of the memory spool (before accessing ``$app['mailer']``):: + + $app['swiftmailer.use_spool'] = false; + +Alternatively, you can just make sure to flush the message spool by hand before +ending the command execution. To do so, use the following code:: + + $app['swiftmailer.spooltransport'] + ->getSpool() + ->flushQueue($app['swiftmailer.transport']) + ; + +Traits +------ + +``Silex\Application\SwiftmailerTrait`` adds the following shortcuts: + +* **mail**: Sends an email. + +.. code-block:: php + + $app->mail(\Swift_Message::newInstance() + ->setSubject('[YourSite] Feedback') + ->setFrom(array('noreply@yoursite.com')) + ->setTo(array('feedback@yoursite.com')) + ->setBody($request->get('message'))); + +For more information, check out the `Swift Mailer documentation +`_. diff --git a/vendor/silex/silex/doc/providers/translation.rst b/vendor/silex/silex/doc/providers/translation.rst new file mode 100644 index 00000000..145fc18c --- /dev/null +++ b/vendor/silex/silex/doc/providers/translation.rst @@ -0,0 +1,193 @@ +Translation +=========== + +The *TranslationServiceProvider* provides a service for translating your +application into different languages. + +Parameters +---------- + +* **translator.domains** (optional): A mapping of domains/locales/messages. + This parameter contains the translation data for all languages and domains. + +* **locale** (optional): The locale for the translator. You will most likely + want to set this based on some request parameter. Defaults to ``en``. + +* **locale_fallbacks** (optional): Fallback locales for the translator. It will + be used when the current locale has no messages set. Defaults to ``en``. + +Services +-------- + +* **translator**: An instance of `Translator + `_, + that is used for translation. + +* **translator.loader**: An instance of an implementation of the translation + `LoaderInterface + `_, + defaults to an `ArrayLoader + `_. + +* **translator.message_selector**: An instance of `MessageSelector + `_. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\LocaleServiceProvider()); + $app->register(new Silex\Provider\TranslationServiceProvider(), array( + 'locale_fallbacks' => array('en'), + )); + +.. note:: + + Add the Symfony Translation Component as a dependency: + + .. code-block:: bash + + composer require symfony/translation + +Usage +----- + +The Translation provider provides a ``translator`` service and makes use of +the ``translator.domains`` parameter:: + + $app['translator.domains'] = array( + 'messages' => array( + 'en' => array( + 'hello' => 'Hello %name%', + 'goodbye' => 'Goodbye %name%', + ), + 'de' => array( + 'hello' => 'Hallo %name%', + 'goodbye' => 'Tschüss %name%', + ), + 'fr' => array( + 'hello' => 'Bonjour %name%', + 'goodbye' => 'Au revoir %name%', + ), + ), + 'validators' => array( + 'fr' => array( + 'This value should be a valid number.' => 'Cette valeur doit être un nombre.', + ), + ), + ); + + $app->get('/{_locale}/{message}/{name}', function ($message, $name) use ($app) { + return $app['translator']->trans($message, array('%name%' => $name)); + }); + +The above example will result in following routes: + +* ``/en/hello/igor`` will return ``Hello igor``. + +* ``/de/hello/igor`` will return ``Hallo igor``. + +* ``/fr/hello/igor`` will return ``Bonjour igor``. + +* ``/it/hello/igor`` will return ``Hello igor`` (because of the fallback). + +Using Resources +--------------- + +When translations are stored in a file, you can load them as follows:: + + $app = new Application(); + + $app->register(new TranslationServiceProvider()); + $app->extend('translator.resources', function ($resources, $app) { + $resources = array_merge($resources, array( + array('array', array('This value should be a valid number.' => 'Cette valeur doit être un nombre.'), 'fr', 'validators'), + )); + + return $resources; + }); + +Traits +------ + +``Silex\Application\TranslationTrait`` adds the following shortcuts: + +* **trans**: Translates the given message. + +* **transChoice**: Translates the given choice message by choosing a + translation according to a number. + +.. code-block:: php + + $app->trans('Hello World'); + + $app->transChoice('Hello World'); + +Recipes +------- + +YAML-based language files +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Having your translations in PHP files can be inconvenient. This recipe will +show you how to load translations from external YAML files. + +First, add the Symfony ``Config`` and ``Yaml`` components as dependencies: + +.. code-block:: bash + + composer require symfony/config symfony/yaml + +Next, you have to create the language mappings in YAML files. A naming you can +use is ``locales/en.yml``. Just do the mapping in this file as follows: + +.. code-block:: yaml + + hello: Hello %name% + goodbye: Goodbye %name% + +Then, register the ``YamlFileLoader`` on the ``translator`` and add all your +translation files:: + + use Symfony\Component\Translation\Loader\YamlFileLoader; + + $app->extend('translator', function($translator, $app) { + $translator->addLoader('yaml', new YamlFileLoader()); + + $translator->addResource('yaml', __DIR__.'/locales/en.yml', 'en'); + $translator->addResource('yaml', __DIR__.'/locales/de.yml', 'de'); + $translator->addResource('yaml', __DIR__.'/locales/fr.yml', 'fr'); + + return $translator; + }); + +XLIFF-based language files +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Just as you would do with YAML translation files, you first need to add the +Symfony ``Config`` component as a dependency (see above for details). + +Then, similarly, create XLIFF files in your locales directory and add them to +the translator:: + + $translator->addResource('xliff', __DIR__.'/locales/en.xlf', 'en'); + $translator->addResource('xliff', __DIR__.'/locales/de.xlf', 'de'); + $translator->addResource('xliff', __DIR__.'/locales/fr.xlf', 'fr'); + +.. note:: + + The XLIFF loader is already pre-configured by the extension. + +Accessing translations in Twig templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once loaded, the translation service provider is available from within Twig +templates when using the Twig bridge provided by Symfony (see +:doc:`TwigServiceProvider `): + +.. code-block:: jinja + + {{ 'translation_key'|trans }} + {{ 'translation_key'|transchoice }} + {% trans %}translation_key{% endtrans %} diff --git a/vendor/silex/silex/doc/providers/twig.rst b/vendor/silex/silex/doc/providers/twig.rst new file mode 100644 index 00000000..b713e1a3 --- /dev/null +++ b/vendor/silex/silex/doc/providers/twig.rst @@ -0,0 +1,237 @@ +Twig +==== + +The *TwigServiceProvider* provides integration with the `Twig +`_ template engine. + +Parameters +---------- + +* **twig.path** (optional): Path to the directory containing twig template + files (it can also be an array of paths). + +* **twig.templates** (optional): An associative array of template names to + template contents. Use this if you want to define your templates inline. + +* **twig.options** (optional): An associative array of twig + options. Check out the `twig documentation `_ + for more information. + +* **twig.form.templates** (optional): An array of templates used to render + forms (only available when the ``FormServiceProvider`` is enabled). The + default theme is ``form_div_layout.html.twig``, but you can use the other + built-in themes: ``form_table_layout.html.twig``, + ``bootstrap_3_layout.html.twig``, and + ``bootstrap_3_horizontal_layout.html.twig``. + +* **twig.date.format** (optional): Default format used by the ``date`` + filter. The format string must conform to the format accepted by + `date() `_. + +* **twig.date.interval_format** (optional): Default format used by the + ``date`` filter when the filtered data is of type `DateInterval `_. + The format string must conform to the format accepted by + `DateInterval::format() `_. + +* **twig.date.timezone** (optional): Default timezone used when formatting + dates. If set to ``null`` the timezone returned by `date_default_timezone_get() `_ + is used. + +* **twig.number_format.decimals** (optional): Default number of decimals + displayed by the ``number_format`` filter. + +* **twig.number_format.decimal_point** (optional): Default separator for + the decimal point used by the ``number_format`` filter. + +* **twig.number_format.thousands_separator** (optional): Default thousands + separator used by the ``number_format`` filter. + +Services +-------- + +* **twig**: The ``Twig_Environment`` instance. The main way of + interacting with Twig. + +* **twig.loader**: The loader for Twig templates which uses the ``twig.path`` + and the ``twig.templates`` options. You can also replace the loader + completely. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\TwigServiceProvider(), array( + 'twig.path' => __DIR__.'/views', + )); + +.. note:: + + Add Twig as a dependency: + + .. code-block:: bash + + composer require twig/twig + +Usage +----- + +The Twig provider provides a ``twig`` service that can render templates:: + + $app->get('/hello/{name}', function ($name) use ($app) { + return $app['twig']->render('hello.twig', array( + 'name' => $name, + )); + }); + +Symfony Components Integration +------------------------------ + +Symfony provides a Twig bridge that provides additional integration between +some Symfony components and Twig. Add it as a dependency: + +.. code-block:: bash + + composer require symfony/twig-bridge + +When present, the ``TwigServiceProvider`` will provide you with the following +additional capabilities. + +* Access to the ``path()`` and ``url()`` functions. You can find more + information in the `Symfony Routing documentation + `_: + + .. code-block:: jinja + + {{ path('homepage') }} + {{ url('homepage') }} {# generates the absolute url http://example.org/ #} + {{ path('hello', {name: 'Fabien'}) }} + {{ url('hello', {name: 'Fabien'}) }} {# generates the absolute url http://example.org/hello/Fabien #} + +* Access to the ``absolute_url()`` and ``relative_path()`` Twig functions. + +Translations Support +~~~~~~~~~~~~~~~~~~~~ + +If you are using the ``TranslationServiceProvider``, you will get the +``trans()`` and ``transchoice()`` functions for translation in Twig templates. +You can find more information in the `Symfony Translation documentation +`_. + +Form Support +~~~~~~~~~~~~ + +If you are using the ``FormServiceProvider``, you will get a set of helpers for +working with forms in templates. You can find more information in the `Symfony +Forms reference +`_. + +Security Support +~~~~~~~~~~~~~~~~ + +If you are using the ``SecurityServiceProvider``, you will have access to the +``is_granted()`` function in templates. You can find more information in the +`Symfony Security documentation +`_. + +Web Link Support +~~~~~~~~~~~~~~~~ + +If you are using the ``symfony/web-link`` component, you will have access to the +``preload()``, ``prefetch()``, ``prerender()``, ``dns_prefetch()``, +``preconnect()`` and ``link()`` functions in templates. You can find more +information in the `Symfony WebLink documentation +`_. + +Global Variable +~~~~~~~~~~~~~~~ + +When the Twig bridge is available, the ``global`` variable refers to an +instance of `AppVariable `_. +It gives access to the following methods: + +.. code-block:: jinja + + {# The current Request #} + {{ global.request }} + + {# The current User (when security is enabled) #} + {{ global.user }} + + {# The current Session #} + {{ global.session }} + + {# The debug flag #} + {{ global.debug }} + +Rendering a Controller +~~~~~~~~~~~~~~~~~~~~~~ + +A ``render`` function is also registered to help you render another controller +from a template (available when the :doc:`HttpFragment Service Provider +` is registered): + +.. code-block:: jinja + + {{ render(url('sidebar')) }} + + {# or you can reference a controller directly without defining a route for it #} + {{ render(controller(controller)) }} + +.. note:: + + You must prepend the ``app.request.baseUrl`` to render calls to ensure + that the render works when deployed into a sub-directory of the docroot. + +.. note:: + + Read the Twig `reference`_ for Symfony document to learn more about the + various Twig functions. + +Traits +------ + +``Silex\Application\TwigTrait`` adds the following shortcuts: + +* **render**: Renders a view with the given parameters and returns a Response + object. + +.. code-block:: php + + return $app->render('index.html', ['name' => 'Fabien']); + + $response = new Response(); + $response->setTtl(10); + + return $app->render('index.html', ['name' => 'Fabien'], $response); + +.. code-block:: php + + // stream a view + use Symfony\Component\HttpFoundation\StreamedResponse; + + return $app->render('index.html', ['name' => 'Fabien'], new StreamedResponse()); + +* **renderView**: Renders a view with the given parameters and returns a string. + +.. code-block:: php + + $content = $app->renderView('index.html', ['name' => 'Fabien']); + +Customization +------------- + +You can configure the Twig environment before using it by extending the +``twig`` service:: + + $app->extend('twig', function($twig, $app) { + $twig->addGlobal('pi', 3.14); + $twig->addFilter('levenshtein', new \Twig_Filter_Function('levenshtein')); + + return $twig; + }); + +For more information, check out the `official Twig documentation +`_. + +.. _reference: https://symfony.com/doc/current/reference/twig_reference.html#controller diff --git a/vendor/silex/silex/doc/providers/validator.rst b/vendor/silex/silex/doc/providers/validator.rst new file mode 100644 index 00000000..bd4e9985 --- /dev/null +++ b/vendor/silex/silex/doc/providers/validator.rst @@ -0,0 +1,217 @@ +Validator +========= + +The *ValidatorServiceProvider* provides a service for validating data. It is +most useful when used with the *FormServiceProvider*, but can also be used +standalone. + +Parameters +---------- + +* **validator.validator_service_ids**: An array of service names representing + validators. + +Services +-------- + +* **validator**: An instance of `Validator + `_. + +* **validator.mapping.class_metadata_factory**: Factory for metadata loaders, + which can read validation constraint information from classes. Defaults to + StaticMethodLoader--ClassMetadataFactory. + + This means you can define a static ``loadValidatorMetadata`` method on your + data class, which takes a ClassMetadata argument. Then you can set + constraints on this ClassMetadata instance. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\ValidatorServiceProvider()); + +.. note:: + + Add the Symfony Validator Component as a dependency: + + .. code-block:: bash + + composer require symfony/validator + +Usage +----- + +The Validator provider provides a ``validator`` service. + +Validating Values +~~~~~~~~~~~~~~~~~ + +You can validate values directly using the ``validate`` validator +method:: + + use Symfony\Component\Validator\Constraints as Assert; + + $app->get('/validate/{email}', function ($email) use ($app) { + $errors = $app['validator']->validate($email, new Assert\Email()); + + if (count($errors) > 0) { + return (string) $errors; + } else { + return 'The email is valid'; + } + }); + +Validating Associative Arrays +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Validating associative arrays is like validating simple values, with a +collection of constraints:: + + use Symfony\Component\Validator\Constraints as Assert; + + $book = array( + 'title' => 'My Book', + 'author' => array( + 'first_name' => 'Fabien', + 'last_name' => 'Potencier', + ), + ); + + $constraint = new Assert\Collection(array( + 'title' => new Assert\Length(array('min' => 10)), + 'author' => new Assert\Collection(array( + 'first_name' => array(new Assert\NotBlank(), new Assert\Length(array('min' => 10))), + 'last_name' => new Assert\Length(array('min' => 10)), + )), + )); + $errors = $app['validator']->validate($book, $constraint); + + if (count($errors) > 0) { + foreach ($errors as $error) { + echo $error->getPropertyPath().' '.$error->getMessage()."\n"; + } + } else { + echo 'The book is valid'; + } + +Validating Objects +~~~~~~~~~~~~~~~~~~ + +If you want to add validations to a class, you can define the constraint for +the class properties and getters, and then call the ``validate`` method:: + + use Symfony\Component\Validator\Constraints as Assert; + + class Book + { + public $title; + public $author; + } + + class Author + { + public $first_name; + public $last_name; + } + + $author = new Author(); + $author->first_name = 'Fabien'; + $author->last_name = 'Potencier'; + + $book = new Book(); + $book->title = 'My Book'; + $book->author = $author; + + $metadata = $app['validator.mapping.class_metadata_factory']->getMetadataFor('Author'); + $metadata->addPropertyConstraint('first_name', new Assert\NotBlank()); + $metadata->addPropertyConstraint('first_name', new Assert\Length(array('min' => 10))); + $metadata->addPropertyConstraint('last_name', new Assert\Length(array('min' => 10))); + + $metadata = $app['validator.mapping.class_metadata_factory']->getMetadataFor('Book'); + $metadata->addPropertyConstraint('title', new Assert\Length(array('min' => 10))); + $metadata->addPropertyConstraint('author', new Assert\Valid()); + + $errors = $app['validator']->validate($book); + + if (count($errors) > 0) { + foreach ($errors as $error) { + echo $error->getPropertyPath().' '.$error->getMessage()."\n"; + } + } else { + echo 'The author is valid'; + } + +You can also declare the class constraint by adding a static +``loadValidatorMetadata`` method to your classes:: + + use Symfony\Component\Validator\Mapping\ClassMetadata; + use Symfony\Component\Validator\Constraints as Assert; + + class Book + { + public $title; + public $author; + + static public function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('title', new Assert\Length(array('min' => 10))); + $metadata->addPropertyConstraint('author', new Assert\Valid()); + } + } + + class Author + { + public $first_name; + public $last_name; + + static public function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('first_name', new Assert\NotBlank()); + $metadata->addPropertyConstraint('first_name', new Assert\Length(array('min' => 10))); + $metadata->addPropertyConstraint('last_name', new Assert\Length(array('min' => 10))); + } + } + + $app->get('/validate/{email}', function ($email) use ($app) { + $author = new Author(); + $author->first_name = 'Fabien'; + $author->last_name = 'Potencier'; + + $book = new Book(); + $book->title = 'My Book'; + $book->author = $author; + + $errors = $app['validator']->validate($book); + + if (count($errors) > 0) { + foreach ($errors as $error) { + echo $error->getPropertyPath().' '.$error->getMessage()."\n"; + } + } else { + echo 'The author is valid'; + } + }); + +.. note:: + + Use ``addGetterConstraint()`` to add constraints on getter methods and + ``addConstraint()`` to add constraints on the class itself. + +Translation +~~~~~~~~~~~ + +To be able to translate the error messages, you can use the translator +provider and register the messages under the ``validators`` domain:: + + $app['translator.domains'] = array( + 'validators' => array( + 'fr' => array( + 'This value should be a valid number.' => 'Cette valeur doit être un nombre.', + ), + ), + ); + +For more information, consult the `Symfony Validation documentation +`_. diff --git a/vendor/silex/silex/doc/providers/var_dumper.rst b/vendor/silex/silex/doc/providers/var_dumper.rst new file mode 100644 index 00000000..ea4dd19a --- /dev/null +++ b/vendor/silex/silex/doc/providers/var_dumper.rst @@ -0,0 +1,44 @@ +Var Dumper +========== + +The *VarDumperServiceProvider* provides a mechanism that allows exploring then +dumping any PHP variable. + +Parameters +---------- + +* **var_dumper.dump_destination**: A stream URL where dumps should be written + to (defaults to ``null``). + +Services +-------- + +* n/a + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\VarDumperServiceProvider()); + +.. note:: + + Add the Symfony VarDumper Component as a dependency: + + .. code-block:: bash + + composer require symfony/var-dumper + +Usage +----- + +Adding the VarDumper component as a Composer dependency gives you access to the +``dump()`` PHP function anywhere in your code. + +If you are using Twig, it also provides a ``dump()`` Twig function and a +``dump`` Twig tag. + +The VarDumperServiceProvider is also useful when used with the Silex +WebProfiler as the dumps are made available in the web debug toolbar and in the +web profiler. diff --git a/vendor/silex/silex/doc/services.rst b/vendor/silex/silex/doc/services.rst new file mode 100644 index 00000000..0b34ad35 --- /dev/null +++ b/vendor/silex/silex/doc/services.rst @@ -0,0 +1,269 @@ +Services +======== + +Silex is not only a framework, it is also a service container. It does this by +extending `Pimple `_ which provides a very simple +service container. + +Dependency Injection +-------------------- + +.. note:: + + You can skip this if you already know what Dependency Injection is. + +Dependency Injection is a design pattern where you pass dependencies to +services instead of creating them from within the service or relying on +globals. This generally leads to code that is decoupled, re-usable, flexible +and testable. + +Here is an example of a class that takes a ``User`` object and stores it as a +file in JSON format:: + + class JsonUserPersister + { + private $basePath; + + public function __construct($basePath) + { + $this->basePath = $basePath; + } + + public function persist(User $user) + { + $data = $user->getAttributes(); + $json = json_encode($data); + $filename = $this->basePath.'/'.$user->id.'.json'; + file_put_contents($filename, $json, LOCK_EX); + } + } + +In this simple example the dependency is the ``basePath`` property. It is +passed to the constructor. This means you can create several independent +instances with different base paths. Of course dependencies do not have to be +simple strings. More often they are in fact other services. + +A service container is responsible for creating and storing services. It can +recursively create dependencies of the requested services and inject them. It +does so lazily, which means a service is only created when you actually need it. + +Pimple +------ + +Pimple makes strong use of closures and implements the ArrayAccess interface. + +We will start off by creating a new instance of Pimple -- and because +``Silex\Application`` extends ``Pimple\Container`` all of this applies to Silex +as well:: + + $container = new Pimple\Container(); + +or:: + + $app = new Silex\Application(); + +Parameters +~~~~~~~~~~ + +You can set parameters (which are usually strings) by setting an array key on +the container:: + + $app['some_parameter'] = 'value'; + +The array key can be any value. By convention dots are used for namespacing:: + + $app['asset.host'] = 'http://cdn.mysite.com/'; + +Reading parameter values is possible with the same syntax:: + + echo $app['some_parameter']; + +Service definitions +~~~~~~~~~~~~~~~~~~~ + +Defining services is no different than defining parameters. You just set an +array key on the container to be a closure. However, when you retrieve the +service, the closure is executed. This allows for lazy service creation:: + + $app['some_service'] = function () { + return new Service(); + }; + +And to retrieve the service, use:: + + $service = $app['some_service']; + +On first invocation, this will create the service; the same instance will then +be returned on any subsequent access. + +Factory services +~~~~~~~~~~~~~~~~ + +If you want a different instance to be returned for each service access, wrap +the service definition with the ``factory()`` method:: + + $app['some_service'] = $app->factory(function () { + return new Service(); + }); + +Every time you call ``$app['some_service']``, a new instance of the service is +created. + +Access container from closure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In many cases you will want to access the service container from within a +service definition closure. For example when fetching services the current +service depends on. + +Because of this, the container is passed to the closure as an argument:: + + $app['some_service'] = function ($app) { + return new Service($app['some_other_service'], $app['some_service.config']); + }; + +Here you can see an example of Dependency Injection. ``some_service`` depends +on ``some_other_service`` and takes ``some_service.config`` as configuration +options. The dependency is only created when ``some_service`` is accessed, and +it is possible to replace either of the dependencies by simply overriding +those definitions. + +Going back to our initial example, here's how we could use the container +to manage its dependencies:: + + $app['user.persist_path'] = '/tmp/users'; + $app['user.persister'] = function ($app) { + return new JsonUserPersister($app['user.persist_path']); + }; + + +Protected closures +~~~~~~~~~~~~~~~~~~ + +Because the container sees closures as factories for services, it will always +execute them when reading them. + +In some cases you will however want to store a closure as a parameter, so that +you can fetch it and execute it yourself -- with your own arguments. + +This is why Pimple allows you to protect your closures from being executed, by +using the ``protect`` method:: + + $app['closure_parameter'] = $app->protect(function ($a, $b) { + return $a + $b; + }); + + // will not execute the closure + $add = $app['closure_parameter']; + + // calling it now + echo $add(2, 3); + +Note that the container is not provided as an argument to protected closures. +However, you can inject it via `use($app)`:: + + $app['closure_parameter'] = $app->protect(function ($a, $b) use ($app) { + // ... + }); + +Core services +------------- + +Silex defines a range of services. + +* **request_stack**: Controls the lifecycle of requests, an instance of + `RequestStack `_. + It gives you access to ``GET``, ``POST`` parameters and lots more! + + Example usage:: + + $id = $app['request_stack']->getCurrentRequest()->get('id'); + + A request is only available when a request is being served; you can only + access it from within a controller, an application before/after middlewares, + or an error handler. + +* **routes**: The `RouteCollection + `_ + that is used internally. You can add, modify, read routes. + +* **url_generator**: An instance of `UrlGenerator + `_, + using the `RouteCollection + `_ + that is provided through the ``routes`` service. It has a ``generate`` + method, which takes the route name as an argument, followed by an array of + route parameters. + +* **controllers**: The ``Silex\ControllerCollection`` that is used internally. + Check the :doc:`Internals chapter ` for more information. + +* **dispatcher**: The `EventDispatcher + `_ + that is used internally. It is the core of the Symfony system and is used + quite a bit by Silex. + +* **resolver**: The `ControllerResolver + `_ + that is used internally. It takes care of executing the controller with the + right arguments. + +* **kernel**: The `HttpKernel + `_ + that is used internally. The HttpKernel is the heart of Symfony, it takes a + Request as input and returns a Response as output. + +* **request_context**: The request context is a simplified representation of + the request that is used by the router and the URL generator. + +* **exception_handler**: The Exception handler is the default handler that is + used when you don't register one via the ``error()`` method or if your + handler does not return a Response. Disable it with + ``unset($app['exception_handler'])``. + +* **logger**: A `LoggerInterface `_ instance. By default, logging is + disabled as the value is set to ``null``. To enable logging you can either use + the :doc:`MonologServiceProvider ` or define your own ``logger`` service that + conforms to the PSR logger interface. + +Core traits +----------- + +* ``Silex\Application\UrlGeneratorTrait`` adds the following shortcuts: + + * **path**: Generates a path. + + * **url**: Generates an absolute URL. + + .. code-block:: php + + $app->path('homepage'); + $app->url('homepage'); + +Core parameters +--------------- + +* **request.http_port** (optional): Allows you to override the default port + for non-HTTPS URLs. If the current request is HTTP, it will always use the + current port. + + Defaults to 80. + + This parameter can be used when generating URLs. + +* **request.https_port** (optional): Allows you to override the default port + for HTTPS URLs. If the current request is HTTPS, it will always use the + current port. + + Defaults to 443. + + This parameter can be used when generating URLs. + +* **debug** (optional): Returns whether or not the application is running in + debug mode. + + Defaults to false. + +* **charset** (optional): The charset to use for Responses. + + Defaults to UTF-8. diff --git a/vendor/silex/silex/doc/testing.rst b/vendor/silex/silex/doc/testing.rst new file mode 100644 index 00000000..17f5f571 --- /dev/null +++ b/vendor/silex/silex/doc/testing.rst @@ -0,0 +1,222 @@ +Testing +======= + +Because Silex is built on top of Symfony, it is very easy to write functional +tests for your application. Functional tests are automated software tests that +ensure that your code is working correctly. They go through the user interface, +using a fake browser, and mimic the actions a user would do. + +Why +--- + +If you are not familiar with software tests, you may be wondering why you would +need this. Every time you make a change to your application, you have to test +it. This means going through all the pages and making sure they are still +working. Functional tests save you a lot of time, because they enable you to +test your application in usually under a second by running a single command. + +For more information on functional testing, unit testing, and automated +software tests in general, check out `PHPUnit +`_ and `Bulat Shakirzyanov's talk +on Clean Code `_. + +PHPUnit +------- + +`PHPUnit `_ is the de-facto +standard testing framework for PHP. It was built for writing unit tests, but it +can be used for functional tests too. You write tests by creating a new class, +that extends the ``PHPUnit_Framework_TestCase``. Your test cases are methods +prefixed with ``test``:: + + class ContactFormTest extends \PHPUnit_Framework_TestCase + { + public function testInitialPage() + { + ... + } + } + +In your test cases, you do assertions on the state of what you are testing. In +this case we are testing a contact form, so we would want to assert that the +page loaded correctly and contains our form:: + + public function testInitialPage() + { + $statusCode = ... + $pageContent = ... + + $this->assertEquals(200, $statusCode); + $this->assertContains('Contact us', $pageContent); + $this->assertContains('`_ +section of the PHPUnit documentation. + +WebTestCase +----------- + +Symfony provides a WebTestCase class that can be used to write functional +tests. The Silex version of this class is ``Silex\WebTestCase``, and you can +use it by making your test extend it:: + + use Silex\WebTestCase; + + class ContactFormTest extends WebTestCase + { + ... + } + +.. caution:: + + If you need to override the ``setUp()`` method, don't forget to call the + parent (``parent::setUp()``) to call the Silex default setup. + +.. note:: + + If you want to use the Symfony ``WebTestCase`` class you will need to + explicitly install its dependencies for your project: + + .. code-block:: bash + + composer require --dev symfony/browser-kit symfony/css-selector + +For your WebTestCase, you will have to implement a ``createApplication`` +method, which returns your application instance:: + + public function createApplication() + { + // app.php must return an Application instance + return require __DIR__.'/path/to/app.php'; + } + +Make sure you do **not** use ``require_once`` here, as this method will be +executed before every test. + +.. tip:: + + By default, the application behaves in the same way as when using it from a + browser. But when an error occurs, it is sometimes easier to get raw + exceptions instead of HTML pages. It is rather simple if you tweak the + application configuration in the ``createApplication()`` method like + follows:: + + public function createApplication() + { + $app = require __DIR__.'/path/to/app.php'; + $app['debug'] = true; + unset($app['exception_handler']); + + return $app; + } + +.. tip:: + + If your application use sessions, set ``session.test`` to ``true`` to + simulate sessions:: + + public function createApplication() + { + // ... + + $app['session.test'] = true; + + // ... + } + +The WebTestCase provides a ``createClient`` method. A client acts as a browser, +and allows you to interact with your application. Here's how it works:: + + public function testInitialPage() + { + $client = $this->createClient(); + $crawler = $client->request('GET', '/'); + + $this->assertTrue($client->getResponse()->isOk()); + $this->assertCount(1, $crawler->filter('h1:contains("Contact us")')); + $this->assertCount(1, $crawler->filter('form')); + ... + } + +There are several things going on here. You have both a ``Client`` and a +``Crawler``. + +You can also access the application through ``$this->app``. + +Client +~~~~~~ + +The client represents a browser. It holds your browsing history, cookies and +more. The ``request`` method allows you to make a request to a page on your +application. + +.. note:: + + You can find some documentation for it in `the client section of the + testing chapter of the Symfony documentation + `_. + +Crawler +~~~~~~~ + +The crawler allows you to inspect the content of a page. You can filter it +using CSS expressions and lots more. + +.. note:: + + You can find some documentation for it in `the crawler section of the testing + chapter of the Symfony documentation + `_. + +Configuration +------------- + +The suggested way to configure PHPUnit is to create a ``phpunit.xml.dist`` +file, a ``tests`` folder and your tests in +``tests/YourApp/Tests/YourTest.php``. The ``phpunit.xml.dist`` file should +look like this: + +.. code-block:: xml + + + + + + ./tests/ + + + + +Your ``tests/YourApp/Tests/YourTest.php`` should look like this:: + + namespace YourApp\Tests; + + use Silex\WebTestCase; + + class YourTest extends WebTestCase + { + public function createApplication() + { + return require __DIR__.'/../../../app.php'; + } + + public function testFooBar() + { + ... + } + } + +Now, when running ``phpunit`` on the command line, tests should run. diff --git a/vendor/silex/silex/doc/usage.rst b/vendor/silex/silex/doc/usage.rst new file mode 100644 index 00000000..724254ce --- /dev/null +++ b/vendor/silex/silex/doc/usage.rst @@ -0,0 +1,799 @@ +Usage +===== + +Installation +------------ + +If you want to get started fast, use the `Silex Skeleton`_: + +.. code-block:: bash + + composer create-project fabpot/silex-skeleton path/to/install "~2.0" + +If you want more flexibility, use Composer_ instead: + +.. code-block:: bash + + composer require silex/silex:~2.0 + +Web Server +---------- + +All examples in the documentation rely on a well-configured web server; read +the :doc:`webserver documentation` to check yours. + +Bootstrap +--------- + +To bootstrap Silex, all you need to do is require the ``vendor/autoload.php`` +file and create an instance of ``Silex\Application``. After your controller +definitions, call the ``run`` method on your application:: + + // web/index.php + require_once __DIR__.'/../vendor/autoload.php'; + + $app = new Silex\Application(); + + // ... definitions + + $app->run(); + +.. tip:: + + When developing a website, you might want to turn on the debug mode to + ease debugging:: + + $app['debug'] = true; + +.. tip:: + + If your application is hosted behind a reverse proxy at address ``$ip``, + and you want Silex to trust the ``X-Forwarded-For*`` headers, you will + need to run your application like this:: + + use Symfony\Component\HttpFoundation\Request; + + Request::setTrustedProxies(array($ip)); + $app->run(); + +Routing +------- + +In Silex you define a route and the controller that is called when that +route is matched. A route pattern consists of: + +* *Pattern*: The route pattern defines a path that points to a resource. The + pattern can include variable parts and you are able to set RegExp + requirements for them. + +* *Method*: One of the following HTTP methods: ``GET``, ``POST``, ``PUT``, + ``DELETE``, ``PATCH``, or ``OPTIONS``. This describes the interaction with + the resource. + +The controller is defined using a closure like this:: + + function () { + // ... do something + } + +The return value of the closure becomes the content of the page. + +Example GET Route +~~~~~~~~~~~~~~~~~ + +Here is an example definition of a ``GET`` route:: + + $blogPosts = array( + 1 => array( + 'date' => '2011-03-29', + 'author' => 'igorw', + 'title' => 'Using Silex', + 'body' => '...', + ), + ); + + $app->get('/blog', function () use ($blogPosts) { + $output = ''; + foreach ($blogPosts as $post) { + $output .= $post['title']; + $output .= '
'; + } + + return $output; + }); + +Visiting ``/blog`` will return a list of blog post titles. The ``use`` +statement means something different in this context. It tells the closure to +import the ``$blogPosts`` variable from the outer scope. This allows you to use +it from within the closure. + +Dynamic Routing +~~~~~~~~~~~~~~~ + +Now, you can create another controller for viewing individual blog posts:: + + $app->get('/blog/{id}', function (Silex\Application $app, $id) use ($blogPosts) { + if (!isset($blogPosts[$id])) { + $app->abort(404, "Post $id does not exist."); + } + + $post = $blogPosts[$id]; + + return "

{$post['title']}

". + "

{$post['body']}

"; + }); + +This route definition has a variable ``{id}`` part which is passed to the +closure. + +The current ``Application`` is automatically injected by Silex to the Closure +thanks to the type hinting. + +When the post does not exist, you are using ``abort()`` to stop the request +early. It actually throws an exception, which you will see how to handle later +on. + +Example POST Route +~~~~~~~~~~~~~~~~~~ + +POST routes signify the creation of a resource. An example for this is a +feedback form. You will use the ``mail`` function to send an e-mail:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + $app->post('/feedback', function (Request $request) { + $message = $request->get('message'); + mail('feedback@yoursite.com', '[YourSite] Feedback', $message); + + return new Response('Thank you for your feedback!', 201); + }); + +It is pretty straightforward. + +.. note:: + + There is a :doc:`SwiftmailerServiceProvider ` + included that you can use instead of ``mail()``. + +The current ``request`` is automatically injected by Silex to the Closure +thanks to the type hinting. It is an instance of +Request_, so you can fetch variables using the request ``get`` method. + +Instead of returning a string you are returning an instance of Response_. +This allows setting an HTTP status code, in this case it is set to +``201 Created``. + +.. note:: + + Silex always uses a ``Response`` internally, it converts strings to + responses with status code ``200``. + +Other methods +~~~~~~~~~~~~~ + +You can create controllers for most HTTP methods. Just call one of these +methods on your application: ``get``, ``post``, ``put``, ``delete``, ``patch``, ``options``:: + + $app->put('/blog/{id}', function ($id) { + // ... + }); + + $app->delete('/blog/{id}', function ($id) { + // ... + }); + + $app->patch('/blog/{id}', function ($id) { + // ... + }); + +.. tip:: + + Forms in most web browsers do not directly support the use of other HTTP + methods. To use methods other than GET and POST you can utilize a special + form field with a name of ``_method``. The form's ``method`` attribute must + be set to POST when using this field: + + .. code-block:: html + +
+ + +
+ + You need to explicitly enable this method override:: + + use Symfony\Component\HttpFoundation\Request; + + Request::enableHttpMethodParameterOverride(); + $app->run(); + +You can also call ``match``, which will match all methods. This can be +restricted via the ``method`` method:: + + $app->match('/blog', function () { + // ... + }); + + $app->match('/blog', function () { + // ... + }) + ->method('PATCH'); + + $app->match('/blog', function () { + // ... + }) + ->method('PUT|POST'); + +.. note:: + + The order in which the routes are defined is significant. The first + matching route will be used, so place more generic routes at the bottom. + +Route Variables +~~~~~~~~~~~~~~~ + +As it has been shown before you can define variable parts in a route like +this:: + + $app->get('/blog/{id}', function ($id) { + // ... + }); + +It is also possible to have more than one variable part, just make sure the +closure arguments match the names of the variable parts:: + + $app->get('/blog/{postId}/{commentId}', function ($postId, $commentId) { + // ... + }); + +While it's not recommended, you could also do this (note the switched +arguments):: + + $app->get('/blog/{postId}/{commentId}', function ($commentId, $postId) { + // ... + }); + +You can also ask for the current Request and Application objects:: + + $app->get('/blog/{id}', function (Application $app, Request $request, $id) { + // ... + }); + +.. note:: + + Note for the Application and Request objects, Silex does the injection + based on the type hinting and not on the variable name:: + + $app->get('/blog/{id}', function (Application $foo, Request $bar, $id) { + // ... + }); + +Route Variable Converters +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Before injecting the route variables into the controller, you can apply some +converters:: + + $app->get('/user/{id}', function ($id) { + // ... + })->convert('id', function ($id) { return (int) $id; }); + +This is useful when you want to convert route variables to objects as it +allows to reuse the conversion code across different controllers:: + + $userProvider = function ($id) { + return new User($id); + }; + + $app->get('/user/{user}', function (User $user) { + // ... + })->convert('user', $userProvider); + + $app->get('/user/{user}/edit', function (User $user) { + // ... + })->convert('user', $userProvider); + +The converter callback also receives the ``Request`` as its second argument:: + + $callback = function ($post, Request $request) { + return new Post($request->attributes->get('slug')); + }; + + $app->get('/blog/{id}/{slug}', function (Post $post) { + // ... + })->convert('post', $callback); + +A converter can also be defined as a service. For example, here is a user +converter based on Doctrine ObjectManager:: + + use Doctrine\Common\Persistence\ObjectManager; + use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + + class UserConverter + { + private $om; + + public function __construct(ObjectManager $om) + { + $this->om = $om; + } + + public function convert($id) + { + if (null === $user = $this->om->find('User', (int) $id)) { + throw new NotFoundHttpException(sprintf('User %d does not exist', $id)); + } + + return $user; + } + } + +The service will now be registered in the application, and the +``convert()`` method will be used as converter (using the syntax +``service_name:method_name``):: + + $app['converter.user'] = function () { + return new UserConverter(); + }; + + $app->get('/user/{user}', function (User $user) { + // ... + })->convert('user', 'converter.user:convert'); + +Requirements +~~~~~~~~~~~~ + +In some cases you may want to only match certain expressions. You can define +requirements using regular expressions by calling ``assert`` on the +``Controller`` object, which is returned by the routing methods. + +The following will make sure the ``id`` argument is a positive integer, since +``\d+`` matches any amount of digits:: + + $app->get('/blog/{id}', function ($id) { + // ... + }) + ->assert('id', '\d+'); + +You can also chain these calls:: + + $app->get('/blog/{postId}/{commentId}', function ($postId, $commentId) { + // ... + }) + ->assert('postId', '\d+') + ->assert('commentId', '\d+'); + +Conditions +~~~~~~~~~~ + +Besides restricting route matching based on the HTTP method or parameter +requirements, you can set conditions on any part of the request by calling +``when`` on the ``Controller`` object, which is returned by the routing +methods:: + + $app->get('/blog/{id}', function ($id) { + // ... + }) + ->when("request.headers.get('User-Agent') matches '/firefox/i'"); + +The ``when`` argument is a Symfony Expression_ , which means that you need to +add ``symfony/expression-language`` as a dependency of your project. + +Default Values +~~~~~~~~~~~~~~ + +You can define a default value for any route variable by calling ``value`` on +the ``Controller`` object:: + + $app->get('/{pageName}', function ($pageName) { + // ... + }) + ->value('pageName', 'index'); + +This will allow matching ``/``, in which case the ``pageName`` variable will +have the value ``index``. + +Named Routes +~~~~~~~~~~~~ + +Some providers can make use of named routes. By default Silex will generate an +internal route name for you but you can give an explicit route name by calling +``bind``:: + + $app->get('/', function () { + // ... + }) + ->bind('homepage'); + + $app->get('/blog/{id}', function ($id) { + // ... + }) + ->bind('blog_post'); + +Controllers as Classes +~~~~~~~~~~~~~~~~~~~~~~ + +Instead of anonymous functions, you can also define your controllers as +methods. By using the ``ControllerClass::methodName`` syntax, you can tell +Silex to lazily create the controller object for you:: + + $app->get('/', 'Acme\\Foo::bar'); + + use Silex\Application; + use Symfony\Component\HttpFoundation\Request; + + namespace Acme + { + class Foo + { + public function bar(Request $request, Application $app) + { + // ... + } + } + } + +This will load the ``Acme\Foo`` class on demand, create an instance and call +the ``bar`` method to get the response. You can use ``Request`` and +``Silex\Application`` type hints to get ``$request`` and ``$app`` injected. + +It is also possible to :doc:`define your controllers as services +`. + +Global Configuration +-------------------- + +If a controller setting must be applied to **all** controllers (a converter, a +middleware, a requirement, or a default value), configure it on +``$app['controllers']``, which holds all application controllers:: + + $app['controllers'] + ->value('id', '1') + ->assert('id', '\d+') + ->requireHttps() + ->method('get') + ->convert('id', function () { /* ... */ }) + ->before(function () { /* ... */ }) + ->when('request.isSecure() == true') + ; + +These settings are applied to already registered controllers and they become +the defaults for new controllers. + +.. note:: + + The global configuration does not apply to controller providers you might + mount as they have their own global configuration (read the + :doc:`dedicated chapter` for more information). + +Error Handlers +-------------- + +When an exception is thrown, error handlers allow you to display a custom +error page to the user. They can also be used to do additional things, such as +logging. + +To register an error handler, pass a closure to the ``error`` method which +takes an ``Exception`` argument and returns a response:: + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpFoundation\Request; + + $app->error(function (\Exception $e, Request $request, $code) { + return new Response('We are sorry, but something went terribly wrong.'); + }); + +You can also check for specific errors by using the ``$code`` argument, and +handle them differently:: + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpFoundation\Request; + + $app->error(function (\Exception $e, Request $request, $code) { + switch ($code) { + case 404: + $message = 'The requested page could not be found.'; + break; + default: + $message = 'We are sorry, but something went terribly wrong.'; + } + + return new Response($message); + }); + +You can restrict an error handler to only handle some Exception classes by +setting a more specific type hint for the Closure argument:: + + use Symfony\Component\HttpFoundation\Request; + + $app->error(function (\LogicException $e, Request $request, $code) { + // this handler will only handle \LogicException exceptions + // and exceptions that extend \LogicException + }); + +.. note:: + + As Silex ensures that the Response status code is set to the most + appropriate one depending on the exception, setting the status on the + response won't work. If you want to overwrite the status code, set the + ``X-Status-Code`` header:: + + return new Response('Error', 404 /* ignored */, array('X-Status-Code' => 200)); + +If you want to use a separate error handler for logging, make sure you register +it with a higher priority than response error handlers, because once a response +is returned, the following handlers are ignored. + +.. note:: + + Silex ships with a provider for Monolog_ which handles logging of errors. + Check out the *Providers* :doc:`chapter ` for details. + +.. tip:: + + Silex comes with a default error handler that displays a detailed error + message with the stack trace when **debug** is true, and a simple error + message otherwise. Error handlers registered via the ``error()`` method + always take precedence but you can keep the nice error messages when debug + is turned on like this:: + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpFoundation\Request; + + $app->error(function (\Exception $e, Request $request, $code) use ($app) { + if ($app['debug']) { + return; + } + + // ... logic to handle the error and return a Response + }); + +The error handlers are also called when you use ``abort`` to abort a request +early:: + + $app->get('/blog/{id}', function (Silex\Application $app, $id) use ($blogPosts) { + if (!isset($blogPosts[$id])) { + $app->abort(404, "Post $id does not exist."); + } + + return new Response(...); + }); + +You can convert errors to ``Exceptions``, check out the cookbook :doc:`chapter ` for details. + +View Handlers +------------- + +View Handlers allow you to intercept a controller result that is not a +``Response`` and transform it before it gets returned to the kernel. + +To register a view handler, pass a callable (or string that can be resolved to a +callable) to the ``view()`` method. The callable should accept some sort of result +from the controller:: + + $app->view(function (array $controllerResult) use ($app) { + return $app->json($controllerResult); + }); + +View Handlers also receive the ``Request`` as their second argument, +making them a good candidate for basic content negotiation:: + + $app->view(function (array $controllerResult, Request $request) use ($app) { + $acceptHeader = $request->headers->get('Accept'); + $bestFormat = $app['negotiator']->getBestFormat($acceptHeader, array('json', 'xml')); + + if ('json' === $bestFormat) { + return new JsonResponse($controllerResult); + } + + if ('xml' === $bestFormat) { + return $app['serializer.xml']->renderResponse($controllerResult); + } + + return $controllerResult; + }); + +View Handlers will be examined in the order they are added to the application +and Silex will use type hints to determine if a view handler should be used for +the current result, continuously using the return value of the last view handler +as the input for the next. + +.. note:: + + You must ensure that Silex receives a ``Response`` or a string as the result of + the last view handler (or controller) to be run. + +Redirects +--------- + +You can redirect to another page by returning a ``RedirectResponse`` response, +which you can create by calling the ``redirect`` method:: + + $app->get('/', function () use ($app) { + return $app->redirect('/hello'); + }); + +This will redirect from ``/`` to ``/hello``. + +Forwards +-------- + +When you want to delegate the rendering to another controller, without a +round-trip to the browser (as for a redirect), use an internal sub-request:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpKernel\HttpKernelInterface; + + $app->get('/', function () use ($app) { + // forward to /hello + $subRequest = Request::create('/hello', 'GET'); + + return $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + }); + +.. tip:: + + You can also generate the URI via the built-in URL generator:: + + $request = Request::create($app['url_generator']->generate('hello'), 'GET'); + +There's some more things that you need to keep in mind though. In most cases you +will want to forward some parts of the current master request to the sub-request. +That includes: Cookies, server information, session. +Read more on :doc:`how to make sub-requests `. + +JSON +---- + +If you want to return JSON data, you can use the ``json`` helper method. +Simply pass it your data, status code and headers, and it will create a JSON +response for you:: + + $app->get('/users/{id}', function ($id) use ($app) { + $user = getUser($id); + + if (!$user) { + $error = array('message' => 'The user was not found.'); + + return $app->json($error, 404); + } + + return $app->json($user); + }); + +Streaming +--------- + +It's possible to stream a response, which is important in cases when you don't +want to buffer the data being sent:: + + $app->get('/images/{file}', function ($file) use ($app) { + if (!file_exists(__DIR__.'/images/'.$file)) { + return $app->abort(404, 'The image was not found.'); + } + + $stream = function () use ($file) { + readfile($file); + }; + + return $app->stream($stream, 200, array('Content-Type' => 'image/png')); + }); + +If you need to send chunks, make sure you call ``ob_flush`` and ``flush`` +after every chunk:: + + $stream = function () { + $fh = fopen('http://www.example.com/', 'rb'); + while (!feof($fh)) { + echo fread($fh, 1024); + ob_flush(); + flush(); + } + fclose($fh); + }; + +Sending a file +-------------- + +If you want to return a file, you can use the ``sendFile`` helper method. +It eases returning files that would otherwise not be publicly available. Simply +pass it your file path, status code, headers and the content disposition and it +will create a ``BinaryFileResponse`` response for you:: + + $app->get('/files/{path}', function ($path) use ($app) { + if (!file_exists('/base/path/' . $path)) { + $app->abort(404); + } + + return $app->sendFile('/base/path/' . $path); + }); + +To further customize the response before returning it, check the API doc for +`Symfony\Component\HttpFoundation\BinaryFileResponse +`_:: + + return $app + ->sendFile('/base/path/' . $path) + ->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'pic.jpg') + ; + +Traits +------ + +Silex comes with PHP traits that define shortcut methods. + +Almost all built-in service providers have some corresponding PHP traits. To +use them, define your own Application class and include the traits you want:: + + use Silex\Application; + + class MyApplication extends Application + { + use Application\TwigTrait; + use Application\SecurityTrait; + use Application\FormTrait; + use Application\UrlGeneratorTrait; + use Application\SwiftmailerTrait; + use Application\MonologTrait; + use Application\TranslationTrait; + } + +You can also define your own Route class and use some traits:: + + use Silex\Route; + + class MyRoute extends Route + { + use Route\SecurityTrait; + } + +To use your newly defined route, override the ``$app['route_class']`` +setting:: + + $app['route_class'] = 'MyRoute'; + +Read each provider chapter to learn more about the added methods. + +Security +-------- + +Make sure to protect your application against attacks. + +Escaping +~~~~~~~~ + +When outputting any user input, make sure to escape it correctly to prevent +Cross-Site-Scripting attacks. + +* **Escaping HTML**: PHP provides the ``htmlspecialchars`` function for this. + Silex provides a shortcut ``escape`` method:: + + use Symfony\Component\HttpFoundation\Request; + + $app->get('/name', function (Request $request, Silex\Application $app) { + $name = $request->get('name'); + + return "You provided the name {$app->escape($name)}."; + }); + + If you use the Twig template engine, you should use its escaping or even + auto-escaping mechanisms. Check out the *Providers* :doc:`chapter ` for details. + +* **Escaping JSON**: If you want to provide data in JSON format you should + use the Silex ``json`` function:: + + use Symfony\Component\HttpFoundation\Request; + + $app->get('/name.json', function (Request $request, Silex\Application $app) { + $name = $request->get('name'); + + return $app->json(array('name' => $name)); + }); + +.. _Silex Skeleton: http://github.com/silexphp/Silex-Skeleton +.. _Composer: http://getcomposer.org/ +.. _Request: http://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html +.. _Response: http://api.symfony.com/master/Symfony/Component/HttpFoundation/Response.html +.. _Monolog: https://github.com/Seldaek/monolog +.. _Expression: https://symfony.com/doc/current/book/routing.html#completely-customized-route-matching-with-conditions diff --git a/vendor/silex/silex/doc/web_servers.rst b/vendor/silex/silex/doc/web_servers.rst new file mode 100644 index 00000000..c3d43aa6 --- /dev/null +++ b/vendor/silex/silex/doc/web_servers.rst @@ -0,0 +1,183 @@ +Webserver Configuration +======================= + +Apache +------ + +If you are using Apache, make sure ``mod_rewrite`` is enabled and use the +following ``.htaccess`` file: + +.. code-block:: apache + + + Options -MultiViews + + RewriteEngine On + #RewriteBase /path/to/app + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [QSA,L] + + +.. note:: + + If your site is not at the webroot level you will have to uncomment the + ``RewriteBase`` statement and adjust the path to point to your directory, + relative from the webroot. + +Alternatively, if you use Apache 2.2.16 or higher, you can use the +`FallbackResource directive`_ to make your .htaccess even easier: + +.. code-block:: apache + + FallbackResource index.php + +.. note:: + + If your site is not at the webroot level you will have to adjust the path to + point to your directory, relative from the webroot. + +Or if you're using a VirtualHost, you can add the same directive to the VirtualHost's Directory entry: + +.. code-block:: apache + + + # other directives + + + # other directives + + FallbackResource /index.php + + + +.. note:: + + Note that you need the leading forward slash there, unlike with the .htaccess version + +nginx +----- + +The **minimum configuration** to get your application running under Nginx is: + +.. code-block:: nginx + + server { + server_name domain.tld www.domain.tld; + root /var/www/project/web; + + location / { + # try to serve file directly, fallback to front controller + try_files $uri /index.php$is_args$args; + } + + # If you have 2 front controllers for dev|prod use the following line instead + # location ~ ^/(index|index_dev)\.php(/|$) { + location ~ ^/index\.php(/|$) { + # the ubuntu default + fastcgi_pass unix:/var/run/php/phpX.X-fpm.sock; + # for running on centos + #fastcgi_pass unix:/var/run/php-fpm/www.sock; + + fastcgi_split_path_info ^(.+\.php)(/.*)$; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param HTTPS off; + + # Prevents URIs that include the front controller. This will 404: + # http://domain.tld/index.php/some-path + # Enable the internal directive to disable URIs like this + # internal; + } + + #return 404 for all php files as we do have a front controller + location ~ \.php$ { + return 404; + } + + error_log /var/log/nginx/project_error.log; + access_log /var/log/nginx/project_access.log; + } + +IIS +--- + +If you are using the Internet Information Services from Windows, you can use +this sample ``web.config`` file: + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + + +Lighttpd +-------- + +If you are using lighttpd, use this sample ``simple-vhost`` as a starting +point: + +.. code-block:: lighttpd + + server.document-root = "/path/to/app" + + url.rewrite-once = ( + # configure some static files + "^/assets/.+" => "$0", + "^/favicon\.ico$" => "$0", + + "^(/[^\?]*)(\?.*)?" => "/index.php$1$2" + ) + +.. _FallbackResource directive: http://www.adayinthelifeof.nl/2012/01/21/apaches-fallbackresource-your-new-htaccess-command/ + +PHP +--- + +PHP ships with a built-in webserver for development. This server allows you to +run silex without any configuration. However, in order to serve static files, +you'll have to make sure your front controller returns false in that case:: + + // web/index.php + + $filename = __DIR__.preg_replace('#(\?.*)$#', '', $_SERVER['REQUEST_URI']); + if (php_sapi_name() === 'cli-server' && is_file($filename)) { + return false; + } + + $app = require __DIR__.'/../src/app.php'; + $app->run(); + + +Assuming your front controller is at ``web/index.php``, you can start the +server from the command-line with this command: + +.. code-block:: text + + $ php -S localhost:8080 -t web web/index.php + +Now the application should be running at ``http://localhost:8080``. + +.. note:: + + This server is for development only. It is **not** recommended to use it + in production. diff --git a/vendor/silex/silex/phpunit.xml.dist b/vendor/silex/silex/phpunit.xml.dist new file mode 100644 index 00000000..799f16c9 --- /dev/null +++ b/vendor/silex/silex/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + + + ./tests/Silex/ + + + + + ./src + + + diff --git a/vendor/silex/silex/src/Silex/Api/BootableProviderInterface.php b/vendor/silex/silex/src/Silex/Api/BootableProviderInterface.php new file mode 100644 index 00000000..739e04d5 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Api/BootableProviderInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Api; + +use Silex\Application; + +/** + * Interface for bootable service providers. + * + * @author Fabien Potencier + */ +interface BootableProviderInterface +{ + /** + * Bootstraps the application. + * + * This method is called after all services are registered + * and should be used for "dynamic" configuration (whenever + * a service must be requested). + * + * @param Application $app + */ + public function boot(Application $app); +} diff --git a/vendor/silex/silex/src/Silex/Api/ControllerProviderInterface.php b/vendor/silex/silex/src/Silex/Api/ControllerProviderInterface.php new file mode 100644 index 00000000..28d9d0e5 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Api/ControllerProviderInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Api; + +use Silex\Application; +use Silex\ControllerCollection; + +/** + * Interface for controller providers. + * + * @author Fabien Potencier + */ +interface ControllerProviderInterface +{ + /** + * Returns routes to connect to the given application. + * + * @param Application $app An Application instance + * + * @return ControllerCollection A ControllerCollection instance + */ + public function connect(Application $app); +} diff --git a/vendor/silex/silex/src/Silex/Api/EventListenerProviderInterface.php b/vendor/silex/silex/src/Silex/Api/EventListenerProviderInterface.php new file mode 100644 index 00000000..f3e62555 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Api/EventListenerProviderInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Api; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Pimple\Container; + +/** + * Interface for event listener providers. + * + * @author Fabien Potencier + */ +interface EventListenerProviderInterface +{ + public function subscribe(Container $app, EventDispatcherInterface $dispatcher); +} diff --git a/vendor/silex/silex/src/Silex/Api/LICENSE b/vendor/silex/silex/src/Silex/Api/LICENSE new file mode 100644 index 00000000..bc6ad049 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Api/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-2015 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/silex/silex/src/Silex/Api/composer.json b/vendor/silex/silex/src/Silex/Api/composer.json new file mode 100644 index 00000000..7290988a --- /dev/null +++ b/vendor/silex/silex/src/Silex/Api/composer.json @@ -0,0 +1,34 @@ +{ + "minimum-stability": "dev", + "name": "silex/api", + "description": "The Silex interfaces", + "keywords": ["microframework"], + "homepage": "http://silex.sensiolabs.org", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0" + }, + "suggest": { + "symfony/event-dispatcher": "For EventListenerProviderInterface", + "silex/silex": "For BootableProviderInterface and ControllerProviderInterface" + }, + "autoload": { + "psr-4": { "Silex\\Api\\": "" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + } +} diff --git a/vendor/silex/silex/src/Silex/AppArgumentValueResolver.php b/vendor/silex/silex/src/Silex/AppArgumentValueResolver.php new file mode 100644 index 00000000..cc2197ab --- /dev/null +++ b/vendor/silex/silex/src/Silex/AppArgumentValueResolver.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * HttpKernel Argument Resolver for Silex. + * + * @author Romain Neutron + */ +class AppArgumentValueResolver implements ArgumentValueResolverInterface +{ + private $app; + + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return null !== $argument->getType() && ($argument->getType() === Application::class || is_subclass_of($argument->getType(), Application::class)); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $this->app; + } +} diff --git a/vendor/silex/silex/src/Silex/Application.php b/vendor/silex/silex/src/Silex/Application.php new file mode 100644 index 00000000..c3b751ba --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application.php @@ -0,0 +1,506 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\TerminableInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\JsonResponse; +use Silex\Api\BootableProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Silex\Api\ControllerProviderInterface; +use Silex\Provider\ExceptionHandlerServiceProvider; +use Silex\Provider\RoutingServiceProvider; +use Silex\Provider\HttpKernelServiceProvider; + +/** + * The Silex framework class. + * + * @author Fabien Potencier + */ +class Application extends Container implements HttpKernelInterface, TerminableInterface +{ + const VERSION = '2.2.0-DEV'; + + const EARLY_EVENT = 512; + const LATE_EVENT = -512; + + protected $providers = array(); + protected $booted = false; + + /** + * Instantiate a new Application. + * + * Objects and parameters can be passed as argument to the constructor. + * + * @param array $values The parameters or objects. + */ + public function __construct(array $values = array()) + { + parent::__construct(); + + $this['request.http_port'] = 80; + $this['request.https_port'] = 443; + $this['debug'] = false; + $this['charset'] = 'UTF-8'; + $this['logger'] = null; + + $this->register(new HttpKernelServiceProvider()); + $this->register(new RoutingServiceProvider()); + $this->register(new ExceptionHandlerServiceProvider()); + + foreach ($values as $key => $value) { + $this[$key] = $value; + } + } + + /** + * Registers a service provider. + * + * @param ServiceProviderInterface $provider A ServiceProviderInterface instance + * @param array $values An array of values that customizes the provider + * + * @return Application + */ + public function register(ServiceProviderInterface $provider, array $values = array()) + { + $this->providers[] = $provider; + + parent::register($provider, $values); + + return $this; + } + + /** + * Boots all service providers. + * + * This method is automatically called by handle(), but you can use it + * to boot all service providers when not handling a request. + */ + public function boot() + { + if ($this->booted) { + return; + } + + $this->booted = true; + + foreach ($this->providers as $provider) { + if ($provider instanceof EventListenerProviderInterface) { + $provider->subscribe($this, $this['dispatcher']); + } + + if ($provider instanceof BootableProviderInterface) { + $provider->boot($this); + } + } + } + + /** + * Maps a pattern to a callable. + * + * You can optionally specify HTTP methods that should be matched. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function match($pattern, $to = null) + { + return $this['controllers']->match($pattern, $to); + } + + /** + * Maps a GET request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function get($pattern, $to = null) + { + return $this['controllers']->get($pattern, $to); + } + + /** + * Maps a POST request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function post($pattern, $to = null) + { + return $this['controllers']->post($pattern, $to); + } + + /** + * Maps a PUT request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function put($pattern, $to = null) + { + return $this['controllers']->put($pattern, $to); + } + + /** + * Maps a DELETE request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function delete($pattern, $to = null) + { + return $this['controllers']->delete($pattern, $to); + } + + /** + * Maps an OPTIONS request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function options($pattern, $to = null) + { + return $this['controllers']->options($pattern, $to); + } + + /** + * Maps a PATCH request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function patch($pattern, $to = null) + { + return $this['controllers']->patch($pattern, $to); + } + + /** + * Adds an event listener that listens on the specified events. + * + * @param string $eventName The event to listen on + * @param callable $callback 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 on($eventName, $callback, $priority = 0) + { + if ($this->booted) { + $this['dispatcher']->addListener($eventName, $this['callback_resolver']->resolveCallback($callback), $priority); + + return; + } + + $this->extend('dispatcher', function (EventDispatcherInterface $dispatcher, $app) use ($callback, $priority, $eventName) { + $dispatcher->addListener($eventName, $app['callback_resolver']->resolveCallback($callback), $priority); + + return $dispatcher; + }); + } + + /** + * Registers a before filter. + * + * Before filters are run before any route has been matched. + * + * @param mixed $callback Before filter callback + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function before($callback, $priority = 0) + { + $app = $this; + + $this->on(KernelEvents::REQUEST, function (GetResponseEvent $event) use ($callback, $app) { + if (!$event->isMasterRequest()) { + return; + } + + $ret = call_user_func($app['callback_resolver']->resolveCallback($callback), $event->getRequest(), $app); + + if ($ret instanceof Response) { + $event->setResponse($ret); + } + }, $priority); + } + + /** + * Registers an after filter. + * + * After filters are run after the controller has been executed. + * + * @param mixed $callback After filter callback + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function after($callback, $priority = 0) + { + $app = $this; + + $this->on(KernelEvents::RESPONSE, function (FilterResponseEvent $event) use ($callback, $app) { + if (!$event->isMasterRequest()) { + return; + } + + $response = call_user_func($app['callback_resolver']->resolveCallback($callback), $event->getRequest(), $event->getResponse(), $app); + if ($response instanceof Response) { + $event->setResponse($response); + } elseif (null !== $response) { + throw new \RuntimeException('An after middleware returned an invalid response value. Must return null or an instance of Response.'); + } + }, $priority); + } + + /** + * Registers a finish filter. + * + * Finish filters are run after the response has been sent. + * + * @param mixed $callback Finish filter callback + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function finish($callback, $priority = 0) + { + $app = $this; + + $this->on(KernelEvents::TERMINATE, function (PostResponseEvent $event) use ($callback, $app) { + call_user_func($app['callback_resolver']->resolveCallback($callback), $event->getRequest(), $event->getResponse(), $app); + }, $priority); + } + + /** + * Aborts the current request by sending a proper HTTP error. + * + * @param int $statusCode The HTTP status code + * @param string $message The status message + * @param array $headers An array of HTTP headers + */ + public function abort($statusCode, $message = '', array $headers = array()) + { + throw new HttpException($statusCode, $message, null, $headers); + } + + /** + * Registers an error handler. + * + * Error handlers are simple callables which take a single Exception + * as an argument. If a controller throws an exception, an error handler + * can return a specific response. + * + * When an exception occurs, all handlers will be called, until one returns + * something (a string or a Response object), at which point that will be + * returned to the client. + * + * For this reason you should add logging handlers before output handlers. + * + * @param mixed $callback Error handler callback, takes an Exception argument + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to -8) + */ + public function error($callback, $priority = -8) + { + $this->on(KernelEvents::EXCEPTION, new ExceptionListenerWrapper($this, $callback), $priority); + } + + /** + * Registers a view handler. + * + * View handlers are simple callables which take a controller result and the + * request as arguments, whenever a controller returns a value that is not + * an instance of Response. When this occurs, all suitable handlers will be + * called, until one returns a Response object. + * + * @param mixed $callback View handler callback + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function view($callback, $priority = 0) + { + $this->on(KernelEvents::VIEW, new ViewListenerWrapper($this, $callback), $priority); + } + + /** + * Flushes the controller collection. + */ + public function flush() + { + $this['routes']->addCollection($this['controllers']->flush()); + } + + /** + * Redirects the user to another URL. + * + * @param string $url The URL to redirect to + * @param int $status The status code (302 by default) + * + * @return RedirectResponse + */ + public function redirect($url, $status = 302) + { + return new RedirectResponse($url, $status); + } + + /** + * Creates a streaming response. + * + * @param mixed $callback A valid PHP callback + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return StreamedResponse + */ + public function stream($callback = null, $status = 200, array $headers = array()) + { + return new StreamedResponse($callback, $status, $headers); + } + + /** + * Escapes a text for HTML. + * + * @param string $text The input text to be escaped + * @param int $flags The flags (@see htmlspecialchars) + * @param string $charset The charset + * @param bool $doubleEncode Whether to try to avoid double escaping or not + * + * @return string Escaped text + */ + public function escape($text, $flags = ENT_COMPAT, $charset = null, $doubleEncode = true) + { + return htmlspecialchars($text, $flags, $charset ?: $this['charset'], $doubleEncode); + } + + /** + * Convert some data into a JSON response. + * + * @param mixed $data The response data + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return JsonResponse + */ + public function json($data = array(), $status = 200, array $headers = array()) + { + return new JsonResponse($data, $status, $headers); + } + + /** + * Sends a file. + * + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code + * @param array $headers An array of response headers + * @param null|string $contentDisposition The type of Content-Disposition to set automatically with the filename + * + * @return BinaryFileResponse + */ + public function sendFile($file, $status = 200, array $headers = array(), $contentDisposition = null) + { + return new BinaryFileResponse($file, $status, $headers, true, $contentDisposition); + } + + /** + * Mounts controllers under the given route prefix. + * + * @param string $prefix The route prefix + * @param ControllerCollection|callable|ControllerProviderInterface $controllers A ControllerCollection, a callable, or a ControllerProviderInterface instance + * + * @return Application + * + * @throws \LogicException + */ + public function mount($prefix, $controllers) + { + if ($controllers instanceof ControllerProviderInterface) { + $connectedControllers = $controllers->connect($this); + + if (!$connectedControllers instanceof ControllerCollection) { + throw new \LogicException(sprintf('The method "%s::connect" must return a "ControllerCollection" instance. Got: "%s"', get_class($controllers), is_object($connectedControllers) ? get_class($connectedControllers) : gettype($connectedControllers))); + } + + $controllers = $connectedControllers; + } elseif (!$controllers instanceof ControllerCollection && !is_callable($controllers)) { + throw new \LogicException('The "mount" method takes either a "ControllerCollection" instance, "ControllerProviderInterface" instance, or a callable.'); + } + + $this['controllers']->mount($prefix, $controllers); + + return $this; + } + + /** + * Handles the request and delivers the response. + * + * @param Request|null $request Request to process + */ + public function run(Request $request = null) + { + if (null === $request) { + $request = Request::createFromGlobals(); + } + + $response = $this->handle($request); + $response->send(); + $this->terminate($request, $response); + } + + /** + * {@inheritdoc} + * + * If you call this method directly instead of run(), you must call the + * terminate() method yourself if you want the finish filters to be run. + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + if (!$this->booted) { + $this->boot(); + } + + $this->flush(); + + return $this['kernel']->handle($request, $type, $catch); + } + + /** + * {@inheritdoc} + */ + public function terminate(Request $request, Response $response) + { + $this['kernel']->terminate($request, $response); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/FormTrait.php b/vendor/silex/silex/src/Silex/Application/FormTrait.php new file mode 100644 index 00000000..2eeb23e4 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/FormTrait.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Application; + +use Symfony\Component\Form; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\FormBuilder; +use Symfony\Component\OptionsResolver\OptionsResolver\FormTypeInterface; + +/** + * Form trait. + * + * @author Fabien Potencier + * @author David Berlioz + */ +trait FormTrait +{ + /** + * Creates and returns a form builder instance. + * + * @param mixed $data The initial data for the form + * @param array $options Options for the form + * @param string|FormTypeInterface $type Type of the form + * + * @return FormBuilder + */ + public function form($data = null, array $options = array(), $type = null) + { + return $this['form.factory']->createBuilder($type ?: FormType::class, $data, $options); + } + + /** + * Creates and returns a named form builder instance. + * + * @param string $name + * @param mixed $data The initial data for the form + * @param array $options Options for the form + * @param string|FormTypeInterface $type Type of the form + * + * @return FormBuilder + */ + public function namedForm($name, $data = null, array $options = array(), $type = null) + { + return $this['form.factory']->createNamedBuilder($name, $type ?: FormType::class, $data, $options); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/MonologTrait.php b/vendor/silex/silex/src/Silex/Application/MonologTrait.php new file mode 100644 index 00000000..18cb54c6 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/MonologTrait.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Application; + +use Monolog\Logger; + +/** + * Monolog trait. + * + * @author Fabien Potencier + */ +trait MonologTrait +{ + /** + * Adds a log record. + * + * @param string $message The log message + * @param array $context The log context + * @param int $level The logging level + * + * @return bool Whether the record has been processed + */ + public function log($message, array $context = array(), $level = Logger::INFO) + { + return $this['monolog']->addRecord($level, $message, $context); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/SecurityTrait.php b/vendor/silex/silex/src/Silex/Application/SecurityTrait.php new file mode 100644 index 00000000..43ce5552 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/SecurityTrait.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Application; + +use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Security trait. + * + * @author Fabien Potencier + */ +trait SecurityTrait +{ + /** + * Encodes the raw password. + * + * @param UserInterface $user A UserInterface instance + * @param string $password The password to encode + * + * @return string The encoded password + * + * @throws \RuntimeException when no password encoder could be found for the user + */ + public function encodePassword(UserInterface $user, $password) + { + return $this['security.encoder_factory']->getEncoder($user)->encodePassword($password, $user->getSalt()); + } + + /** + * Checks if the attributes are granted against the current authentication token and optionally supplied object. + * + * @param mixed $attributes + * @param mixed $object + * + * @return bool + * + * @throws AuthenticationCredentialsNotFoundException when the token storage has no authentication token. + */ + public function isGranted($attributes, $object = null) + { + return $this['security.authorization_checker']->isGranted($attributes, $object); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/SwiftmailerTrait.php b/vendor/silex/silex/src/Silex/Application/SwiftmailerTrait.php new file mode 100644 index 00000000..157f94d8 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/SwiftmailerTrait.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Application; + +/** + * Swiftmailer trait. + * + * @author Fabien Potencier + */ +trait SwiftmailerTrait +{ + /** + * Sends an email. + * + * @param \Swift_Message $message A \Swift_Message instance + * @param array $failedRecipients An array of failures by-reference + * + * @return int The number of sent messages + */ + public function mail(\Swift_Message $message, &$failedRecipients = null) + { + return $this['mailer']->send($message, $failedRecipients); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/TranslationTrait.php b/vendor/silex/silex/src/Silex/Application/TranslationTrait.php new file mode 100644 index 00000000..8b6e818e --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/TranslationTrait.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Application; + +/** + * Translation trait. + * + * @author Fabien Potencier + */ +trait TranslationTrait +{ + /** + * Translates the given message. + * + * @param string $id The message id + * @param array $parameters An array of parameters for the message + * @param string $domain The domain for the message + * @param string $locale The locale + * + * @return string The translated string + */ + public function trans($id, array $parameters = array(), $domain = 'messages', $locale = null) + { + return $this['translator']->trans($id, $parameters, $domain, $locale); + } + + /** + * Translates the given choice message by choosing a translation according to a number. + * + * @param string $id The message id + * @param int $number The number to use to find the indice of the message + * @param array $parameters An array of parameters for the message + * @param string $domain The domain for the message + * @param string $locale The locale + * + * @return string The translated string + */ + public function transChoice($id, $number, array $parameters = array(), $domain = 'messages', $locale = null) + { + return $this['translator']->transChoice($id, $number, $parameters, $domain, $locale); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/TwigTrait.php b/vendor/silex/silex/src/Silex/Application/TwigTrait.php new file mode 100644 index 00000000..cb4127d7 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/TwigTrait.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Application; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * Twig trait. + * + * @author Fabien Potencier + */ +trait TwigTrait +{ + /** + * Renders a view and returns a Response. + * + * To stream a view, pass an instance of StreamedResponse as a third argument. + * + * @param string $view The view name + * @param array $parameters An array of parameters to pass to the view + * @param Response $response A Response instance + * + * @return Response A Response instance + */ + public function render($view, array $parameters = array(), Response $response = null) + { + $twig = $this['twig']; + + if ($response instanceof StreamedResponse) { + $response->setCallback(function () use ($twig, $view, $parameters) { + $twig->display($view, $parameters); + }); + } else { + if (null === $response) { + $response = new Response(); + } + $response->setContent($twig->render($view, $parameters)); + } + + return $response; + } + + /** + * Renders a view. + * + * @param string $view The view name + * @param array $parameters An array of parameters to pass to the view + * + * @return string The rendered view + */ + public function renderView($view, array $parameters = array()) + { + return $this['twig']->render($view, $parameters); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/UrlGeneratorTrait.php b/vendor/silex/silex/src/Silex/Application/UrlGeneratorTrait.php new file mode 100644 index 00000000..7ccdf8ac --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/UrlGeneratorTrait.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Application; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * UrlGenerator trait. + * + * @author Fabien Potencier + */ +trait UrlGeneratorTrait +{ + /** + * Generates a path from the given parameters. + * + * @param string $route The name of the route + * @param mixed $parameters An array of parameters + * + * @return string The generated path + */ + public function path($route, $parameters = array()) + { + return $this['url_generator']->generate($route, $parameters, UrlGeneratorInterface::ABSOLUTE_PATH); + } + + /** + * Generates an absolute URL from the given parameters. + * + * @param string $route The name of the route + * @param mixed $parameters An array of parameters + * + * @return string The generated URL + */ + public function url($route, $parameters = array()) + { + return $this['url_generator']->generate($route, $parameters, UrlGeneratorInterface::ABSOLUTE_URL); + } +} diff --git a/vendor/silex/silex/src/Silex/CallbackResolver.php b/vendor/silex/silex/src/Silex/CallbackResolver.php new file mode 100644 index 00000000..692901c2 --- /dev/null +++ b/vendor/silex/silex/src/Silex/CallbackResolver.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Pimple\Container; + +class CallbackResolver +{ + const SERVICE_PATTERN = "/[A-Za-z0-9\._\-]+:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/"; + + private $app; + + public function __construct(Container $app) + { + $this->app = $app; + } + + /** + * Returns true if the string is a valid service method representation. + * + * @param string $name + * + * @return bool + */ + public function isValid($name) + { + return is_string($name) && (preg_match(static::SERVICE_PATTERN, $name) || isset($this->app[$name])); + } + + /** + * Returns a callable given its string representation. + * + * @param string $name + * + * @return callable + * + * @throws \InvalidArgumentException In case the method does not exist. + */ + public function convertCallback($name) + { + if (preg_match(static::SERVICE_PATTERN, $name)) { + list($service, $method) = explode(':', $name, 2); + $callback = array($this->app[$service], $method); + } else { + $service = $name; + $callback = $this->app[$name]; + } + + if (!is_callable($callback)) { + throw new \InvalidArgumentException(sprintf('Service "%s" is not callable.', $service)); + } + + return $callback; + } + + /** + * Returns a callable given its string representation if it is a valid service method. + * + * @param string $name + * + * @return string|callable A callable value or the string passed in + * + * @throws \InvalidArgumentException In case the method does not exist. + */ + public function resolveCallback($name) + { + return $this->isValid($name) ? $this->convertCallback($name) : $name; + } +} diff --git a/vendor/silex/silex/src/Silex/Controller.php b/vendor/silex/silex/src/Silex/Controller.php new file mode 100644 index 00000000..9a807559 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Controller.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Silex\Exception\ControllerFrozenException; + +/** + * A wrapper for a controller, mapped to a route. + * + * __call() forwards method-calls to Route, but returns instance of Controller + * listing Route's methods below, so that IDEs know they are valid + * + * @method Controller assert(string $variable, string $regexp) + * @method Controller value(string $variable, mixed $default) + * @method Controller convert(string $variable, mixed $callback) + * @method Controller method(string $method) + * @method Controller requireHttp() + * @method Controller requireHttps() + * @method Controller before(mixed $callback) + * @method Controller after(mixed $callback) + * @method Controller when(string $condition) + * + * @author Igor Wiedler + */ +class Controller +{ + private $route; + private $routeName; + private $isFrozen = false; + + /** + * Constructor. + * + * @param Route $route + */ + public function __construct(Route $route) + { + $this->route = $route; + } + + /** + * Gets the controller's route. + * + * @return Route + */ + public function getRoute() + { + return $this->route; + } + + /** + * Gets the controller's route name. + * + * @return string + */ + public function getRouteName() + { + return $this->routeName; + } + + /** + * Sets the controller's route. + * + * @param string $routeName + * + * @return Controller $this The current Controller instance + */ + public function bind($routeName) + { + if ($this->isFrozen) { + throw new ControllerFrozenException(sprintf('Calling %s on frozen %s instance.', __METHOD__, __CLASS__)); + } + + $this->routeName = $routeName; + + return $this; + } + + public function __call($method, $arguments) + { + if (!method_exists($this->route, $method)) { + throw new \BadMethodCallException(sprintf('Method "%s::%s" does not exist.', get_class($this->route), $method)); + } + + call_user_func_array(array($this->route, $method), $arguments); + + return $this; + } + + /** + * Freezes the controller. + * + * Once the controller is frozen, you can no longer change the route name + */ + public function freeze() + { + $this->isFrozen = true; + } + + public function generateRouteName($prefix) + { + $methods = implode('_', $this->route->getMethods()).'_'; + + $routeName = $methods.$prefix.$this->route->getPath(); + $routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName); + $routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName); + + // Collapse consecutive underscores down into a single underscore. + $routeName = preg_replace('/_+/', '_', $routeName); + + return $routeName; + } +} diff --git a/vendor/silex/silex/src/Silex/ControllerCollection.php b/vendor/silex/silex/src/Silex/ControllerCollection.php new file mode 100644 index 00000000..40368964 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ControllerCollection.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\HttpFoundation\Request; + +/** + * Builds Silex controllers. + * + * It acts as a staging area for routes. You are able to set the route name + * until flush() is called, at which point all controllers are frozen and + * converted to a RouteCollection. + * + * __call() forwards method-calls to Route, but returns instance of ControllerCollection + * listing Route's methods below, so that IDEs know they are valid + * + * @method ControllerCollection assert(string $variable, string $regexp) + * @method ControllerCollection value(string $variable, mixed $default) + * @method ControllerCollection convert(string $variable, mixed $callback) + * @method ControllerCollection method(string $method) + * @method ControllerCollection requireHttp() + * @method ControllerCollection requireHttps() + * @method ControllerCollection before(mixed $callback) + * @method ControllerCollection after(mixed $callback) + * @method ControllerCollection when(string $condition) + * + * @author Igor Wiedler + * @author Fabien Potencier + */ +class ControllerCollection +{ + protected $controllers = array(); + protected $defaultRoute; + protected $defaultController; + protected $prefix; + protected $routesFactory; + protected $controllersFactory; + + public function __construct(Route $defaultRoute, RouteCollection $routesFactory = null, $controllersFactory = null) + { + $this->defaultRoute = $defaultRoute; + $this->routesFactory = $routesFactory; + $this->controllersFactory = $controllersFactory; + $this->defaultController = function (Request $request) { + throw new \LogicException(sprintf('The "%s" route must have code to run when it matches.', $request->attributes->get('_route'))); + }; + } + + /** + * Mounts controllers under the given route prefix. + * + * @param string $prefix The route prefix + * @param ControllerCollection|callable $controllers A ControllerCollection instance or a callable for defining routes + * + * @throws \LogicException + */ + public function mount($prefix, $controllers) + { + if (is_callable($controllers)) { + $collection = $this->controllersFactory ? call_user_func($this->controllersFactory) : new static(new Route(), new RouteCollection()); + call_user_func($controllers, $collection); + $controllers = $collection; + } elseif (!$controllers instanceof self) { + throw new \LogicException('The "mount" method takes either a "ControllerCollection" instance or callable.'); + } + + $controllers->prefix = $prefix; + + $this->controllers[] = $controllers; + } + + /** + * Maps a pattern to a callable. + * + * You can optionally specify HTTP methods that should be matched. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function match($pattern, $to = null) + { + $route = clone $this->defaultRoute; + $route->setPath($pattern); + $this->controllers[] = $controller = new Controller($route); + $route->setDefault('_controller', null === $to ? $this->defaultController : $to); + + return $controller; + } + + /** + * Maps a GET request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function get($pattern, $to = null) + { + return $this->match($pattern, $to)->method('GET'); + } + + /** + * Maps a POST request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function post($pattern, $to = null) + { + return $this->match($pattern, $to)->method('POST'); + } + + /** + * Maps a PUT request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function put($pattern, $to = null) + { + return $this->match($pattern, $to)->method('PUT'); + } + + /** + * Maps a DELETE request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function delete($pattern, $to = null) + { + return $this->match($pattern, $to)->method('DELETE'); + } + + /** + * Maps an OPTIONS request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function options($pattern, $to = null) + { + return $this->match($pattern, $to)->method('OPTIONS'); + } + + /** + * Maps a PATCH request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function patch($pattern, $to = null) + { + return $this->match($pattern, $to)->method('PATCH'); + } + + public function __call($method, $arguments) + { + if (!method_exists($this->defaultRoute, $method)) { + throw new \BadMethodCallException(sprintf('Method "%s::%s" does not exist.', get_class($this->defaultRoute), $method)); + } + + call_user_func_array(array($this->defaultRoute, $method), $arguments); + + foreach ($this->controllers as $controller) { + call_user_func_array(array($controller, $method), $arguments); + } + + return $this; + } + + /** + * Persists and freezes staged controllers. + * + * @return RouteCollection A RouteCollection instance + */ + public function flush() + { + if (null === $this->routesFactory) { + $routes = new RouteCollection(); + } else { + $routes = $this->routesFactory; + } + + return $this->doFlush('', $routes); + } + + private function doFlush($prefix, RouteCollection $routes) + { + if ($prefix !== '') { + $prefix = '/'.trim(trim($prefix), '/'); + } + + foreach ($this->controllers as $controller) { + if ($controller instanceof Controller) { + $controller->getRoute()->setPath($prefix.$controller->getRoute()->getPath()); + if (!$name = $controller->getRouteName()) { + $name = $base = $controller->generateRouteName(''); + $i = 0; + while ($routes->get($name)) { + $name = $base.'_'.++$i; + } + $controller->bind($name); + } + $routes->add($name, $controller->getRoute()); + $controller->freeze(); + } else { + $controller->doFlush($prefix.$controller->prefix, $routes); + } + } + + $this->controllers = array(); + + return $routes; + } +} diff --git a/vendor/silex/silex/src/Silex/ControllerResolver.php b/vendor/silex/silex/src/Silex/ControllerResolver.php new file mode 100644 index 00000000..0a95e15f --- /dev/null +++ b/vendor/silex/silex/src/Silex/ControllerResolver.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpKernel\Controller\ControllerResolver as BaseControllerResolver; +use Symfony\Component\HttpFoundation\Request; + +/** + * Adds Application as a valid argument for controllers. + * + * @author Fabien Potencier + * + * @deprecated This class can be dropped once Symfony 3.0 is not supported anymore. + */ +class ControllerResolver extends BaseControllerResolver +{ + protected $app; + + /** + * Constructor. + * + * @param Application $app An Application instance + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(Application $app, LoggerInterface $logger = null) + { + $this->app = $app; + + parent::__construct($logger); + } + + protected function doGetArguments(Request $request, $controller, array $parameters) + { + foreach ($parameters as $param) { + if ($param->getClass() && $param->getClass()->isInstance($this->app)) { + $request->attributes->set($param->getName(), $this->app); + + break; + } + } + + return parent::doGetArguments($request, $controller, $parameters); + } +} diff --git a/vendor/silex/silex/src/Silex/EventListener/ConverterListener.php b/vendor/silex/silex/src/Silex/EventListener/ConverterListener.php new file mode 100644 index 00000000..2fa93c19 --- /dev/null +++ b/vendor/silex/silex/src/Silex/EventListener/ConverterListener.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\EventListener; + +use Silex\CallbackResolver; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Routing\RouteCollection; + +/** + * Handles converters. + * + * @author Fabien Potencier + */ +class ConverterListener implements EventSubscriberInterface +{ + protected $routes; + protected $callbackResolver; + + /** + * Constructor. + * + * @param RouteCollection $routes A RouteCollection instance + * @param CallbackResolver $callbackResolver A CallbackResolver instance + */ + public function __construct(RouteCollection $routes, CallbackResolver $callbackResolver) + { + $this->routes = $routes; + $this->callbackResolver = $callbackResolver; + } + + /** + * Handles converters. + * + * @param FilterControllerEvent $event The event to handle + */ + public function onKernelController(FilterControllerEvent $event) + { + $request = $event->getRequest(); + $route = $this->routes->get($request->attributes->get('_route')); + if ($route && $converters = $route->getOption('_converters')) { + foreach ($converters as $name => $callback) { + $callback = $this->callbackResolver->resolveCallback($callback); + + $request->attributes->set($name, call_user_func($callback, $request->attributes->get($name), $request)); + } + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::CONTROLLER => 'onKernelController', + ); + } +} diff --git a/vendor/silex/silex/src/Silex/EventListener/LogListener.php b/vendor/silex/silex/src/Silex/EventListener/LogListener.php new file mode 100644 index 00000000..5f3cc904 --- /dev/null +++ b/vendor/silex/silex/src/Silex/EventListener/LogListener.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\EventListener; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; + +/** + * Logs request, response, and exceptions. + */ +class LogListener implements EventSubscriberInterface +{ + protected $logger; + protected $exceptionLogFilter; + + public function __construct(LoggerInterface $logger, $exceptionLogFilter = null) + { + $this->logger = $logger; + if (null === $exceptionLogFilter) { + $exceptionLogFilter = function (\Exception $e) { + if ($e instanceof HttpExceptionInterface && $e->getStatusCode() < 500) { + return LogLevel::ERROR; + } + + return LogLevel::CRITICAL; + }; + } + + $this->exceptionLogFilter = $exceptionLogFilter; + } + + /** + * Logs master requests on event KernelEvents::REQUEST. + * + * @param GetResponseEvent $event + */ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $this->logRequest($event->getRequest()); + } + + /** + * Logs master response on event KernelEvents::RESPONSE. + * + * @param FilterResponseEvent $event + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $this->logResponse($event->getResponse()); + } + + /** + * Logs uncaught exceptions on event KernelEvents::EXCEPTION. + * + * @param GetResponseForExceptionEvent $event + */ + public function onKernelException(GetResponseForExceptionEvent $event) + { + $this->logException($event->getException()); + } + + /** + * Logs a request. + * + * @param Request $request + */ + protected function logRequest(Request $request) + { + $this->logger->log(LogLevel::DEBUG, '> '.$request->getMethod().' '.$request->getRequestUri()); + } + + /** + * Logs a response. + * + * @param Response $response + */ + protected function logResponse(Response $response) + { + $message = '< '.$response->getStatusCode(); + + if ($response instanceof RedirectResponse) { + $message .= ' '.$response->getTargetUrl(); + } + + $this->logger->log(LogLevel::DEBUG, $message); + } + + /** + * Logs an exception. + */ + protected function logException(\Exception $e) + { + $this->logger->log(call_user_func($this->exceptionLogFilter, $e), sprintf('%s: %s (uncaught exception) at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()), array('exception' => $e)); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array('onKernelRequest', 0), + KernelEvents::RESPONSE => array('onKernelResponse', 0), + /* + * Priority -4 is used to come after those from SecurityServiceProvider (0) + * but before the error handlers added with Silex\Application::error (defaults to -8) + */ + KernelEvents::EXCEPTION => array('onKernelException', -4), + ); + } +} diff --git a/vendor/silex/silex/src/Silex/EventListener/MiddlewareListener.php b/vendor/silex/silex/src/Silex/EventListener/MiddlewareListener.php new file mode 100644 index 00000000..9b28ff1a --- /dev/null +++ b/vendor/silex/silex/src/Silex/EventListener/MiddlewareListener.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\EventListener; + +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Silex\Application; + +/** + * Manages the route middlewares. + * + * @author Fabien Potencier + */ +class MiddlewareListener implements EventSubscriberInterface +{ + protected $app; + + /** + * Constructor. + * + * @param Application $app An Application instance + */ + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * Runs before filters. + * + * @param GetResponseEvent $event The event to handle + */ + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + $routeName = $request->attributes->get('_route'); + if (!$route = $this->app['routes']->get($routeName)) { + return; + } + + foreach ((array) $route->getOption('_before_middlewares') as $callback) { + $ret = call_user_func($this->app['callback_resolver']->resolveCallback($callback), $request, $this->app); + if ($ret instanceof Response) { + $event->setResponse($ret); + + return; + } elseif (null !== $ret) { + throw new \RuntimeException(sprintf('A before middleware for route "%s" returned an invalid response value. Must return null or an instance of Response.', $routeName)); + } + } + } + + /** + * Runs after filters. + * + * @param FilterResponseEvent $event The event to handle + */ + public function onKernelResponse(FilterResponseEvent $event) + { + $request = $event->getRequest(); + $routeName = $request->attributes->get('_route'); + if (!$route = $this->app['routes']->get($routeName)) { + return; + } + + foreach ((array) $route->getOption('_after_middlewares') as $callback) { + $response = call_user_func($this->app['callback_resolver']->resolveCallback($callback), $request, $event->getResponse(), $this->app); + if ($response instanceof Response) { + $event->setResponse($response); + } elseif (null !== $response) { + throw new \RuntimeException(sprintf('An after middleware for route "%s" returned an invalid response value. Must return null or an instance of Response.', $routeName)); + } + } + } + + public static function getSubscribedEvents() + { + return array( + // this must be executed after the late events defined with before() (and their priority is -512) + KernelEvents::REQUEST => array('onKernelRequest', -1024), + KernelEvents::RESPONSE => array('onKernelResponse', 128), + ); + } +} diff --git a/vendor/silex/silex/src/Silex/EventListener/StringToResponseListener.php b/vendor/silex/silex/src/Silex/EventListener/StringToResponseListener.php new file mode 100644 index 00000000..9fdba5fe --- /dev/null +++ b/vendor/silex/silex/src/Silex/EventListener/StringToResponseListener.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\EventListener; + +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Response; + +/** + * Converts string responses to proper Response instances. + * + * @author Fabien Potencier + */ +class StringToResponseListener implements EventSubscriberInterface +{ + /** + * Handles string responses. + * + * @param GetResponseForControllerResultEvent $event The event to handle + */ + public function onKernelView(GetResponseForControllerResultEvent $event) + { + $response = $event->getControllerResult(); + + if (!( + null === $response + || is_array($response) + || $response instanceof Response + || (is_object($response) && !method_exists($response, '__toString')) + )) { + $event->setResponse(new Response((string) $response)); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::VIEW => array('onKernelView', -10), + ); + } +} diff --git a/vendor/silex/silex/src/Silex/Exception/ControllerFrozenException.php b/vendor/silex/silex/src/Silex/Exception/ControllerFrozenException.php new file mode 100644 index 00000000..7f0d65f1 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Exception/ControllerFrozenException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Exception; + +/** + * Exception, is thrown when a frozen controller is modified. + * + * @author Igor Wiedler + */ +class ControllerFrozenException extends \RuntimeException +{ +} diff --git a/vendor/silex/silex/src/Silex/ExceptionHandler.php b/vendor/silex/silex/src/Silex/ExceptionHandler.php new file mode 100644 index 00000000..34eb8937 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ExceptionHandler.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Symfony\Component\Debug\ExceptionHandler as DebugExceptionHandler; +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Default exception handler. + * + * @author Fabien Potencier + */ +class ExceptionHandler implements EventSubscriberInterface +{ + protected $debug; + + public function __construct($debug) + { + $this->debug = $debug; + } + + public function onSilexError(GetResponseForExceptionEvent $event) + { + $handler = new DebugExceptionHandler($this->debug); + + $exception = $event->getException(); + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + $response = Response::create($handler->getHtml($exception), $exception->getStatusCode(), $exception->getHeaders())->setCharset(ini_get('default_charset')); + + $event->setResponse($response); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array(KernelEvents::EXCEPTION => array('onSilexError', -255)); + } +} diff --git a/vendor/silex/silex/src/Silex/ExceptionListenerWrapper.php b/vendor/silex/silex/src/Silex/ExceptionListenerWrapper.php new file mode 100644 index 00000000..e0d527b0 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ExceptionListenerWrapper.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; + +/** + * Wraps exception listeners. + * + * @author Fabien Potencier + */ +class ExceptionListenerWrapper +{ + protected $app; + protected $callback; + + /** + * Constructor. + * + * @param Application $app An Application instance + * @param callable $callback + */ + public function __construct(Application $app, $callback) + { + $this->app = $app; + $this->callback = $callback; + } + + public function __invoke(GetResponseForExceptionEvent $event) + { + $exception = $event->getException(); + $this->callback = $this->app['callback_resolver']->resolveCallback($this->callback); + + if (!$this->shouldRun($exception)) { + return; + } + + $code = $exception instanceof HttpExceptionInterface ? $exception->getStatusCode() : 500; + + $response = call_user_func($this->callback, $exception, $event->getRequest(), $code); + + $this->ensureResponse($response, $event); + } + + protected function shouldRun(\Exception $exception) + { + if (is_array($this->callback)) { + $callbackReflection = new \ReflectionMethod($this->callback[0], $this->callback[1]); + } elseif (is_object($this->callback) && !$this->callback instanceof \Closure) { + $callbackReflection = new \ReflectionObject($this->callback); + $callbackReflection = $callbackReflection->getMethod('__invoke'); + } else { + $callbackReflection = new \ReflectionFunction($this->callback); + } + + if ($callbackReflection->getNumberOfParameters() > 0) { + $parameters = $callbackReflection->getParameters(); + $expectedException = $parameters[0]; + if ($expectedException->getClass() && !$expectedException->getClass()->isInstance($exception)) { + return false; + } + } + + return true; + } + + protected function ensureResponse($response, GetResponseForExceptionEvent $event) + { + if ($response instanceof Response) { + $event->setResponse($response); + } else { + $viewEvent = new GetResponseForControllerResultEvent($this->app['kernel'], $event->getRequest(), $event->getRequestType(), $response); + $this->app['dispatcher']->dispatch(KernelEvents::VIEW, $viewEvent); + + if ($viewEvent->hasResponse()) { + $event->setResponse($viewEvent->getResponse()); + } + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/AssetServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/AssetServiceProvider.php new file mode 100644 index 00000000..fa603306 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/AssetServiceProvider.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Asset\Packages; +use Symfony\Component\Asset\Package; +use Symfony\Component\Asset\PathPackage; +use Symfony\Component\Asset\UrlPackage; +use Symfony\Component\Asset\Context\RequestStackContext; +use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy; +use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy; +use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy; + +/** + * Symfony Asset component Provider. + * + * @author Fabien Potencier + */ +class AssetServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['assets.packages'] = function ($app) { + $packages = array(); + foreach ($app['assets.named_packages'] as $name => $package) { + $version = $app['assets.strategy_factory'](isset($package['version']) ? $package['version'] : null, isset($package['version_format']) ? $package['version_format'] : null, isset($package['json_manifest_path']) ? $package['json_manifest_path'] : null, $name); + + $packages[$name] = $app['assets.package_factory'](isset($package['base_path']) ? $package['base_path'] : '', isset($package['base_urls']) ? $package['base_urls'] : array(), $version, $name); + } + + return new Packages($app['assets.default_package'], $packages); + }; + + $app['assets.default_package'] = function ($app) { + $version = $app['assets.strategy_factory']($app['assets.version'], $app['assets.version_format'], $app['assets.json_manifest_path'], 'default'); + + return $app['assets.package_factory']($app['assets.base_path'], $app['assets.base_urls'], $version, 'default'); + }; + + $app['assets.context'] = function ($app) { + return new RequestStackContext($app['request_stack']); + }; + + $app['assets.base_path'] = ''; + $app['assets.base_urls'] = array(); + $app['assets.version'] = null; + $app['assets.version_format'] = null; + $app['assets.json_manifest_path'] = null; + + $app['assets.named_packages'] = array(); + + // prototypes + + $app['assets.strategy_factory'] = $app->protect(function ($version, $format, $jsonManifestPath, $name) use ($app) { + if ($version && $jsonManifestPath) { + throw new \LogicException(sprintf('Asset package "%s" cannot have version and manifest.', $name)); + } + + if ($version) { + return new StaticVersionStrategy($version, $format); + } + + if ($jsonManifestPath) { + if (!class_exists('Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy')) { + throw new \RuntimeException('You must require symfony/asset >= 3.3 to use JSON manifest version strategy.'); + } + + return new JsonManifestVersionStrategy($jsonManifestPath); + } + + return new EmptyVersionStrategy(); + }); + + $app['assets.package_factory'] = $app->protect(function ($basePath, $baseUrls, $version, $name) use ($app) { + if ($basePath && $baseUrls) { + throw new \LogicException(sprintf('Asset package "%s" cannot have base URLs and base paths.', $name)); + } + + if (!$baseUrls) { + return new PathPackage($basePath, $version, $app['assets.context']); + } + + return new UrlPackage($baseUrls, $version, $app['assets.context']); + }); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/CsrfServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/CsrfServiceProvider.php new file mode 100644 index 00000000..eb6e882d --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/CsrfServiceProvider.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManager; +use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator; +use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; +use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage; + +/** + * Symfony CSRF Security component Provider. + * + * @author Fabien Potencier + */ +class CsrfServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['csrf.token_manager'] = function ($app) { + return new CsrfTokenManager($app['csrf.token_generator'], $app['csrf.token_storage']); + }; + + $app['csrf.token_storage'] = function ($app) { + if (isset($app['session'])) { + return new SessionTokenStorage($app['session'], $app['csrf.session_namespace']); + } + + return new NativeSessionTokenStorage($app['csrf.session_namespace']); + }; + + $app['csrf.token_generator'] = function ($app) { + return new UriSafeTokenGenerator(); + }; + + $app['csrf.session_namespace'] = '_csrf'; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/DoctrineServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/DoctrineServiceProvider.php new file mode 100644 index 00000000..9c71d5b7 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/DoctrineServiceProvider.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Configuration; +use Doctrine\Common\EventManager; +use Symfony\Bridge\Doctrine\Logger\DbalLogger; + +/** + * Doctrine DBAL Provider. + * + * @author Fabien Potencier + */ +class DoctrineServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['db.default_options'] = array( + 'driver' => 'pdo_mysql', + 'dbname' => null, + 'host' => 'localhost', + 'user' => 'root', + 'password' => null, + ); + + $app['dbs.options.initializer'] = $app->protect(function () use ($app) { + static $initialized = false; + + if ($initialized) { + return; + } + + $initialized = true; + + if (!isset($app['dbs.options'])) { + $app['dbs.options'] = array('default' => isset($app['db.options']) ? $app['db.options'] : array()); + } + + $tmp = $app['dbs.options']; + foreach ($tmp as $name => &$options) { + $options = array_replace($app['db.default_options'], $options); + + if (!isset($app['dbs.default'])) { + $app['dbs.default'] = $name; + } + } + $app['dbs.options'] = $tmp; + }); + + $app['dbs'] = function ($app) { + $app['dbs.options.initializer'](); + + $dbs = new Container(); + foreach ($app['dbs.options'] as $name => $options) { + if ($app['dbs.default'] === $name) { + // we use shortcuts here in case the default has been overridden + $config = $app['db.config']; + $manager = $app['db.event_manager']; + } else { + $config = $app['dbs.config'][$name]; + $manager = $app['dbs.event_manager'][$name]; + } + + $dbs[$name] = function ($dbs) use ($options, $config, $manager) { + return DriverManager::getConnection($options, $config, $manager); + }; + } + + return $dbs; + }; + + $app['dbs.config'] = function ($app) { + $app['dbs.options.initializer'](); + + $configs = new Container(); + $addLogger = isset($app['logger']) && null !== $app['logger'] && class_exists('Symfony\Bridge\Doctrine\Logger\DbalLogger'); + foreach ($app['dbs.options'] as $name => $options) { + $configs[$name] = new Configuration(); + if ($addLogger) { + $configs[$name]->setSQLLogger(new DbalLogger($app['logger'], isset($app['stopwatch']) ? $app['stopwatch'] : null)); + } + } + + return $configs; + }; + + $app['dbs.event_manager'] = function ($app) { + $app['dbs.options.initializer'](); + + $managers = new Container(); + foreach ($app['dbs.options'] as $name => $options) { + $managers[$name] = new EventManager(); + } + + return $managers; + }; + + // shortcuts for the "first" DB + $app['db'] = function ($app) { + $dbs = $app['dbs']; + + return $dbs[$app['dbs.default']]; + }; + + $app['db.config'] = function ($app) { + $dbs = $app['dbs.config']; + + return $dbs[$app['dbs.default']]; + }; + + $app['db.event_manager'] = function ($app) { + $dbs = $app['dbs.event_manager']; + + return $dbs[$app['dbs.default']]; + }; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/ExceptionHandlerServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/ExceptionHandlerServiceProvider.php new file mode 100644 index 00000000..1c6f2028 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/ExceptionHandlerServiceProvider.php @@ -0,0 +1,32 @@ +addSubscriber($app['exception_handler']); + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Form/SilexFormExtension.php b/vendor/silex/silex/src/Silex/Provider/Form/SilexFormExtension.php new file mode 100644 index 00000000..12efbdf2 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Form/SilexFormExtension.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Form; + +use Pimple\Container; +use Symfony\Component\Form\Exception\InvalidArgumentException; +use Symfony\Component\Form\FormExtensionInterface; +use Symfony\Component\Form\FormTypeGuesserChain; + +class SilexFormExtension implements FormExtensionInterface +{ + private $app; + private $types; + private $typeExtensions; + private $guessers; + private $guesserLoaded = false; + private $guesser; + + public function __construct(Container $app, array $types, array $typeExtensions, array $guessers) + { + $this->app = $app; + $this->setTypes($types); + $this->setTypeExtensions($typeExtensions); + $this->setGuessers($guessers); + } + + public function getType($name) + { + if (!isset($this->types[$name])) { + throw new InvalidArgumentException(sprintf('The type "%s" is not the name of a registered form type.', $name)); + } + if (!is_object($this->types[$name])) { + $this->types[$name] = $this->app[$this->types[$name]]; + } + + return $this->types[$name]; + } + + public function hasType($name) + { + return isset($this->types[$name]); + } + + public function getTypeExtensions($name) + { + return isset($this->typeExtensions[$name]) ? $this->typeExtensions[$name] : []; + } + + public function hasTypeExtensions($name) + { + return isset($this->typeExtensions[$name]); + } + + public function getTypeGuesser() + { + if (!$this->guesserLoaded) { + $this->guesserLoaded = true; + + if ($this->guessers) { + $guessers = []; + foreach ($this->guessers as $guesser) { + if (!is_object($guesser)) { + $guesser = $this->app[$guesser]; + } + $guessers[] = $guesser; + } + $this->guesser = new FormTypeGuesserChain($guessers); + } + } + + return $this->guesser; + } + + private function setTypes(array $types) + { + $this->types = []; + foreach ($types as $type) { + if (!is_object($type)) { + if (!isset($this->app[$type])) { + throw new InvalidArgumentException(sprintf('Invalid form type. The silex service "%s" does not exist.', $type)); + } + $this->types[$type] = $type; + } else { + $this->types[get_class($type)] = $type; + } + } + } + + private function setTypeExtensions(array $typeExtensions) + { + $this->typeExtensions = []; + foreach ($typeExtensions as $extension) { + if (!is_object($extension)) { + if (!isset($this->app[$extension])) { + throw new InvalidArgumentException(sprintf('Invalid form type extension. The silex service "%s" does not exist.', $extension)); + } + $extension = $this->app[$extension]; + } + $this->typeExtensions[$extension->getExtendedType()][] = $extension; + } + } + + private function setGuessers(array $guessers) + { + $this->guessers = []; + foreach ($guessers as $guesser) { + if (!is_object($guesser) && !isset($this->app[$guesser])) { + throw new InvalidArgumentException(sprintf('Invalid form type guesser. The silex service "%s" does not exist.', $guesser)); + } + $this->guessers[] = $guesser; + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/FormServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/FormServiceProvider.php new file mode 100644 index 00000000..00841d0b --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/FormServiceProvider.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Form\Extension\Csrf\CsrfExtension; +use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension; +use Symfony\Component\Form\Extension\Validator\ValidatorExtension as FormValidatorExtension; +use Symfony\Component\Form\FormFactory; +use Symfony\Component\Form\FormRegistry; +use Symfony\Component\Form\ResolvedFormTypeFactory; + +/** + * Symfony Form component Provider. + * + * @author Fabien Potencier + */ +class FormServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + if (!class_exists('Locale')) { + throw new \RuntimeException('You must either install the PHP intl extension or the Symfony Intl Component to use the Form extension.'); + } + + $app['form.types'] = function ($app) { + return array(); + }; + + $app['form.type.extensions'] = function ($app) { + return array(); + }; + + $app['form.type.guessers'] = function ($app) { + return array(); + }; + + $app['form.extension.csrf'] = function ($app) { + if (isset($app['translator'])) { + return new CsrfExtension($app['csrf.token_manager'], $app['translator']); + } + + return new CsrfExtension($app['csrf.token_manager']); + }; + + $app['form.extension.silex'] = function ($app) { + return new Form\SilexFormExtension($app, $app['form.types'], $app['form.type.extensions'], $app['form.type.guessers']); + }; + + $app['form.extensions'] = function ($app) { + $extensions = array( + new HttpFoundationExtension(), + ); + + if (isset($app['csrf.token_manager'])) { + $extensions[] = $app['form.extension.csrf']; + } + + if (isset($app['validator'])) { + $extensions[] = new FormValidatorExtension($app['validator']); + } + $extensions[] = $app['form.extension.silex']; + + return $extensions; + }; + + $app['form.factory'] = function ($app) { + return new FormFactory($app['form.registry'], $app['form.resolved_type_factory']); + }; + + $app['form.registry'] = function ($app) { + return new FormRegistry($app['form.extensions'], $app['form.resolved_type_factory']); + }; + + $app['form.resolved_type_factory'] = function ($app) { + return new ResolvedFormTypeFactory(); + }; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/HttpCache/HttpCache.php b/vendor/silex/silex/src/Silex/Provider/HttpCache/HttpCache.php new file mode 100644 index 00000000..b0ebb5cc --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/HttpCache/HttpCache.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\HttpCache; + +use Symfony\Component\HttpKernel\HttpCache\HttpCache as BaseHttpCache; +use Symfony\Component\HttpFoundation\Request; + +/** + * HTTP Cache extension to allow using the run() shortcut. + * + * @author Fabien Potencier + */ +class HttpCache extends BaseHttpCache +{ + /** + * Handles the Request and delivers the Response. + * + * @param Request $request The Request object + */ + public function run(Request $request = null) + { + if (null === $request) { + $request = Request::createFromGlobals(); + } + + $response = $this->handle($request); + $response->send(); + $this->terminate($request, $response); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/HttpCacheServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/HttpCacheServiceProvider.php new file mode 100644 index 00000000..8b3f37ee --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/HttpCacheServiceProvider.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Provider\HttpCache\HttpCache; +use Silex\Api\EventListenerProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpKernel\HttpCache\Store; +use Symfony\Component\HttpKernel\EventListener\SurrogateListener; + +/** + * Symfony HttpKernel component Provider for HTTP cache. + * + * @author Fabien Potencier + */ +class HttpCacheServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['http_cache'] = function ($app) { + $app['http_cache.options'] = array_replace( + array( + 'debug' => $app['debug'], + ), $app['http_cache.options'] + ); + + return new HttpCache($app, $app['http_cache.store'], $app['http_cache.esi'], $app['http_cache.options']); + }; + + $app['http_cache.esi'] = function ($app) { + return new Esi(); + }; + + $app['http_cache.store'] = function ($app) { + return new Store($app['http_cache.cache_dir']); + }; + + $app['http_cache.esi_listener'] = function ($app) { + return new SurrogateListener($app['http_cache.esi']); + }; + + $app['http_cache.options'] = array(); + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['http_cache.esi_listener']); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/HttpFragmentServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/HttpFragmentServiceProvider.php new file mode 100644 index 00000000..fb1f4990 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/HttpFragmentServiceProvider.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; +use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer; +use Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer; +use Symfony\Component\HttpKernel\Fragment\HIncludeFragmentRenderer; +use Symfony\Component\HttpKernel\EventListener\FragmentListener; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\UriSigner; + +/** + * HttpKernel Fragment integration for Silex. + * + * @author Fabien Potencier + */ +class HttpFragmentServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['fragment.handler'] = function ($app) { + return new FragmentHandler($app['request_stack'], $app['fragment.renderers'], $app['debug']); + }; + + $app['fragment.renderer.inline'] = function ($app) { + $renderer = new InlineFragmentRenderer($app['kernel'], $app['dispatcher']); + $renderer->setFragmentPath($app['fragment.path']); + + return $renderer; + }; + + $app['fragment.renderer.hinclude'] = function ($app) { + $renderer = new HIncludeFragmentRenderer(null, $app['uri_signer'], $app['fragment.renderer.hinclude.global_template'], $app['charset']); + $renderer->setFragmentPath($app['fragment.path']); + + return $renderer; + }; + + $app['fragment.renderer.esi'] = function ($app) { + $renderer = new EsiFragmentRenderer($app['http_cache.esi'], $app['fragment.renderer.inline'], $app['uri_signer']); + $renderer->setFragmentPath($app['fragment.path']); + + return $renderer; + }; + + $app['fragment.listener'] = function ($app) { + return new FragmentListener($app['uri_signer'], $app['fragment.path']); + }; + + $app['uri_signer'] = function ($app) { + return new UriSigner($app['uri_signer.secret']); + }; + + $app['uri_signer.secret'] = md5(__DIR__); + $app['fragment.path'] = '/_fragment'; + $app['fragment.renderer.hinclude.global_template'] = null; + $app['fragment.renderers'] = function ($app) { + $renderers = array($app['fragment.renderer.inline'], $app['fragment.renderer.hinclude']); + + if (isset($app['http_cache.esi'])) { + $renderers[] = $app['fragment.renderer.esi']; + } + + return $renderers; + }; + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['fragment.listener']); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/HttpKernelServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/HttpKernelServiceProvider.php new file mode 100644 index 00000000..86f155fe --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/HttpKernelServiceProvider.php @@ -0,0 +1,101 @@ += 30100) { + return new SfControllerResolver($app['logger']); + } + + return new ControllerResolver($app, $app['logger']); + }; + + if (Kernel::VERSION_ID >= 30100) { + $app['argument_metadata_factory'] = function ($app) { + return new ArgumentMetadataFactory(); + }; + $app['argument_value_resolvers'] = function ($app) { + if (Kernel::VERSION_ID < 30200) { + return array( + new AppArgumentValueResolver($app), + new RequestAttributeValueResolver(), + new RequestValueResolver(), + new DefaultValueResolver(), + new VariadicValueResolver(), + ); + } + + return array_merge(array(new AppArgumentValueResolver($app)), ArgumentResolver::getDefaultArgumentValueResolvers()); + }; + } + + $app['argument_resolver'] = function ($app) { + if (Kernel::VERSION_ID >= 30100) { + return new ArgumentResolver($app['argument_metadata_factory'], $app['argument_value_resolvers']); + } + }; + + $app['kernel'] = function ($app) { + return new HttpKernel($app['dispatcher'], $app['resolver'], $app['request_stack'], $app['argument_resolver']); + }; + + $app['request_stack'] = function () { + return new RequestStack(); + }; + + $app['dispatcher'] = function () { + return new EventDispatcher(); + }; + + $app['callback_resolver'] = function ($app) { + return new CallbackResolver($app); + }; + } + + /** + * {@inheritdoc} + */ + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber(new ResponseListener($app['charset'])); + $dispatcher->addSubscriber(new MiddlewareListener($app)); + $dispatcher->addSubscriber(new ConverterListener($app['routes'], $app['callback_resolver'])); + $dispatcher->addSubscriber(new StringToResponseListener()); + + if (class_exists(HttpHeaderSerializer::class)) { + $dispatcher->addSubscriber(new AddLinkHeaderListener()); + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/LICENSE b/vendor/silex/silex/src/Silex/Provider/LICENSE new file mode 100644 index 00000000..bc6ad049 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-2015 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/silex/silex/src/Silex/Provider/Locale/LocaleListener.php b/vendor/silex/silex/src/Silex/Provider/Locale/LocaleListener.php new file mode 100644 index 00000000..d5002640 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Locale/LocaleListener.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Locale; + +use Pimple\Container; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Initializes the locale based on the current request. + * + * @author Fabien Potencier + * @author Jérôme Tamarelle + */ +class LocaleListener implements EventSubscriberInterface +{ + private $app; + private $defaultLocale; + private $requestStack; + private $requestContext; + + public function __construct(Container $app, $defaultLocale = 'en', RequestStack $requestStack, RequestContext $requestContext = null) + { + $this->app = $app; + $this->defaultLocale = $defaultLocale; + $this->requestStack = $requestStack; + $this->requestContext = $requestContext; + } + + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + $request->setDefaultLocale($this->defaultLocale); + + $this->setLocale($request); + $this->setRouterContext($request); + + $this->app['locale'] = $request->getLocale(); + } + + public function onKernelFinishRequest(FinishRequestEvent $event) + { + if (null !== $parentRequest = $this->requestStack->getParentRequest()) { + $this->setRouterContext($parentRequest); + } + } + + private function setLocale(Request $request) + { + if ($locale = $request->attributes->get('_locale')) { + $request->setLocale($locale); + } + } + + private function setRouterContext(Request $request) + { + if (null !== $this->requestContext) { + $this->requestContext->setParameter('_locale', $request->getLocale()); + } + } + + public static function getSubscribedEvents() + { + return array( + // must be registered after the Router to have access to the _locale + KernelEvents::REQUEST => array(array('onKernelRequest', 16)), + KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + ); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/LocaleServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/LocaleServiceProvider.php new file mode 100644 index 00000000..ddea81bf --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/LocaleServiceProvider.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Silex\Provider\Locale\LocaleListener; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Locale Provider. + * + * @author Fabien Potencier + */ +class LocaleServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['locale.listener'] = function ($app) { + return new LocaleListener($app, $app['locale'], $app['request_stack'], isset($app['request_context']) ? $app['request_context'] : null); + }; + + $app['locale'] = 'en'; + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['locale.listener']); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/MonologServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/MonologServiceProvider.php new file mode 100644 index 00000000..f8cba4e2 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/MonologServiceProvider.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; +use Monolog\Handler; +use Monolog\ErrorHandler; +use Silex\Application; +use Silex\Api\BootableProviderInterface; +use Symfony\Bridge\Monolog\Handler\DebugHandler; +use Symfony\Bridge\Monolog\Handler\FingersCrossed\NotFoundActivationStrategy; +use Symfony\Bridge\Monolog\Processor\DebugProcessor; +use Silex\EventListener\LogListener; + +/** + * Monolog Provider. + * + * @author Fabien Potencier + */ +class MonologServiceProvider implements ServiceProviderInterface, BootableProviderInterface +{ + public function register(Container $app) + { + $app['logger'] = function () use ($app) { + return $app['monolog']; + }; + + if ($bridge = class_exists('Symfony\Bridge\Monolog\Logger')) { + $app['monolog.handler.debug'] = function () use ($app) { + $level = MonologServiceProvider::translateLevel($app['monolog.level']); + + return new DebugHandler($level); + }; + + if (isset($app['request_stack'])) { + $app['monolog.not_found_activation_strategy'] = function () use ($app) { + return new NotFoundActivationStrategy($app['request_stack'], array('^/'), $app['monolog.level']); + }; + } + } + + $app['monolog.logger.class'] = $bridge ? 'Symfony\Bridge\Monolog\Logger' : 'Monolog\Logger'; + + $app['monolog'] = function ($app) use ($bridge) { + $log = new $app['monolog.logger.class']($app['monolog.name']); + + $handler = new Handler\GroupHandler($app['monolog.handlers']); + if (isset($app['monolog.not_found_activation_strategy'])) { + $handler = new Handler\FingersCrossedHandler($handler, $app['monolog.not_found_activation_strategy']); + } + + $log->pushHandler($handler); + + if ($app['debug'] && $bridge) { + if (class_exists(DebugProcessor::class)) { + $log->pushProcessor(new DebugProcessor()); + } else { + $log->pushHandler($app['monolog.handler.debug']); + } + } + + return $log; + }; + + $app['monolog.formatter'] = function () { + return new LineFormatter(); + }; + + $app['monolog.handler'] = $defaultHandler = function () use ($app) { + $level = MonologServiceProvider::translateLevel($app['monolog.level']); + + $handler = new Handler\StreamHandler($app['monolog.logfile'], $level, $app['monolog.bubble'], $app['monolog.permission']); + $handler->setFormatter($app['monolog.formatter']); + + return $handler; + }; + + $app['monolog.handlers'] = function () use ($app, $defaultHandler) { + $handlers = array(); + + // enables the default handler if a logfile was set or the monolog.handler service was redefined + if ($app['monolog.logfile'] || $defaultHandler !== $app->raw('monolog.handler')) { + $handlers[] = $app['monolog.handler']; + } + + return $handlers; + }; + + $app['monolog.level'] = function () { + return Logger::DEBUG; + }; + + $app['monolog.listener'] = function () use ($app) { + return new LogListener($app['logger'], $app['monolog.exception.logger_filter']); + }; + + $app['monolog.name'] = 'app'; + $app['monolog.bubble'] = true; + $app['monolog.permission'] = null; + $app['monolog.exception.logger_filter'] = null; + $app['monolog.logfile'] = null; + $app['monolog.use_error_handler'] = function ($app) { + return !$app['debug']; + }; + } + + public function boot(Application $app) + { + if ($app['monolog.use_error_handler']) { + ErrorHandler::register($app['monolog']); + } + + if (isset($app['monolog.listener'])) { + $app['dispatcher']->addSubscriber($app['monolog.listener']); + } + } + + public static function translateLevel($name) + { + // level is already translated to logger constant, return as-is + if (is_int($name)) { + return $name; + } + + $levels = Logger::getLevels(); + $upper = strtoupper($name); + + if (!isset($levels[$upper])) { + throw new \InvalidArgumentException("Provided logging level '$name' does not exist. Must be a valid monolog logging level."); + } + + return $levels[$upper]; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/RememberMeServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/RememberMeServiceProvider.php new file mode 100644 index 00000000..766631c5 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/RememberMeServiceProvider.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Security\Core\Authentication\Provider\RememberMeAuthenticationProvider; +use Symfony\Component\Security\Http\Firewall\RememberMeListener; +use Symfony\Component\Security\Http\RememberMe\TokenBasedRememberMeServices; +use Symfony\Component\Security\Http\RememberMe\ResponseListener; + +/** + * Remember-me authentication for the SecurityServiceProvider. + * + * @author Jérôme Tamarelle + */ +class RememberMeServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['security.remember_me.response_listener'] = function ($app) { + if (!isset($app['security.token_storage'])) { + throw new \LogicException('You must register the SecurityServiceProvider to use the RememberMeServiceProvider'); + } + + return new ResponseListener(); + }; + + $app['security.authentication_listener.factory.remember_me'] = $app->protect(function ($name, $options) use ($app) { + if (empty($options['key'])) { + $options['key'] = $name; + } + + if (!isset($app['security.remember_me.service.'.$name])) { + $app['security.remember_me.service.'.$name] = $app['security.remember_me.service._proto']($name, $options); + } + + if (!isset($app['security.authentication_listener.'.$name.'.remember_me'])) { + $app['security.authentication_listener.'.$name.'.remember_me'] = $app['security.authentication_listener.remember_me._proto']($name, $options); + } + + if (!isset($app['security.authentication_provider.'.$name.'.remember_me'])) { + $app['security.authentication_provider.'.$name.'.remember_me'] = $app['security.authentication_provider.remember_me._proto']($name, $options); + } + + return array( + 'security.authentication_provider.'.$name.'.remember_me', + 'security.authentication_listener.'.$name.'.remember_me', + null, // entry point + 'remember_me', + ); + }); + + $app['security.remember_me.service._proto'] = $app->protect(function ($providerKey, $options) use ($app) { + return function () use ($providerKey, $options, $app) { + $options = array_replace(array( + 'name' => 'REMEMBERME', + 'lifetime' => 31536000, + 'path' => '/', + 'domain' => null, + 'secure' => false, + 'httponly' => true, + 'always_remember_me' => false, + 'remember_me_parameter' => '_remember_me', + ), $options); + + return new TokenBasedRememberMeServices(array($app['security.user_provider.'.$providerKey]), $options['key'], $providerKey, $options, $app['logger']); + }; + }); + + $app['security.authentication_listener.remember_me._proto'] = $app->protect(function ($providerKey) use ($app) { + return function () use ($app, $providerKey) { + $listener = new RememberMeListener( + $app['security.token_storage'], + $app['security.remember_me.service.'.$providerKey], + $app['security.authentication_manager'], + $app['logger'], + $app['dispatcher'] + ); + + return $listener; + }; + }); + + $app['security.authentication_provider.remember_me._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($app, $name, $options) { + return new RememberMeAuthenticationProvider($app['security.user_checker'], $options['key'], $name); + }; + }); + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['security.remember_me.response_listener']); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Routing/LazyRequestMatcher.php b/vendor/silex/silex/src/Silex/Provider/Routing/LazyRequestMatcher.php new file mode 100644 index 00000000..6837c79a --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Routing/LazyRequestMatcher.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Routing; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; + +/** + * Implements a lazy UrlMatcher. + * + * @author Igor Wiedler + * @author Jérôme Tamarelle + */ +class LazyRequestMatcher implements RequestMatcherInterface +{ + private $factory; + + public function __construct(\Closure $factory) + { + $this->factory = $factory; + } + + /** + * Returns the corresponding RequestMatcherInterface instance. + * + * @return UrlMatcherInterface + */ + public function getRequestMatcher() + { + $matcher = call_user_func($this->factory); + if (!$matcher instanceof RequestMatcherInterface) { + throw new \LogicException("Factory supplied to LazyRequestMatcher must return implementation of Symfony\Component\Routing\RequestMatcherInterface."); + } + + return $matcher; + } + + /** + * {@inheritdoc} + */ + public function matchRequest(Request $request) + { + return $this->getRequestMatcher()->matchRequest($request); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Routing/RedirectableUrlMatcher.php b/vendor/silex/silex/src/Silex/Provider/Routing/RedirectableUrlMatcher.php new file mode 100644 index 00000000..8b4a4dae --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Routing/RedirectableUrlMatcher.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Routing; + +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\Routing\Matcher\RedirectableUrlMatcher as BaseRedirectableUrlMatcher; + +/** + * Implements the RedirectableUrlMatcherInterface for Silex. + * + * @author Fabien Potencier + */ +class RedirectableUrlMatcher extends BaseRedirectableUrlMatcher +{ + /** + * {@inheritdoc} + */ + public function redirect($path, $route, $scheme = null) + { + $url = $this->context->getBaseUrl().$path; + $query = $this->context->getQueryString() ?: ''; + + if ($query !== '') { + $url .= '?'.$query; + } + + if ($this->context->getHost()) { + if ($scheme) { + $port = ''; + if ('http' === $scheme && 80 != $this->context->getHttpPort()) { + $port = ':'.$this->context->getHttpPort(); + } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) { + $port = ':'.$this->context->getHttpsPort(); + } + + $url = $scheme.'://'.$this->context->getHost().$port.$url; + } + } + + return array( + '_controller' => function ($url) { return new RedirectResponse($url, 301); }, + '_route' => $route, + 'url' => $url, + ); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/RoutingServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/RoutingServiceProvider.php new file mode 100644 index 00000000..d040ba0d --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/RoutingServiceProvider.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\ControllerCollection; +use Silex\Api\EventListenerProviderInterface; +use Silex\Provider\Routing\RedirectableUrlMatcher; +use Silex\Provider\Routing\LazyRequestMatcher; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\HttpKernel\EventListener\RouterListener; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Symfony Routing component Provider. + * + * @author Fabien Potencier + */ +class RoutingServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['route_class'] = 'Silex\\Route'; + + $app['route_factory'] = $app->factory(function ($app) { + return new $app['route_class'](); + }); + + $app['routes_factory'] = $app->factory(function () { + return new RouteCollection(); + }); + + $app['routes'] = function ($app) { + return $app['routes_factory']; + }; + $app['url_generator'] = function ($app) { + return new UrlGenerator($app['routes'], $app['request_context']); + }; + + $app['request_matcher'] = function ($app) { + return new RedirectableUrlMatcher($app['routes'], $app['request_context']); + }; + + $app['request_context'] = function ($app) { + $context = new RequestContext(); + + $context->setHttpPort(isset($app['request.http_port']) ? $app['request.http_port'] : 80); + $context->setHttpsPort(isset($app['request.https_port']) ? $app['request.https_port'] : 443); + + return $context; + }; + + $app['controllers'] = function ($app) { + return $app['controllers_factory']; + }; + + $controllers_factory = function () use ($app, &$controllers_factory) { + return new ControllerCollection($app['route_factory'], $app['routes_factory'], $controllers_factory); + }; + $app['controllers_factory'] = $app->factory($controllers_factory); + + $app['routing.listener'] = function ($app) { + $urlMatcher = new LazyRequestMatcher(function () use ($app) { + return $app['request_matcher']; + }); + + return new RouterListener($urlMatcher, $app['request_stack'], $app['request_context'], $app['logger']); + }; + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['routing.listener']); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/SecurityServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/SecurityServiceProvider.php new file mode 100644 index 00000000..ebc5bea6 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SecurityServiceProvider.php @@ -0,0 +1,696 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Application; +use Silex\Api\BootableProviderInterface; +use Silex\Api\ControllerProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\RequestMatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Core\User\UserChecker; +use Symfony\Component\Security\Core\User\InMemoryUserProvider; +use Symfony\Component\Security\Core\Encoder\EncoderFactory; +use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; +use Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder; +use Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder; +use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider; +use Symfony\Component\Security\Core\Authentication\Provider\AnonymousAuthenticationProvider; +use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; +use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler; +use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; +use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; +use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter; +use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; +use Symfony\Component\Security\Core\Role\RoleHierarchy; +use Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator; +use Symfony\Component\Security\Http\Firewall; +use Symfony\Component\Security\Http\FirewallMap; +use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener; +use Symfony\Component\Security\Http\Firewall\AccessListener; +use Symfony\Component\Security\Http\Firewall\BasicAuthenticationListener; +use Symfony\Component\Security\Http\Firewall\LogoutListener; +use Symfony\Component\Security\Http\Firewall\SwitchUserListener; +use Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener; +use Symfony\Component\Security\Http\Firewall\ContextListener; +use Symfony\Component\Security\Http\Firewall\ExceptionListener; +use Symfony\Component\Security\Http\Firewall\ChannelListener; +use Symfony\Component\Security\Http\EntryPoint\FormAuthenticationEntryPoint; +use Symfony\Component\Security\Http\EntryPoint\BasicAuthenticationEntryPoint; +use Symfony\Component\Security\Http\EntryPoint\RetryAuthenticationEntryPoint; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy; +use Symfony\Component\Security\Http\Logout\SessionLogoutHandler; +use Symfony\Component\Security\Http\Logout\DefaultLogoutSuccessHandler; +use Symfony\Component\Security\Http\AccessMap; +use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; +use Symfony\Component\Security\Guard\Firewall\GuardAuthenticationListener; +use Symfony\Component\Security\Guard\Provider\GuardAuthenticationProvider; + +/** + * Symfony Security component Provider. + * + * @author Fabien Potencier + */ +class SecurityServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface, ControllerProviderInterface, BootableProviderInterface +{ + protected $fakeRoutes; + + public function register(Container $app) + { + // used to register routes for login_check and logout + $this->fakeRoutes = array(); + + $that = $this; + + $app['security.role_hierarchy'] = array(); + $app['security.access_rules'] = array(); + $app['security.hide_user_not_found'] = true; + $app['security.encoder.bcrypt.cost'] = 13; + + $app['security.authorization_checker'] = function ($app) { + return new AuthorizationChecker($app['security.token_storage'], $app['security.authentication_manager'], $app['security.access_manager']); + }; + + $app['security.token_storage'] = function ($app) { + return new TokenStorage(); + }; + + $app['user'] = $app->factory(function ($app) { + if (null === $token = $app['security.token_storage']->getToken()) { + return; + } + + if (!is_object($user = $token->getUser())) { + return; + } + + return $user; + }); + + $app['security.authentication_manager'] = function ($app) { + $manager = new AuthenticationProviderManager($app['security.authentication_providers']); + $manager->setEventDispatcher($app['dispatcher']); + + return $manager; + }; + + // by default, all users use the digest encoder + $app['security.encoder_factory'] = function ($app) { + return new EncoderFactory(array( + 'Symfony\Component\Security\Core\User\UserInterface' => $app['security.default_encoder'], + )); + }; + + // by default, all users use the BCrypt encoder + $app['security.default_encoder'] = function ($app) { + return $app['security.encoder.bcrypt']; + }; + + $app['security.encoder.digest'] = function ($app) { + return new MessageDigestPasswordEncoder(); + }; + + $app['security.encoder.bcrypt'] = function ($app) { + return new BCryptPasswordEncoder($app['security.encoder.bcrypt.cost']); + }; + + $app['security.encoder.pbkdf2'] = function ($app) { + return new Pbkdf2PasswordEncoder(); + }; + + $app['security.user_checker'] = function ($app) { + return new UserChecker(); + }; + + $app['security.access_manager'] = function ($app) { + return new AccessDecisionManager($app['security.voters']); + }; + + $app['security.voters'] = function ($app) { + return array( + new RoleHierarchyVoter(new RoleHierarchy($app['security.role_hierarchy'])), + new AuthenticatedVoter($app['security.trust_resolver']), + ); + }; + + $app['security.firewall'] = function ($app) { + return new Firewall($app['security.firewall_map'], $app['dispatcher']); + }; + + $app['security.channel_listener'] = function ($app) { + return new ChannelListener( + $app['security.access_map'], + new RetryAuthenticationEntryPoint( + isset($app['request.http_port']) ? $app['request.http_port'] : 80, + isset($app['request.https_port']) ? $app['request.https_port'] : 443 + ), + $app['logger'] + ); + }; + + // generate the build-in authentication factories + foreach (array('logout', 'pre_auth', 'guard', 'form', 'http', 'remember_me', 'anonymous') as $type) { + $entryPoint = null; + if ('http' === $type) { + $entryPoint = 'http'; + } elseif ('form' === $type) { + $entryPoint = 'form'; + } elseif ('guard' === $type) { + $entryPoint = 'guard'; + } + + $app['security.authentication_listener.factory.'.$type] = $app->protect(function ($name, $options) use ($type, $app, $entryPoint) { + if ($entryPoint && !isset($app['security.entry_point.'.$name.'.'.$entryPoint])) { + $app['security.entry_point.'.$name.'.'.$entryPoint] = $app['security.entry_point.'.$entryPoint.'._proto']($name, $options); + } + + if (!isset($app['security.authentication_listener.'.$name.'.'.$type])) { + $app['security.authentication_listener.'.$name.'.'.$type] = $app['security.authentication_listener.'.$type.'._proto']($name, $options); + } + + $provider = 'dao'; + if ('anonymous' === $type) { + $provider = 'anonymous'; + } elseif ('guard' === $type) { + $provider = 'guard'; + } + if (!isset($app['security.authentication_provider.'.$name.'.'.$provider])) { + $app['security.authentication_provider.'.$name.'.'.$provider] = $app['security.authentication_provider.'.$provider.'._proto']($name, $options); + } + + return array( + 'security.authentication_provider.'.$name.'.'.$provider, + 'security.authentication_listener.'.$name.'.'.$type, + $entryPoint ? 'security.entry_point.'.$name.'.'.$entryPoint : null, + $type, + ); + }); + } + + $app['security.firewall_map'] = function ($app) { + $positions = array('logout', 'pre_auth', 'guard', 'form', 'http', 'remember_me', 'anonymous'); + $providers = array(); + $configs = array(); + foreach ($app['security.firewalls'] as $name => $firewall) { + $entryPoint = null; + $pattern = isset($firewall['pattern']) ? $firewall['pattern'] : null; + $users = isset($firewall['users']) ? $firewall['users'] : array(); + $security = isset($firewall['security']) ? (bool) $firewall['security'] : true; + $stateless = isset($firewall['stateless']) ? (bool) $firewall['stateless'] : false; + $context = isset($firewall['context']) ? $firewall['context'] : $name; + $hosts = isset($firewall['hosts']) ? $firewall['hosts'] : null; + $methods = isset($firewall['methods']) ? $firewall['methods'] : null; + unset($firewall['pattern'], $firewall['users'], $firewall['security'], $firewall['stateless'], $firewall['context'], $firewall['methods'], $firewall['hosts']); + $protected = false === $security ? false : count($firewall); + $listeners = array('security.channel_listener'); + + if ($protected) { + if (!isset($app['security.context_listener.'.$name])) { + if (!isset($app['security.user_provider.'.$name])) { + $app['security.user_provider.'.$name] = is_array($users) ? $app['security.user_provider.inmemory._proto']($users) : $users; + } + + $app['security.context_listener.'.$name] = $app['security.context_listener._proto']($name, array($app['security.user_provider.'.$name])); + } + + if (false === $stateless) { + $listeners[] = 'security.context_listener.'.$context; + } + + $factories = array(); + foreach ($positions as $position) { + $factories[$position] = array(); + } + + foreach ($firewall as $type => $options) { + if ('switch_user' === $type) { + continue; + } + + // normalize options + if (!is_array($options)) { + if (!$options) { + continue; + } + + $options = array(); + } + + if (!isset($app['security.authentication_listener.factory.'.$type])) { + throw new \LogicException(sprintf('The "%s" authentication entry is not registered.', $type)); + } + + $options['stateless'] = $stateless; + + list($providerId, $listenerId, $entryPointId, $position) = $app['security.authentication_listener.factory.'.$type]($name, $options); + + if (null !== $entryPointId) { + $entryPoint = $entryPointId; + } + + $factories[$position][] = $listenerId; + $providers[] = $providerId; + } + + foreach ($positions as $position) { + foreach ($factories[$position] as $listener) { + $listeners[] = $listener; + } + } + + $listeners[] = 'security.access_listener'; + + if (isset($firewall['switch_user'])) { + $app['security.switch_user.'.$name] = $app['security.authentication_listener.switch_user._proto']($name, $firewall['switch_user']); + + $listeners[] = 'security.switch_user.'.$name; + } + + if (!isset($app['security.exception_listener.'.$name])) { + if (null == $entryPoint) { + $app[$entryPoint = 'security.entry_point.'.$name.'.form'] = $app['security.entry_point.form._proto']($name, array()); + } + $accessDeniedHandler = null; + if (isset($app['security.access_denied_handler.'.$name])) { + $accessDeniedHandler = $app['security.access_denied_handler.'.$name]; + } + $app['security.exception_listener.'.$name] = $app['security.exception_listener._proto']($entryPoint, $name, $accessDeniedHandler); + } + } + + $configs[$name] = array( + 'pattern' => $pattern, + 'listeners' => $listeners, + 'protected' => $protected, + 'methods' => $methods, + 'hosts' => $hosts, + ); + } + + $app['security.authentication_providers'] = array_map(function ($provider) use ($app) { + return $app[$provider]; + }, array_unique($providers)); + + $map = new FirewallMap(); + foreach ($configs as $name => $config) { + if (is_string($config['pattern'])) { + $requestMatcher = new RequestMatcher($config['pattern'], $config['hosts'], $config['methods']); + } else { + $requestMatcher = $config['pattern']; + } + + $map->add( + $requestMatcher, + array_map(function ($listenerId) use ($app, $name) { + $listener = $app[$listenerId]; + + if (isset($app['security.remember_me.service.'.$name])) { + if ($listener instanceof AbstractAuthenticationListener || $listener instanceof GuardAuthenticationListener) { + $listener->setRememberMeServices($app['security.remember_me.service.'.$name]); + } + if ($listener instanceof LogoutListener) { + $listener->addHandler($app['security.remember_me.service.'.$name]); + } + } + + return $listener; + }, $config['listeners']), + $config['protected'] ? $app['security.exception_listener.'.$name] : null + ); + } + + return $map; + }; + + $app['security.access_listener'] = function ($app) { + return new AccessListener( + $app['security.token_storage'], + $app['security.access_manager'], + $app['security.access_map'], + $app['security.authentication_manager'], + $app['logger'] + ); + }; + + $app['security.access_map'] = function ($app) { + $map = new AccessMap(); + + foreach ($app['security.access_rules'] as $rule) { + if (is_string($rule[0])) { + $rule[0] = new RequestMatcher($rule[0]); + } elseif (is_array($rule[0])) { + $rule[0] += array( + 'path' => null, + 'host' => null, + 'methods' => null, + 'ips' => null, + 'attributes' => array(), + 'schemes' => null, + ); + $rule[0] = new RequestMatcher($rule[0]['path'], $rule[0]['host'], $rule[0]['methods'], $rule[0]['ips'], $rule[0]['attributes'], $rule[0]['schemes']); + } + $map->add($rule[0], (array) $rule[1], isset($rule[2]) ? $rule[2] : null); + } + + return $map; + }; + + $app['security.trust_resolver'] = function ($app) { + return new AuthenticationTrustResolver('Symfony\Component\Security\Core\Authentication\Token\AnonymousToken', 'Symfony\Component\Security\Core\Authentication\Token\RememberMeToken'); + }; + + $app['security.session_strategy'] = function ($app) { + return new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE); + }; + + $app['security.http_utils'] = function ($app) { + return new HttpUtils($app['url_generator'], $app['request_matcher']); + }; + + $app['security.last_error'] = $app->protect(function (Request $request) { + if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { + return $request->attributes->get(Security::AUTHENTICATION_ERROR)->getMessage(); + } + + $session = $request->getSession(); + if ($session && $session->has(Security::AUTHENTICATION_ERROR)) { + $message = $session->get(Security::AUTHENTICATION_ERROR)->getMessage(); + $session->remove(Security::AUTHENTICATION_ERROR); + + return $message; + } + }); + + // prototypes (used by the Firewall Map) + + $app['security.context_listener._proto'] = $app->protect(function ($providerKey, $userProviders) use ($app) { + return function () use ($app, $userProviders, $providerKey) { + return new ContextListener( + $app['security.token_storage'], + $userProviders, + $providerKey, + $app['logger'], + $app['dispatcher'] + ); + }; + }); + + $app['security.user_provider.inmemory._proto'] = $app->protect(function ($params) use ($app) { + return function () use ($app, $params) { + $users = array(); + foreach ($params as $name => $user) { + $users[$name] = array('roles' => (array) $user[0], 'password' => $user[1]); + } + + return new InMemoryUserProvider($users); + }; + }); + + $app['security.exception_listener._proto'] = $app->protect(function ($entryPoint, $name, $accessDeniedHandler = null) use ($app) { + return function () use ($app, $entryPoint, $name, $accessDeniedHandler) { + return new ExceptionListener( + $app['security.token_storage'], + $app['security.trust_resolver'], + $app['security.http_utils'], + $name, + $app[$entryPoint], + null, // errorPage + $accessDeniedHandler, + $app['logger'] + ); + }; + }); + + $app['security.authentication.success_handler._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($name, $options, $app) { + $handler = new DefaultAuthenticationSuccessHandler( + $app['security.http_utils'], + $options + ); + $handler->setProviderKey($name); + + return $handler; + }; + }); + + $app['security.authentication.failure_handler._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($name, $options, $app) { + return new DefaultAuthenticationFailureHandler( + $app, + $app['security.http_utils'], + $options, + $app['logger'] + ); + }; + }); + + $app['security.authentication_listener.guard._proto'] = $app->protect(function ($providerKey, $options) use ($app, $that) { + return function () use ($app, $providerKey, $options, $that) { + if (!isset($app['security.authentication.guard_handler'])) { + $app['security.authentication.guard_handler'] = new GuardAuthenticatorHandler($app['security.token_storage'], $app['dispatcher']); + } + + $authenticators = array(); + foreach ($options['authenticators'] as $authenticatorId) { + $authenticators[] = $app[$authenticatorId]; + } + + return new GuardAuthenticationListener( + $app['security.authentication.guard_handler'], + $app['security.authentication_manager'], + $providerKey, + $authenticators, + $app['logger'] + ); + }; + }); + + $app['security.authentication_listener.form._proto'] = $app->protect(function ($name, $options) use ($app, $that) { + return function () use ($app, $name, $options, $that) { + $that->addFakeRoute( + 'match', + $tmp = isset($options['check_path']) ? $options['check_path'] : '/login_check', + str_replace('/', '_', ltrim($tmp, '/')) + ); + + $class = isset($options['listener_class']) ? $options['listener_class'] : 'Symfony\\Component\\Security\\Http\\Firewall\\UsernamePasswordFormAuthenticationListener'; + + if (!isset($app['security.authentication.success_handler.'.$name])) { + $app['security.authentication.success_handler.'.$name] = $app['security.authentication.success_handler._proto']($name, $options); + } + + if (!isset($app['security.authentication.failure_handler.'.$name])) { + $app['security.authentication.failure_handler.'.$name] = $app['security.authentication.failure_handler._proto']($name, $options); + } + + return new $class( + $app['security.token_storage'], + $app['security.authentication_manager'], + isset($app['security.session_strategy.'.$name]) ? $app['security.session_strategy.'.$name] : $app['security.session_strategy'], + $app['security.http_utils'], + $name, + $app['security.authentication.success_handler.'.$name], + $app['security.authentication.failure_handler.'.$name], + $options, + $app['logger'], + $app['dispatcher'], + isset($options['with_csrf']) && $options['with_csrf'] && isset($app['csrf.token_manager']) ? $app['csrf.token_manager'] : null + ); + }; + }); + + $app['security.authentication_listener.http._proto'] = $app->protect(function ($providerKey, $options) use ($app) { + return function () use ($app, $providerKey, $options) { + return new BasicAuthenticationListener( + $app['security.token_storage'], + $app['security.authentication_manager'], + $providerKey, + $app['security.entry_point.'.$providerKey.'.http'], + $app['logger'] + ); + }; + }); + + $app['security.authentication_listener.anonymous._proto'] = $app->protect(function ($providerKey, $options) use ($app) { + return function () use ($app, $providerKey, $options) { + return new AnonymousAuthenticationListener( + $app['security.token_storage'], + $providerKey, + $app['logger'] + ); + }; + }); + + $app['security.authentication.logout_handler._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($name, $options, $app) { + return new DefaultLogoutSuccessHandler( + $app['security.http_utils'], + isset($options['target_url']) ? $options['target_url'] : '/' + ); + }; + }); + + $app['security.authentication_listener.logout._proto'] = $app->protect(function ($name, $options) use ($app, $that) { + return function () use ($app, $name, $options, $that) { + $that->addFakeRoute( + 'get', + $tmp = isset($options['logout_path']) ? $options['logout_path'] : '/logout', + str_replace('/', '_', ltrim($tmp, '/')) + ); + + if (!isset($app['security.authentication.logout_handler.'.$name])) { + $app['security.authentication.logout_handler.'.$name] = $app['security.authentication.logout_handler._proto']($name, $options); + } + + $listener = new LogoutListener( + $app['security.token_storage'], + $app['security.http_utils'], + $app['security.authentication.logout_handler.'.$name], + $options, + isset($options['with_csrf']) && $options['with_csrf'] && isset($app['csrf.token_manager']) ? $app['csrf.token_manager'] : null + ); + + $invalidateSession = isset($options['invalidate_session']) ? $options['invalidate_session'] : true; + if (true === $invalidateSession && false === $options['stateless']) { + $listener->addHandler(new SessionLogoutHandler()); + } + + return $listener; + }; + }); + + $app['security.authentication_listener.switch_user._proto'] = $app->protect(function ($name, $options) use ($app, $that) { + return function () use ($app, $name, $options, $that) { + return new SwitchUserListener( + $app['security.token_storage'], + $app['security.user_provider.'.$name], + $app['security.user_checker'], + $name, + $app['security.access_manager'], + $app['logger'], + isset($options['parameter']) ? $options['parameter'] : '_switch_user', + isset($options['role']) ? $options['role'] : 'ROLE_ALLOWED_TO_SWITCH', + $app['dispatcher'] + ); + }; + }); + + $app['security.entry_point.form._proto'] = $app->protect(function ($name, array $options) use ($app) { + return function () use ($app, $options) { + $loginPath = isset($options['login_path']) ? $options['login_path'] : '/login'; + $useForward = isset($options['use_forward']) ? $options['use_forward'] : false; + + return new FormAuthenticationEntryPoint($app, $app['security.http_utils'], $loginPath, $useForward); + }; + }); + + $app['security.entry_point.http._proto'] = $app->protect(function ($name, array $options) use ($app) { + return function () use ($app, $name, $options) { + return new BasicAuthenticationEntryPoint(isset($options['real_name']) ? $options['real_name'] : 'Secured'); + }; + }); + + $app['security.entry_point.guard._proto'] = $app->protect(function ($name, array $options) use ($app) { + if (isset($options['entry_point'])) { + // if it's configured explicitly, use it! + return $app[$options['entry_point']]; + } + $authenticatorIds = $options['authenticators']; + if (count($authenticatorIds) == 1) { + // if there is only one authenticator, use that as the entry point + return $app[reset($authenticatorIds)]; + } + // we have multiple entry points - we must ask them to configure one + throw new \LogicException(sprintf( + 'Because you have multiple guard configurators, you need to set the "guard.entry_point" key to one of your configurators (%s)', + implode(', ', $authenticatorIds) + )); + }); + + $app['security.authentication_provider.dao._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($app, $name) { + return new DaoAuthenticationProvider( + $app['security.user_provider.'.$name], + $app['security.user_checker'], + $name, + $app['security.encoder_factory'], + $app['security.hide_user_not_found'] + ); + }; + }); + + $app['security.authentication_provider.guard._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($app, $name, $options) { + $authenticators = array(); + foreach ($options['authenticators'] as $authenticatorId) { + $authenticators[] = $app[$authenticatorId]; + } + + return new GuardAuthenticationProvider( + $authenticators, + $app['security.user_provider.'.$name], + $name, + $app['security.user_checker'] + ); + }; + }); + + $app['security.authentication_provider.anonymous._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($app, $name) { + return new AnonymousAuthenticationProvider($name); + }; + }); + + if (isset($app['validator'])) { + $app['security.validator.user_password_validator'] = function ($app) { + return new UserPasswordValidator($app['security.token_storage'], $app['security.encoder_factory']); + }; + + $app['validator.validator_service_ids'] = array_merge($app['validator.validator_service_ids'], array('security.validator.user_password' => 'security.validator.user_password_validator')); + } + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['security.firewall']); + } + + public function connect(Application $app) + { + $controllers = $app['controllers_factory']; + foreach ($this->fakeRoutes as $route) { + list($method, $pattern, $name) = $route; + + $controllers->$method($pattern)->run(null)->bind($name); + } + + return $controllers; + } + + public function boot(Application $app) + { + $app->mount('/', $this->connect($app)); + } + + public function addFakeRoute($method, $pattern, $name) + { + $this->fakeRoutes[] = array($method, $pattern, $name); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/SerializerServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/SerializerServiceProvider.php new file mode 100644 index 00000000..8986abef --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SerializerServiceProvider.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Encoder\XmlEncoder; +use Symfony\Component\Serializer\Normalizer\CustomNormalizer; +use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; + +/** + * Symfony Serializer component Provider. + * + * @author Fabien Potencier + * @author Marijn Huizendveld + */ +class SerializerServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc} + * + * This method registers a serializer service. {@link http://api.symfony.com/master/Symfony/Component/Serializer/Serializer.html + * The service is provided by the Symfony Serializer component}. + */ + public function register(Container $app) + { + $app['serializer'] = function ($app) { + return new Serializer($app['serializer.normalizers'], $app['serializer.encoders']); + }; + + $app['serializer.encoders'] = function () { + return array(new JsonEncoder(), new XmlEncoder()); + }; + + $app['serializer.normalizers'] = function () { + return array(new CustomNormalizer(), new GetSetMethodNormalizer()); + }; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/ServiceControllerServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/ServiceControllerServiceProvider.php new file mode 100644 index 00000000..1c38adc9 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/ServiceControllerServiceProvider.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\ServiceControllerResolver; + +class ServiceControllerServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app->extend('resolver', function ($resolver, $app) { + return new ServiceControllerResolver($resolver, $app['callback_resolver']); + }); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Session/SessionListener.php b/vendor/silex/silex/src/Silex/Provider/Session/SessionListener.php new file mode 100644 index 00000000..aba4c4e8 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Session/SessionListener.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Session; + +use Pimple\Container; +use Symfony\Component\HttpKernel\EventListener\SessionListener as BaseSessionListener; + +/** + * Sets the session in the request. + * + * @author Fabien Potencier + */ +class SessionListener extends BaseSessionListener +{ + private $app; + + public function __construct(Container $app) + { + $this->app = $app; + } + + protected function getSession() + { + if (!isset($this->app['session'])) { + return; + } + + return $this->app['session']; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Session/TestSessionListener.php b/vendor/silex/silex/src/Silex/Provider/Session/TestSessionListener.php new file mode 100644 index 00000000..ab98eb12 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Session/TestSessionListener.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Session; + +use Pimple\Container; +use Symfony\Component\HttpKernel\EventListener\TestSessionListener as BaseTestSessionListener; + +/** + * Simulates sessions for testing purpose. + * + * @author Fabien Potencier + */ +class TestSessionListener extends BaseTestSessionListener +{ + private $app; + + public function __construct(Container $app) + { + $this->app = $app; + } + + protected function getSession() + { + if (!isset($this->app['session'])) { + return; + } + + return $this->app['session']; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/SessionServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/SessionServiceProvider.php new file mode 100644 index 00000000..a51e230e --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SessionServiceProvider.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Silex\Provider\Session\SessionListener; +use Silex\Provider\Session\TestSessionListener; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage; +use Symfony\Component\HttpFoundation\Session\Session; + +/** + * Symfony HttpFoundation component Provider for sessions. + * + * @author Fabien Potencier + */ +class SessionServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['session.test'] = false; + + $app['session'] = function ($app) { + return new Session($app['session.storage'], $app['session.attribute_bag'], $app['session.flash_bag']); + }; + + $app['session.storage'] = function ($app) { + if ($app['session.test']) { + return $app['session.storage.test']; + } + + return $app['session.storage.native']; + }; + + $app['session.storage.handler'] = function ($app) { + return new NativeFileSessionHandler($app['session.storage.save_path']); + }; + + $app['session.storage.native'] = function ($app) { + return new NativeSessionStorage( + $app['session.storage.options'], + $app['session.storage.handler'] + ); + }; + + $app['session.listener'] = function ($app) { + return new SessionListener($app); + }; + + $app['session.storage.test'] = function () { + return new MockFileSessionStorage(); + }; + + $app['session.listener.test'] = function ($app) { + return new TestSessionListener($app); + }; + + $app['session.storage.options'] = array(); + $app['session.default_locale'] = 'en'; + $app['session.storage.save_path'] = null; + $app['session.attribute_bag'] = null; + $app['session.flash_bag'] = null; + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['session.listener']); + + if ($app['session.test']) { + $app['dispatcher']->addSubscriber($app['session.listener.test']); + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/SwiftmailerServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/SwiftmailerServiceProvider.php new file mode 100644 index 00000000..c3dce6ce --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SwiftmailerServiceProvider.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; + +/** + * Swiftmailer Provider. + * + * @author Fabien Potencier + */ +class SwiftmailerServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['swiftmailer.options'] = array(); + $app['swiftmailer.use_spool'] = true; + + $app['mailer.initialized'] = false; + + $app['mailer'] = function ($app) { + $app['mailer.initialized'] = true; + $transport = $app['swiftmailer.use_spool'] ? $app['swiftmailer.spooltransport'] : $app['swiftmailer.transport']; + + return new \Swift_Mailer($transport); + }; + + $app['swiftmailer.spooltransport'] = function ($app) { + return new \Swift_Transport_SpoolTransport($app['swiftmailer.transport.eventdispatcher'], $app['swiftmailer.spool']); + }; + + $app['swiftmailer.spool'] = function ($app) { + return new \Swift_MemorySpool(); + }; + + $app['swiftmailer.transport'] = function ($app) { + $transport = new \Swift_Transport_EsmtpTransport( + $app['swiftmailer.transport.buffer'], + array($app['swiftmailer.transport.authhandler']), + $app['swiftmailer.transport.eventdispatcher'] + ); + + $options = $app['swiftmailer.options'] = array_replace(array( + 'host' => 'localhost', + 'port' => 25, + 'username' => '', + 'password' => '', + 'encryption' => null, + 'auth_mode' => null, + ), $app['swiftmailer.options']); + + $transport->setHost($options['host']); + $transport->setPort($options['port']); + $transport->setEncryption($options['encryption']); + $transport->setUsername($options['username']); + $transport->setPassword($options['password']); + $transport->setAuthMode($options['auth_mode']); + + return $transport; + }; + + $app['swiftmailer.transport.buffer'] = function () { + return new \Swift_Transport_StreamBuffer(new \Swift_StreamFilters_StringReplacementFilterFactory()); + }; + + $app['swiftmailer.transport.authhandler'] = function () { + return new \Swift_Transport_Esmtp_AuthHandler(array( + new \Swift_Transport_Esmtp_Auth_CramMd5Authenticator(), + new \Swift_Transport_Esmtp_Auth_LoginAuthenticator(), + new \Swift_Transport_Esmtp_Auth_PlainAuthenticator(), + )); + }; + + $app['swiftmailer.transport.eventdispatcher'] = function ($app) { + $dispatcher = new \Swift_Events_SimpleEventDispatcher(); + + $plugins = $app['swiftmailer.plugins']; + + if (null !== $app['swiftmailer.sender_address']) { + $plugins[] = new \Swift_Plugins_ImpersonatePlugin($app['swiftmailer.sender_address']); + } + + if (!empty($app['swiftmailer.delivery_addresses'])) { + $plugins[] = new \Swift_Plugins_RedirectingPlugin( + $app['swiftmailer.delivery_addresses'], + $app['swiftmailer.delivery_whitelist'] + ); + } + + foreach ($plugins as $plugin) { + $dispatcher->bindEventListener($plugin); + } + + return $dispatcher; + }; + + $app['swiftmailer.plugins'] = function ($app) { + return array(); + }; + + $app['swiftmailer.sender_address'] = null; + $app['swiftmailer.delivery_addresses'] = array(); + $app['swiftmailer.delivery_whitelist'] = array(); + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + // Event has no typehint as it can be either a PostResponseEvent or a ConsoleTerminateEvent + $onTerminate = function ($event) use ($app) { + // To speed things up (by avoiding Swift Mailer initialization), flush + // messages only if our mailer has been created (potentially used) + if ($app['mailer.initialized'] && $app['swiftmailer.use_spool'] && $app['swiftmailer.spooltransport'] instanceof \Swift_Transport_SpoolTransport) { + $app['swiftmailer.spooltransport']->getSpool()->flushQueue($app['swiftmailer.transport']); + } + }; + + $dispatcher->addListener(KernelEvents::TERMINATE, $onTerminate); + + if (class_exists('Symfony\Component\Console\ConsoleEvents')) { + $dispatcher->addListener(ConsoleEvents::TERMINATE, $onTerminate); + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/TranslationServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/TranslationServiceProvider.php new file mode 100644 index 00000000..a9ee55c9 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/TranslationServiceProvider.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Translation\Translator; +use Symfony\Component\Translation\MessageSelector; +use Symfony\Component\Translation\Loader\ArrayLoader; +use Symfony\Component\Translation\Loader\XliffFileLoader; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\EventListener\TranslatorListener; +use Silex\Api\EventListenerProviderInterface; + +/** + * Symfony Translation component Provider. + * + * @author Fabien Potencier + */ +class TranslationServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['translator'] = function ($app) { + if (!isset($app['locale'])) { + throw new \LogicException('You must define \'locale\' parameter or register the LocaleServiceProvider to use the TranslationServiceProvider'); + } + + $translator = new Translator($app['locale'], $app['translator.message_selector'], $app['translator.cache_dir'], $app['debug']); + $translator->setFallbackLocales($app['locale_fallbacks']); + $translator->addLoader('array', new ArrayLoader()); + $translator->addLoader('xliff', new XliffFileLoader()); + + if (isset($app['validator'])) { + $r = new \ReflectionClass('Symfony\Component\Validator\Validation'); + $file = dirname($r->getFilename()).'/Resources/translations/validators.'.$app['locale'].'.xlf'; + if (file_exists($file)) { + $translator->addResource('xliff', $file, $app['locale'], 'validators'); + } + } + + if (isset($app['form.factory'])) { + $r = new \ReflectionClass('Symfony\Component\Form\Form'); + $file = dirname($r->getFilename()).'/Resources/translations/validators.'.$app['locale'].'.xlf'; + if (file_exists($file)) { + $translator->addResource('xliff', $file, $app['locale'], 'validators'); + } + } + + // Register default resources + foreach ($app['translator.resources'] as $resource) { + $translator->addResource($resource[0], $resource[1], $resource[2], $resource[3]); + } + + foreach ($app['translator.domains'] as $domain => $data) { + foreach ($data as $locale => $messages) { + $translator->addResource('array', $messages, $locale, $domain); + } + } + + return $translator; + }; + + if (isset($app['request_stack'])) { + $app['translator.listener'] = function ($app) { + return new TranslatorListener($app['translator'], $app['request_stack']); + }; + } + + $app['translator.message_selector'] = function () { + return new MessageSelector(); + }; + + $app['translator.resources'] = function ($app) { + return array(); + }; + + $app['translator.domains'] = array(); + $app['locale_fallbacks'] = array('en'); + $app['translator.cache_dir'] = null; + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + if (isset($app['translator.listener'])) { + $dispatcher->addSubscriber($app['translator.listener']); + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Twig/RuntimeLoader.php b/vendor/silex/silex/src/Silex/Provider/Twig/RuntimeLoader.php new file mode 100644 index 00000000..9a7aa915 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Twig/RuntimeLoader.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Twig; + +use Pimple\Container; + +/** + * Loads Twig extension runtimes via Pimple. + * + * @author Fabien Potencier + */ +class RuntimeLoader implements \Twig_RuntimeLoaderInterface +{ + private $container; + private $mapping; + + public function __construct(Container $container, array $mapping) + { + $this->container = $container; + $this->mapping = $mapping; + } + + /** + * {@inheritdoc} + */ + public function load($class) + { + if (isset($this->mapping[$class])) { + return $this->container[$this->mapping[$class]]; + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/TwigServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/TwigServiceProvider.php new file mode 100644 index 00000000..f15a93bf --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/TwigServiceProvider.php @@ -0,0 +1,190 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Provider\Twig\RuntimeLoader; +use Symfony\Bridge\Twig\AppVariable; +use Symfony\Bridge\Twig\Extension\AssetExtension; +use Symfony\Bridge\Twig\Extension\DumpExtension; +use Symfony\Bridge\Twig\Extension\RoutingExtension; +use Symfony\Bridge\Twig\Extension\TranslationExtension; +use Symfony\Bridge\Twig\Extension\FormExtension; +use Symfony\Bridge\Twig\Extension\SecurityExtension; +use Symfony\Bridge\Twig\Extension\HttpFoundationExtension; +use Symfony\Bridge\Twig\Extension\HttpKernelExtension; +use Symfony\Bridge\Twig\Extension\WebLinkExtension; +use Symfony\Bridge\Twig\Form\TwigRendererEngine; +use Symfony\Bridge\Twig\Form\TwigRenderer; +use Symfony\Bridge\Twig\Extension\HttpKernelRuntime; +use Symfony\Component\WebLink\HttpHeaderSerializer; + +/** + * Twig integration for Silex. + * + * @author Fabien Potencier + */ +class TwigServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['twig.options'] = array(); + $app['twig.form.templates'] = array('form_div_layout.html.twig'); + $app['twig.path'] = array(); + $app['twig.templates'] = array(); + + $app['twig.date.format'] = 'F j, Y H:i'; + $app['twig.date.interval_format'] = '%d days'; + $app['twig.date.timezone'] = null; + + $app['twig.number_format.decimals'] = 0; + $app['twig.number_format.decimal_point'] = '.'; + $app['twig.number_format.thousands_separator'] = ','; + + $app['twig'] = function ($app) { + $app['twig.options'] = array_replace( + array( + 'charset' => $app['charset'], + 'debug' => $app['debug'], + 'strict_variables' => $app['debug'], + ), $app['twig.options'] + ); + + $twig = $app['twig.environment_factory']($app); + // registered for BC, but should not be used anymore + // deprecated and should probably be removed in Silex 3.0 + $twig->addGlobal('app', $app); + + $coreExtension = $twig->getExtension('Twig_Extension_Core'); + + $coreExtension->setDateFormat($app['twig.date.format'], $app['twig.date.interval_format']); + + if (null !== $app['twig.date.timezone']) { + $coreExtension->setTimezone($app['twig.date.timezone']); + } + + $coreExtension->setNumberFormat($app['twig.number_format.decimals'], $app['twig.number_format.decimal_point'], $app['twig.number_format.thousands_separator']); + + if ($app['debug']) { + $twig->addExtension(new \Twig_Extension_Debug()); + } + + if (class_exists('Symfony\Bridge\Twig\Extension\RoutingExtension')) { + $app['twig.app_variable'] = function ($app) { + $var = new AppVariable(); + if (isset($app['security.token_storage'])) { + $var->setTokenStorage($app['security.token_storage']); + } + if (isset($app['request_stack'])) { + $var->setRequestStack($app['request_stack']); + } + $var->setDebug($app['debug']); + + return $var; + }; + + $twig->addGlobal('global', $app['twig.app_variable']); + + if (isset($app['request_stack'])) { + $twig->addExtension(new HttpFoundationExtension($app['request_stack'])); + $twig->addExtension(new RoutingExtension($app['url_generator'])); + } + + if (isset($app['translator'])) { + $twig->addExtension(new TranslationExtension($app['translator'])); + } + + if (isset($app['security.authorization_checker'])) { + $twig->addExtension(new SecurityExtension($app['security.authorization_checker'])); + } + + if (isset($app['fragment.handler'])) { + $app['fragment.renderer.hinclude']->setTemplating($twig); + + $twig->addExtension(new HttpKernelExtension($app['fragment.handler'])); + } + + if (isset($app['assets.packages'])) { + $twig->addExtension(new AssetExtension($app['assets.packages'])); + } + + if (isset($app['form.factory'])) { + $app['twig.form.engine'] = function ($app) use ($twig) { + return new TwigRendererEngine($app['twig.form.templates'], $twig); + }; + + $app['twig.form.renderer'] = function ($app) { + $csrfTokenManager = isset($app['csrf.token_manager']) ? $app['csrf.token_manager'] : null; + + return new TwigRenderer($app['twig.form.engine'], $csrfTokenManager); + }; + + $twig->addExtension(new FormExtension(class_exists(HttpKernelRuntime::class) ? null : $app['twig.form.renderer'])); + + // add loader for Symfony built-in form templates + $reflected = new \ReflectionClass('Symfony\Bridge\Twig\Extension\FormExtension'); + $path = dirname($reflected->getFileName()).'/../Resources/views/Form'; + $app['twig.loader']->addLoader(new \Twig_Loader_Filesystem($path)); + } + + if (isset($app['var_dumper.cloner'])) { + $twig->addExtension(new DumpExtension($app['var_dumper.cloner'])); + } + + if (class_exists(HttpKernelRuntime::class)) { + $twig->addRuntimeLoader($app['twig.runtime_loader']); + } + + if (class_exists(HttpHeaderSerializer::class) && class_exists(WebLinkExtension::class)) { + $twig->addExtension(new WebLinkExtension($app['request_stack'])); + } + } + + return $twig; + }; + + $app['twig.loader.filesystem'] = function ($app) { + return new \Twig_Loader_Filesystem($app['twig.path']); + }; + + $app['twig.loader.array'] = function ($app) { + return new \Twig_Loader_Array($app['twig.templates']); + }; + + $app['twig.loader'] = function ($app) { + return new \Twig_Loader_Chain(array( + $app['twig.loader.array'], + $app['twig.loader.filesystem'], + )); + }; + + $app['twig.environment_factory'] = $app->protect(function ($app) { + return new \Twig_Environment($app['twig.loader'], $app['twig.options']); + }); + + $app['twig.runtime.httpkernel'] = function ($app) { + return new HttpKernelRuntime($app['fragment.handler']); + }; + + $app['twig.runtimes'] = function ($app) { + return array( + HttpKernelRuntime::class => 'twig.runtime.httpkernel', + TwigRenderer::class => 'twig.form.renderer', + ); + }; + + $app['twig.runtime_loader'] = function ($app) { + return new RuntimeLoader($app, $app['twig.runtimes']); + }; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Validator/ConstraintValidatorFactory.php b/vendor/silex/silex/src/Silex/Provider/Validator/ConstraintValidatorFactory.php new file mode 100644 index 00000000..9f5e499d --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Validator/ConstraintValidatorFactory.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Validator; + +use Pimple\Container; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidatorFactory as BaseConstraintValidatorFactory; + +/** + * Uses a service container to create constraint validators with dependencies. + * + * @author Kris Wallsmith + * @author Alex Kalyvitis + */ +class ConstraintValidatorFactory extends BaseConstraintValidatorFactory +{ + /** + * @var Container + */ + protected $container; + + /** + * @var array + */ + protected $serviceNames; + + /** + * Constructor. + * + * @param Container $container DI container + * @param array $serviceNames Validator service names + */ + public function __construct(Container $container, array $serviceNames = array(), $propertyAccessor = null) + { + parent::__construct($propertyAccessor); + + $this->container = $container; + $this->serviceNames = $serviceNames; + } + + /** + * {@inheritdoc} + */ + public function getInstance(Constraint $constraint) + { + $name = $constraint->validatedBy(); + + if (isset($this->serviceNames[$name])) { + return $this->container[$this->serviceNames[$name]]; + } + + return parent::getInstance($constraint); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/ValidatorServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/ValidatorServiceProvider.php new file mode 100644 index 00000000..d89a3cb5 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/ValidatorServiceProvider.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Provider\Validator\ConstraintValidatorFactory; +use Symfony\Component\Validator\Validator; +use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory; +use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader; +use Symfony\Component\Validator\Validation; + +/** + * Symfony Validator component Provider. + * + * @author Fabien Potencier + */ +class ValidatorServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['validator'] = function ($app) { + return $app['validator.builder']->getValidator(); + }; + + $app['validator.builder'] = function ($app) { + $builder = Validation::createValidatorBuilder(); + $builder->setConstraintValidatorFactory($app['validator.validator_factory']); + $builder->setTranslationDomain('validators'); + $builder->addObjectInitializers($app['validator.object_initializers']); + $builder->setMetadataFactory($app['validator.mapping.class_metadata_factory']); + if (isset($app['translator'])) { + $builder->setTranslator($app['translator']); + } + + return $builder; + }; + + $app['validator.mapping.class_metadata_factory'] = function ($app) { + return new LazyLoadingMetadataFactory(new StaticMethodLoader()); + }; + + $app['validator.validator_factory'] = function () use ($app) { + return new ConstraintValidatorFactory($app, $app['validator.validator_service_ids']); + }; + + $app['validator.object_initializers'] = function ($app) { + return array(); + }; + + $app['validator.validator_service_ids'] = array(); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/VarDumperServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/VarDumperServiceProvider.php new file mode 100644 index 00000000..7c40b5ee --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/VarDumperServiceProvider.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Application; +use Silex\Api\BootableProviderInterface; +use Symfony\Component\VarDumper\VarDumper; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * Symfony Var Dumper component Provider. + * + * @author Fabien Potencier + */ +class VarDumperServiceProvider implements ServiceProviderInterface, BootableProviderInterface +{ + public function register(Container $app) + { + $app['var_dumper.cli_dumper'] = function ($app) { + return new CliDumper($app['var_dumper.dump_destination'], $app['charset']); + }; + + $app['var_dumper.cloner'] = function ($app) { + return new VarCloner(); + }; + + $app['var_dumper.dump_destination'] = null; + } + + public function boot(Application $app) + { + if (!$app['debug']) { + return; + } + + // This code is here to lazy load the dump stack. This default + // configuration for CLI mode is overridden in HTTP mode on + // 'kernel.request' event + VarDumper::setHandler(function ($var) use ($app) { + VarDumper::setHandler($handler = function ($var) use ($app) { + $app['var_dumper.cli_dumper']->dump($app['var_dumper.cloner']->cloneVar($var)); + }); + $handler($var); + }); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/composer.json b/vendor/silex/silex/src/Silex/Provider/composer.json new file mode 100644 index 00000000..a1e9fbfe --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/composer.json @@ -0,0 +1,31 @@ +{ + "minimum-stability": "dev", + "name": "silex/providers", + "description": "The Silex providers", + "keywords": ["microframework"], + "homepage": "http://silex.sensiolabs.org", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0", + "silex/api": "~2.2" + }, + "autoload": { + "psr-4": { "Silex\\Provider\\": "" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + } +} diff --git a/vendor/silex/silex/src/Silex/Route.php b/vendor/silex/silex/src/Silex/Route.php new file mode 100644 index 00000000..99e82d87 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Route.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Symfony\Component\Routing\Route as BaseRoute; + +/** + * A wrapper for a controller, mapped to a route. + * + * @author Fabien Potencier + */ +class Route extends BaseRoute +{ + /** + * Constructor. + * + * Available options: + * + * * compiler_class: A class name able to compile this route instance (RouteCompiler by default) + * + * @param string $path The path pattern to match + * @param array $defaults An array of default parameter values + * @param array $requirements An array of requirements for parameters (regexes) + * @param array $options An array of options + * @param string $host The host pattern to match + * @param string|array $schemes A required URI scheme or an array of restricted schemes + * @param string|array $methods A required HTTP method or an array of restricted methods + */ + public function __construct($path = '/', array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array()) + { + // overridden constructor to make $path optional + parent::__construct($path, $defaults, $requirements, $options, $host, $schemes, $methods); + } + + /** + * Sets the route code that should be executed when matched. + * + * @param callable $to PHP callback that returns the response when matched + * + * @return Route $this The current Route instance + */ + public function run($to) + { + $this->setDefault('_controller', $to); + + return $this; + } + + /** + * Sets the requirement for a route variable. + * + * @param string $variable The variable name + * @param string $regexp The regexp to apply + * + * @return Route $this The current route instance + */ + public function assert($variable, $regexp) + { + $this->setRequirement($variable, $regexp); + + return $this; + } + + /** + * Sets the default value for a route variable. + * + * @param string $variable The variable name + * @param mixed $default The default value + * + * @return Route $this The current Route instance + */ + public function value($variable, $default) + { + $this->setDefault($variable, $default); + + return $this; + } + + /** + * Sets a converter for a route variable. + * + * @param string $variable The variable name + * @param mixed $callback A PHP callback that converts the original value + * + * @return Route $this The current Route instance + */ + public function convert($variable, $callback) + { + $converters = $this->getOption('_converters'); + $converters[$variable] = $callback; + $this->setOption('_converters', $converters); + + return $this; + } + + /** + * Sets the requirement for the HTTP method. + * + * @param string $method The HTTP method name. Multiple methods can be supplied, delimited by a pipe character '|', eg. 'GET|POST' + * + * @return Route $this The current Route instance + */ + public function method($method) + { + $this->setMethods(explode('|', $method)); + + return $this; + } + + /** + * Sets the requirement of host on this Route. + * + * @param string $host The host for which this route should be enabled + * + * @return Route $this The current Route instance + */ + public function host($host) + { + $this->setHost($host); + + return $this; + } + + /** + * Sets the requirement of HTTP (no HTTPS) on this Route. + * + * @return Route $this The current Route instance + */ + public function requireHttp() + { + $this->setSchemes('http'); + + return $this; + } + + /** + * Sets the requirement of HTTPS on this Route. + * + * @return Route $this The current Route instance + */ + public function requireHttps() + { + $this->setSchemes('https'); + + return $this; + } + + /** + * Sets a callback to handle before triggering the route callback. + * + * @param mixed $callback A PHP callback to be triggered when the Route is matched, just before the route callback + * + * @return Route $this The current Route instance + */ + public function before($callback) + { + $callbacks = $this->getOption('_before_middlewares'); + $callbacks[] = $callback; + $this->setOption('_before_middlewares', $callbacks); + + return $this; + } + + /** + * Sets a callback to handle after the route callback. + * + * @param mixed $callback A PHP callback to be triggered after the route callback + * + * @return Route $this The current Route instance + */ + public function after($callback) + { + $callbacks = $this->getOption('_after_middlewares'); + $callbacks[] = $callback; + $this->setOption('_after_middlewares', $callbacks); + + return $this; + } + + /** + * Sets a condition for the route to match. + * + * @param string $condition The condition + * + * @return Route $this The current Route instance + */ + public function when($condition) + { + $this->setCondition($condition); + + return $this; + } +} diff --git a/vendor/silex/silex/src/Silex/Route/SecurityTrait.php b/vendor/silex/silex/src/Silex/Route/SecurityTrait.php new file mode 100644 index 00000000..d42ba2fb --- /dev/null +++ b/vendor/silex/silex/src/Silex/Route/SecurityTrait.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Route; + +use Symfony\Component\Security\Core\Exception\AccessDeniedException; + +/** + * Security trait. + * + * @author Fabien Potencier + */ +trait SecurityTrait +{ + public function secure($roles) + { + $this->before(function ($request, $app) use ($roles) { + if (!$app['security.authorization_checker']->isGranted($roles)) { + throw new AccessDeniedException(); + } + }); + } +} diff --git a/vendor/silex/silex/src/Silex/ServiceControllerResolver.php b/vendor/silex/silex/src/Silex/ServiceControllerResolver.php new file mode 100644 index 00000000..87f91b04 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ServiceControllerResolver.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; + +/** + * Enables name_of_service:method_name syntax for declaring controllers. + * + * @link http://silex.sensiolabs.org/doc/providers/service_controller.html + */ +class ServiceControllerResolver implements ControllerResolverInterface +{ + protected $controllerResolver; + protected $callbackResolver; + + /** + * Constructor. + * + * @param ControllerResolverInterface $controllerResolver A ControllerResolverInterface instance to delegate to + * @param CallbackResolver $callbackResolver A service resolver instance + */ + public function __construct(ControllerResolverInterface $controllerResolver, CallbackResolver $callbackResolver) + { + $this->controllerResolver = $controllerResolver; + $this->callbackResolver = $callbackResolver; + } + + /** + * {@inheritdoc} + */ + public function getController(Request $request) + { + $controller = $request->attributes->get('_controller', null); + + if (!$this->callbackResolver->isValid($controller)) { + return $this->controllerResolver->getController($request); + } + + return $this->callbackResolver->convertCallback($controller); + } + + /** + * {@inheritdoc} + */ + public function getArguments(Request $request, $controller) + { + return $this->controllerResolver->getArguments($request, $controller); + } +} diff --git a/vendor/silex/silex/src/Silex/ViewListenerWrapper.php b/vendor/silex/silex/src/Silex/ViewListenerWrapper.php new file mode 100644 index 00000000..a67ec938 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ViewListenerWrapper.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; + +/** + * Wraps view listeners. + * + * @author Dave Marshall + */ +class ViewListenerWrapper +{ + private $app; + private $callback; + + /** + * Constructor. + * + * @param Application $app An Application instance + * @param mixed $callback + */ + public function __construct(Application $app, $callback) + { + $this->app = $app; + $this->callback = $callback; + } + + public function __invoke(GetResponseForControllerResultEvent $event) + { + $controllerResult = $event->getControllerResult(); + $callback = $this->app['callback_resolver']->resolveCallback($this->callback); + + if (!$this->shouldRun($callback, $controllerResult)) { + return; + } + + $response = call_user_func($callback, $controllerResult, $event->getRequest()); + + if ($response instanceof Response) { + $event->setResponse($response); + } elseif (null !== $response) { + $event->setControllerResult($response); + } + } + + private function shouldRun($callback, $controllerResult) + { + if (is_array($callback)) { + $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]); + } elseif (is_object($callback) && !$callback instanceof \Closure) { + $callbackReflection = new \ReflectionObject($callback); + $callbackReflection = $callbackReflection->getMethod('__invoke'); + } else { + $callbackReflection = new \ReflectionFunction($callback); + } + + if ($callbackReflection->getNumberOfParameters() > 0) { + $parameters = $callbackReflection->getParameters(); + $expectedControllerResult = $parameters[0]; + + if ($expectedControllerResult->getClass() && (!is_object($controllerResult) || !$expectedControllerResult->getClass()->isInstance($controllerResult))) { + return false; + } + + if ($expectedControllerResult->isArray() && !is_array($controllerResult)) { + return false; + } + + if (method_exists($expectedControllerResult, 'isCallable') && $expectedControllerResult->isCallable() && !is_callable($controllerResult)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/silex/silex/src/Silex/WebTestCase.php b/vendor/silex/silex/src/Silex/WebTestCase.php new file mode 100644 index 00000000..e72403a1 --- /dev/null +++ b/vendor/silex/silex/src/Silex/WebTestCase.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * WebTestCase is the base class for functional tests. + * + * @author Igor Wiedler + */ +abstract class WebTestCase extends TestCase +{ + /** + * HttpKernelInterface instance. + * + * @var HttpKernelInterface + */ + protected $app; + + /** + * PHPUnit setUp for setting up the application. + * + * Note: Child classes that define a setUp method must call + * parent::setUp(). + */ + protected function setUp() + { + $this->app = $this->createApplication(); + } + + /** + * Creates the application. + * + * @return HttpKernelInterface + */ + abstract public function createApplication(); + + /** + * Creates a Client. + * + * @param array $server Server parameters + * + * @return Client A Client instance + */ + public function createClient(array $server = array()) + { + if (!class_exists('Symfony\Component\BrowserKit\Client')) { + throw new \LogicException('Component "symfony/browser-kit" is required by WebTestCase.'.PHP_EOL.'Run composer require symfony/browser-kit'); + } + + return new Client($this->app, $server); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/FormApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/FormApplication.php new file mode 100644 index 00000000..5851a4c9 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/FormApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class FormApplication extends Application +{ + use Application\FormTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/FormTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/FormTraitTest.php new file mode 100644 index 00000000..1d36a0cb --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/FormTraitTest.php @@ -0,0 +1,45 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use PHPUnit\Framework\TestCase; +use Silex\Provider\FormServiceProvider; +use Symfony\Component\Form\FormBuilder; + +/** + * FormTrait test cases. + * + * @author Fabien Potencier + */ +class FormTraitTest extends TestCase +{ + public function testForm() + { + $this->assertInstanceOf(FormBuilder::class, $this->createApplication()->form()); + } + + public function testNamedForm() + { + $builder = $this->createApplication()->namedForm('foo'); + + $this->assertInstanceOf(FormBuilder::class, $builder); + $this->assertSame('foo', $builder->getName()); + } + + public function createApplication() + { + $app = new FormApplication(); + $app->register(new FormServiceProvider()); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/MonologApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/MonologApplication.php new file mode 100644 index 00000000..9fec12fb --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/MonologApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class MonologApplication extends Application +{ + use Application\MonologTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/MonologTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/MonologTraitTest.php new file mode 100644 index 00000000..73dcda65 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/MonologTraitTest.php @@ -0,0 +1,48 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use PHPUnit\Framework\TestCase; +use Silex\Provider\MonologServiceProvider; +use Monolog\Handler\TestHandler; +use Monolog\Logger; + +/** + * MonologTrait test cases. + * + * @author Fabien Potencier + */ +class MonologTraitTest extends TestCase +{ + public function testLog() + { + $app = $this->createApplication(); + + $app->log('Foo'); + $app->log('Bar', array(), Logger::DEBUG); + $this->assertTrue($app['monolog.handler']->hasInfo('Foo')); + $this->assertTrue($app['monolog.handler']->hasDebug('Bar')); + } + + public function createApplication() + { + $app = new MonologApplication(); + $app->register(new MonologServiceProvider(), array( + 'monolog.handler' => function () use ($app) { + return new TestHandler($app['monolog.level']); + }, + 'monolog.logfile' => 'php://memory', + )); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/SecurityApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/SecurityApplication.php new file mode 100644 index 00000000..dc85999e --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/SecurityApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class SecurityApplication extends Application +{ + use Application\SecurityTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/SecurityTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/SecurityTraitTest.php new file mode 100644 index 00000000..71cb3af6 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/SecurityTraitTest.php @@ -0,0 +1,91 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use PHPUnit\Framework\TestCase; +use Silex\Provider\SecurityServiceProvider; +use Symfony\Component\Security\Core\User\User; +use Symfony\Component\HttpFoundation\Request; + +/** + * SecurityTrait test cases. + * + * @author Fabien Potencier + */ +class SecurityTraitTest extends TestCase +{ + public function testEncodePassword() + { + $app = $this->createApplication(array( + 'fabien' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + )); + + $user = new User('foo', 'bar'); + $password = 'foo'; + $encoded = $app->encodePassword($user, $password); + + $this->assertTrue( + $app['security.encoder_factory']->getEncoder($user)->isPasswordValid($encoded, $password, $user->getSalt()) + ); + } + + /** + * @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException + */ + public function testIsGrantedWithoutTokenThrowsException() + { + $app = $this->createApplication(); + $app->get('/', function () { return 'foo'; }); + $app->handle(Request::create('/')); + $app->isGranted('ROLE_ADMIN'); + } + + public function testIsGranted() + { + $request = Request::create('/'); + + $app = $this->createApplication(array( + 'fabien' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + 'monique' => array('ROLE_USER', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + )); + $app->get('/', function () { return 'foo'; }); + + // User is Monique (ROLE_USER) + $request->headers->set('PHP_AUTH_USER', 'monique'); + $request->headers->set('PHP_AUTH_PW', 'foo'); + $app->handle($request); + $this->assertTrue($app->isGranted('ROLE_USER')); + $this->assertFalse($app->isGranted('ROLE_ADMIN')); + + // User is Fabien (ROLE_ADMIN) + $request->headers->set('PHP_AUTH_USER', 'fabien'); + $request->headers->set('PHP_AUTH_PW', 'foo'); + $app->handle($request); + $this->assertFalse($app->isGranted('ROLE_USER')); + $this->assertTrue($app->isGranted('ROLE_ADMIN')); + } + + public function createApplication($users = array()) + { + $app = new SecurityApplication(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + 'users' => $users, + ), + ), + )); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerApplication.php new file mode 100644 index 00000000..6a28d539 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class SwiftmailerApplication extends Application +{ + use Application\SwiftmailerTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerTraitTest.php new file mode 100644 index 00000000..34620b71 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerTraitTest.php @@ -0,0 +1,45 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use PHPUnit\Framework\TestCase; +use Silex\Provider\SwiftmailerServiceProvider; + +/** + * SwiftmailerTrait test cases. + * + * @author Fabien Potencier + */ +class SwiftmailerTraitTest extends TestCase +{ + public function testMail() + { + $app = $this->createApplication(); + + $message = $this->getMockBuilder('Swift_Message')->disableOriginalConstructor()->getMock(); + $app['mailer'] = $mailer = $this->getMockBuilder('Swift_Mailer')->disableOriginalConstructor()->getMock(); + $mailer->expects($this->once()) + ->method('send') + ->with($message) + ; + + $app->mail($message); + } + + public function createApplication() + { + $app = new SwiftmailerApplication(); + $app->register(new SwiftmailerServiceProvider()); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/TranslationApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/TranslationApplication.php new file mode 100644 index 00000000..3e51b9c2 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/TranslationApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class TranslationApplication extends Application +{ + use Application\TranslationTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/TranslationTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/TranslationTraitTest.php new file mode 100644 index 00000000..5c054600 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/TranslationTraitTest.php @@ -0,0 +1,47 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use PHPUnit\Framework\TestCase; +use Silex\Provider\TranslationServiceProvider; + +/** + * TranslationTrait test cases. + * + * @author Fabien Potencier + */ +class TranslationTraitTest extends TestCase +{ + public function testTrans() + { + $app = $this->createApplication(); + $app['translator'] = $translator = $this->getMockBuilder('Symfony\Component\Translation\Translator')->disableOriginalConstructor()->getMock(); + $translator->expects($this->once())->method('trans'); + $app->trans('foo'); + } + + public function testTransChoice() + { + $app = $this->createApplication(); + $app['translator'] = $translator = $this->getMockBuilder('Symfony\Component\Translation\Translator')->disableOriginalConstructor()->getMock(); + $translator->expects($this->once())->method('transChoice'); + $app->transChoice('foo', 2); + } + + public function createApplication() + { + $app = new TranslationApplication(); + $app->register(new TranslationServiceProvider()); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/TwigApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/TwigApplication.php new file mode 100644 index 00000000..f1bb4738 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/TwigApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class TwigApplication extends Application +{ + use Application\TwigTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/TwigTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/TwigTraitTest.php new file mode 100644 index 00000000..250ebcfa --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/TwigTraitTest.php @@ -0,0 +1,81 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use PHPUnit\Framework\TestCase; +use Silex\Provider\TwigServiceProvider; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * TwigTrait test cases. + * + * @author Fabien Potencier + */ +class TwigTraitTest extends TestCase +{ + public function testRender() + { + $app = $this->createApplication(); + + $app['twig'] = $mailer = $this->getMockBuilder('Twig_Environment')->disableOriginalConstructor()->getMock(); + $mailer->expects($this->once())->method('render')->will($this->returnValue('foo')); + + $response = $app->render('view'); + $this->assertEquals('Symfony\Component\HttpFoundation\Response', get_class($response)); + $this->assertEquals('foo', $response->getContent()); + } + + public function testRenderKeepResponse() + { + $app = $this->createApplication(); + + $app['twig'] = $mailer = $this->getMockBuilder('Twig_Environment')->disableOriginalConstructor()->getMock(); + $mailer->expects($this->once())->method('render')->will($this->returnValue('foo')); + + $response = $app->render('view', array(), new Response('', 404)); + $this->assertEquals(404, $response->getStatusCode()); + } + + public function testRenderForStream() + { + $app = $this->createApplication(); + + $app['twig'] = $mailer = $this->getMockBuilder('Twig_Environment')->disableOriginalConstructor()->getMock(); + $mailer->expects($this->once())->method('display')->will($this->returnCallback(function () { echo 'foo'; })); + + $response = $app->render('view', array(), new StreamedResponse()); + $this->assertEquals('Symfony\Component\HttpFoundation\StreamedResponse', get_class($response)); + + ob_start(); + $response->send(); + $this->assertEquals('foo', ob_get_clean()); + } + + public function testRenderView() + { + $app = $this->createApplication(); + + $app['twig'] = $mailer = $this->getMockBuilder('Twig_Environment')->disableOriginalConstructor()->getMock(); + $mailer->expects($this->once())->method('render'); + + $app->renderView('view'); + } + + public function createApplication() + { + $app = new TwigApplication(); + $app->register(new TwigServiceProvider()); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorApplication.php new file mode 100644 index 00000000..4239af46 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class UrlGeneratorApplication extends Application +{ + use Application\UrlGeneratorTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorTraitTest.php new file mode 100644 index 00000000..df29d6e7 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorTraitTest.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * UrlGeneratorTrait test cases. + * + * @author Fabien Potencier + */ +class UrlGeneratorTraitTest extends TestCase +{ + public function testUrl() + { + $app = new UrlGeneratorApplication(); + $app['url_generator'] = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->disableOriginalConstructor()->getMock(); + $app['url_generator']->expects($this->once())->method('generate')->with('foo', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $app->url('foo'); + } + + public function testPath() + { + $app = new UrlGeneratorApplication(); + $app['url_generator'] = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->disableOriginalConstructor()->getMock(); + $app['url_generator']->expects($this->once())->method('generate')->with('foo', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + $app->path('foo'); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ApplicationTest.php b/vendor/silex/silex/tests/Silex/Tests/ApplicationTest.php new file mode 100644 index 00000000..57a1d637 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ApplicationTest.php @@ -0,0 +1,725 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Fig\Link\GenericLinkProvider; +use Fig\Link\Link; +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Silex\ControllerCollection; +use Silex\Api\ControllerProviderInterface; +use Silex\Route; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\WebLink\HttpHeaderSerializer; + +/** + * Application test cases. + * + * @author Igor Wiedler + */ +class ApplicationTest extends TestCase +{ + public function testMatchReturnValue() + { + $app = new Application(); + + $returnValue = $app->match('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + + $returnValue = $app->get('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + + $returnValue = $app->post('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + + $returnValue = $app->put('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + + $returnValue = $app->patch('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + + $returnValue = $app->delete('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + } + + public function testConstructorInjection() + { + // inject a custom parameter + $params = array('param' => 'value'); + $app = new Application($params); + $this->assertSame($params['param'], $app['param']); + + // inject an existing parameter + $params = array('locale' => 'value'); + $app = new Application($params); + $this->assertSame($params['locale'], $app['locale']); + } + + public function testGetRequest() + { + $request = Request::create('/'); + + $app = new Application(); + $app->get('/', function (Request $req) use ($request) { + return $request === $req ? 'ok' : 'ko'; + }); + + $this->assertEquals('ok', $app->handle($request)->getContent()); + } + + public function testGetRoutesWithNoRoutes() + { + $app = new Application(); + + $routes = $app['routes']; + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $routes); + $this->assertEquals(0, count($routes->all())); + } + + public function testGetRoutesWithRoutes() + { + $app = new Application(); + + $app->get('/foo', function () { + return 'foo'; + }); + + $app->get('/bar')->run(function () { + return 'bar'; + }); + + $routes = $app['routes']; + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $routes); + $this->assertEquals(0, count($routes->all())); + $app->flush(); + $this->assertEquals(2, count($routes->all())); + } + + public function testOnCoreController() + { + $app = new Application(); + + $app->get('/foo/{foo}', function (\ArrayObject $foo) { + return $foo['foo']; + })->convert('foo', function ($foo) { return new \ArrayObject(array('foo' => $foo)); }); + + $response = $app->handle(Request::create('/foo/bar')); + $this->assertEquals('bar', $response->getContent()); + + $app->get('/foo/{foo}/{bar}', function (\ArrayObject $foo) { + return $foo['foo']; + })->convert('foo', function ($foo, Request $request) { return new \ArrayObject(array('foo' => $foo.$request->attributes->get('bar'))); }); + + $response = $app->handle(Request::create('/foo/foo/bar')); + $this->assertEquals('foobar', $response->getContent()); + } + + public function testOn() + { + $app = new Application(); + $app['pass'] = false; + + $app->on('test', function (Event $e) use ($app) { + $app['pass'] = true; + }); + + $app['dispatcher']->dispatch('test'); + + $this->assertTrue($app['pass']); + } + + public function testAbort() + { + $app = new Application(); + + try { + $app->abort(404); + $this->fail(); + } catch (HttpException $e) { + $this->assertEquals(404, $e->getStatusCode()); + } + } + + /** + * @dataProvider escapeProvider + */ + public function testEscape($expected, $text) + { + $app = new Application(); + + $this->assertEquals($expected, $app->escape($text)); + } + + public function escapeProvider() + { + return array( + array('<', '<'), + array('>', '>'), + array('"', '"'), + array("'", "'"), + array('abc', 'abc'), + ); + } + + public function testControllersAsMethods() + { + $app = new Application(); + unset($app['exception_handler']); + + $app->get('/{name}', 'Silex\Tests\FooController::barAction'); + + $this->assertEquals('Hello Fabien', $app->handle(Request::create('/Fabien'))->getContent()); + } + + public function testApplicationTypeHintWorks() + { + $app = new SpecialApplication(); + unset($app['exception_handler']); + + $app->get('/{name}', 'Silex\Tests\FooController::barSpecialAction'); + + $this->assertEquals('Hello Fabien in Silex\Tests\SpecialApplication', $app->handle(Request::create('/Fabien'))->getContent()); + } + + /** + * @requires PHP 7.0 + */ + public function testPhp7TypeHintWorks() + { + $app = new SpecialApplication(); + unset($app['exception_handler']); + + $app->get('/{name}', 'Silex\Tests\Fixtures\Php7Controller::typehintedAction'); + + $this->assertEquals('Hello Fabien in Silex\Tests\SpecialApplication', $app->handle(Request::create('/Fabien'))->getContent()); + } + + public function testHttpSpec() + { + $app = new Application(); + $app['charset'] = 'ISO-8859-1'; + + $app->get('/', function () { + return 'hello'; + }); + + // content is empty for HEAD requests + $response = $app->handle(Request::create('/', 'HEAD')); + $this->assertEquals('', $response->getContent()); + + // charset is appended to Content-Type + $response = $app->handle(Request::create('/')); + + $this->assertEquals('text/html; charset=ISO-8859-1', $response->headers->get('Content-Type')); + } + + public function testRoutesMiddlewares() + { + $app = new Application(); + + $test = $this; + + $middlewareTarget = array(); + $beforeMiddleware1 = function (Request $request) use (&$middlewareTarget, $test) { + $test->assertEquals('/reached', $request->getRequestUri()); + $middlewareTarget[] = 'before_middleware1_triggered'; + }; + $beforeMiddleware2 = function (Request $request) use (&$middlewareTarget, $test) { + $test->assertEquals('/reached', $request->getRequestUri()); + $middlewareTarget[] = 'before_middleware2_triggered'; + }; + $beforeMiddleware3 = function (Request $request) use (&$middlewareTarget, $test) { + throw new \Exception('This middleware shouldn\'t run!'); + }; + + $afterMiddleware1 = function (Request $request, Response $response) use (&$middlewareTarget, $test) { + $test->assertEquals('/reached', $request->getRequestUri()); + $middlewareTarget[] = 'after_middleware1_triggered'; + }; + $afterMiddleware2 = function (Request $request, Response $response) use (&$middlewareTarget, $test) { + $test->assertEquals('/reached', $request->getRequestUri()); + $middlewareTarget[] = 'after_middleware2_triggered'; + }; + $afterMiddleware3 = function (Request $request, Response $response) use (&$middlewareTarget, $test) { + throw new \Exception('This middleware shouldn\'t run!'); + }; + + $app->get('/reached', function () use (&$middlewareTarget) { + $middlewareTarget[] = 'route_triggered'; + + return 'hello'; + }) + ->before($beforeMiddleware1) + ->before($beforeMiddleware2) + ->after($afterMiddleware1) + ->after($afterMiddleware2); + + $app->get('/never-reached', function () use (&$middlewareTarget) { + throw new \Exception('This route shouldn\'t run!'); + }) + ->before($beforeMiddleware3) + ->after($afterMiddleware3); + + $result = $app->handle(Request::create('/reached')); + + $this->assertSame(array('before_middleware1_triggered', 'before_middleware2_triggered', 'route_triggered', 'after_middleware1_triggered', 'after_middleware2_triggered'), $middlewareTarget); + $this->assertEquals('hello', $result->getContent()); + } + + public function testRoutesBeforeMiddlewaresWithResponseObject() + { + $app = new Application(); + + $app->get('/foo', function () { + throw new \Exception('This route shouldn\'t run!'); + }) + ->before(function () { + return new Response('foo'); + }); + + $request = Request::create('/foo'); + $result = $app->handle($request); + + $this->assertEquals('foo', $result->getContent()); + } + + public function testRoutesAfterMiddlewaresWithResponseObject() + { + $app = new Application(); + + $app->get('/foo', function () { + return new Response('foo'); + }) + ->after(function () { + return new Response('bar'); + }); + + $request = Request::create('/foo'); + $result = $app->handle($request); + + $this->assertEquals('bar', $result->getContent()); + } + + public function testRoutesBeforeMiddlewaresWithRedirectResponseObject() + { + $app = new Application(); + + $app->get('/foo', function () { + throw new \Exception('This route shouldn\'t run!'); + }) + ->before(function () use ($app) { + return $app->redirect('/bar'); + }); + + $request = Request::create('/foo'); + $result = $app->handle($request); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $result); + $this->assertEquals('/bar', $result->getTargetUrl()); + } + + public function testRoutesBeforeMiddlewaresTriggeredAfterSilexBeforeFilters() + { + $app = new Application(); + + $middlewareTarget = array(); + $middleware = function (Request $request) use (&$middlewareTarget) { + $middlewareTarget[] = 'middleware_triggered'; + }; + + $app->get('/foo', function () use (&$middlewareTarget) { + $middlewareTarget[] = 'route_triggered'; + }) + ->before($middleware); + + $app->before(function () use (&$middlewareTarget) { + $middlewareTarget[] = 'before_triggered'; + }); + + $app->handle(Request::create('/foo')); + + $this->assertSame(array('before_triggered', 'middleware_triggered', 'route_triggered'), $middlewareTarget); + } + + public function testRoutesAfterMiddlewaresTriggeredBeforeSilexAfterFilters() + { + $app = new Application(); + + $middlewareTarget = array(); + $middleware = function (Request $request) use (&$middlewareTarget) { + $middlewareTarget[] = 'middleware_triggered'; + }; + + $app->get('/foo', function () use (&$middlewareTarget) { + $middlewareTarget[] = 'route_triggered'; + }) + ->after($middleware); + + $app->after(function () use (&$middlewareTarget) { + $middlewareTarget[] = 'after_triggered'; + }); + + $app->handle(Request::create('/foo')); + + $this->assertSame(array('route_triggered', 'middleware_triggered', 'after_triggered'), $middlewareTarget); + } + + public function testFinishFilter() + { + $containerTarget = array(); + + $app = new Application(); + + $app->finish(function () use (&$containerTarget) { + $containerTarget[] = '4_filterFinish'; + }); + + $app->get('/foo', function () use (&$containerTarget) { + $containerTarget[] = '1_routeTriggered'; + + return new StreamedResponse(function () use (&$containerTarget) { + $containerTarget[] = '3_responseSent'; + }); + }); + + $app->after(function () use (&$containerTarget) { + $containerTarget[] = '2_filterAfter'; + }); + + $app->run(Request::create('/foo')); + + $this->assertSame(array('1_routeTriggered', '2_filterAfter', '3_responseSent', '4_filterFinish'), $containerTarget); + } + + /** + * @expectedException \RuntimeException + */ + public function testNonResponseAndNonNullReturnFromRouteBeforeMiddlewareShouldThrowRuntimeException() + { + $app = new Application(); + + $middleware = function (Request $request) { + return 'string return'; + }; + + $app->get('/', function () { + return 'hello'; + }) + ->before($middleware); + + $app->handle(Request::create('/'), HttpKernelInterface::MASTER_REQUEST, false); + } + + /** + * @expectedException \RuntimeException + */ + public function testNonResponseAndNonNullReturnFromRouteAfterMiddlewareShouldThrowRuntimeException() + { + $app = new Application(); + + $middleware = function (Request $request) { + return 'string return'; + }; + + $app->get('/', function () { + return 'hello'; + }) + ->after($middleware); + + $app->handle(Request::create('/'), HttpKernelInterface::MASTER_REQUEST, false); + } + + public function testSubRequest() + { + $app = new Application(); + $app->get('/sub', function (Request $request) { + return new Response('foo'); + }); + $app->get('/', function (Request $request) use ($app) { + return $app->handle(Request::create('/sub'), HttpKernelInterface::SUB_REQUEST); + }); + + $this->assertEquals('foo', $app->handle(Request::create('/'))->getContent()); + } + + public function testRegisterShouldReturnSelf() + { + $app = new Application(); + $provider = $this->getMockBuilder('Pimple\ServiceProviderInterface')->getMock(); + + $this->assertSame($app, $app->register($provider)); + } + + public function testMountShouldReturnSelf() + { + $app = new Application(); + $mounted = new ControllerCollection(new Route()); + $mounted->get('/{name}', function ($name) { return new Response($name); }); + + $this->assertSame($app, $app->mount('/hello', $mounted)); + } + + public function testMountPreservesOrder() + { + $app = new Application(); + $mounted = new ControllerCollection(new Route()); + $mounted->get('/mounted')->bind('second'); + + $app->get('/before')->bind('first'); + $app->mount('/', $mounted); + $app->get('/after')->bind('third'); + $app->flush(); + + $this->assertEquals(array('first', 'second', 'third'), array_keys(iterator_to_array($app['routes']))); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The "mount" method takes either a "ControllerCollection" instance, "ControllerProviderInterface" instance, or a callable. + */ + public function testMountNullException() + { + $app = new Application(); + $app->mount('/exception', null); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The method "Silex\Tests\IncorrectControllerCollection::connect" must return a "ControllerCollection" instance. Got: "NULL" + */ + public function testMountWrongConnectReturnValueException() + { + $app = new Application(); + $app->mount('/exception', new IncorrectControllerCollection()); + } + + public function testMountCallable() + { + $app = new Application(); + $app->mount('/prefix', function (ControllerCollection $coll) { + $coll->get('/path'); + }); + + $app->flush(); + + $this->assertEquals(1, $app['routes']->count()); + } + + public function testSendFile() + { + $app = new Application(); + + $response = $app->sendFile(__FILE__, 200, array('Content-Type: application/php')); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\BinaryFileResponse', $response); + $this->assertEquals(__FILE__, (string) $response->getFile()); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The "homepage" route must have code to run when it matches. + */ + public function testGetRouteCollectionWithRouteWithoutController() + { + $app = new Application(); + unset($app['exception_handler']); + $app->match('/')->bind('homepage'); + $app->handle(Request::create('/')); + } + + public function testBeforeFilterOnMountedControllerGroupIsolatedToGroup() + { + $app = new Application(); + $app->match('/', function () { return new Response('ok'); }); + $mounted = $app['controllers_factory']; + $mounted->before(function () { return new Response('not ok'); }); + $app->mount('/group', $mounted); + + $response = $app->handle(Request::create('/')); + $this->assertEquals('ok', $response->getContent()); + } + + public function testViewListenerWithPrimitive() + { + $app = new Application(); + $app->get('/foo', function () { return 123; }); + $app->view(function ($view, Request $request) { + return new Response($view); + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('123', $response->getContent()); + } + + public function testViewListenerWithArrayTypeHint() + { + $app = new Application(); + $app->get('/foo', function () { return array('ok'); }); + $app->view(function (array $view) { + return new Response($view[0]); + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('ok', $response->getContent()); + } + + public function testViewListenerWithObjectTypeHint() + { + $app = new Application(); + $app->get('/foo', function () { return (object) array('name' => 'world'); }); + $app->view(function (\stdClass $view) { + return new Response('Hello '.$view->name); + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('Hello world', $response->getContent()); + } + + public function testViewListenerWithCallableTypeHint() + { + $app = new Application(); + $app->get('/foo', function () { return function () { return 'world'; }; }); + $app->view(function (callable $view) { + return new Response('Hello '.$view()); + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('Hello world', $response->getContent()); + } + + public function testViewListenersCanBeChained() + { + $app = new Application(); + $app->get('/foo', function () { return (object) array('name' => 'world'); }); + + $app->view(function (\stdClass $view) { + return array('msg' => 'Hello '.$view->name); + }); + + $app->view(function (array $view) { + return $view['msg']; + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('Hello world', $response->getContent()); + } + + public function testViewListenersAreIgnoredIfNotSuitable() + { + $app = new Application(); + $app->get('/foo', function () { return 'Hello world'; }); + + $app->view(function (\stdClass $view) { + throw new \Exception('View listener was called'); + }); + + $app->view(function (array $view) { + throw new \Exception('View listener was called'); + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('Hello world', $response->getContent()); + } + + public function testViewListenersResponsesAreNotUsedIfNull() + { + $app = new Application(); + $app->get('/foo', function () { return 'Hello world'; }); + + $app->view(function ($view) { + return 'Hello view listener'; + }); + + $app->view(function ($view) { + return; + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('Hello view listener', $response->getContent()); + } + + public function testWebLinkListener() + { + if (!class_exists(HttpHeaderSerializer::class)) { + self::markTestSkipped('Symfony WebLink component is required.'); + } + + $app = new Application(); + + $app->get('/', function () { + return 'hello'; + }); + + $request = Request::create('/'); + $request->attributes->set('_links', (new GenericLinkProvider())->withLink(new Link('preload', '/foo.css'))); + + $response = $app->handle($request); + + $this->assertEquals('; rel="preload"', $response->headers->get('Link')); + } + + public function testDefaultRoutesFactory() + { + $app = new Application(); + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $app['routes']); + } + + public function testOverriddenRoutesFactory() + { + $app = new Application(); + $app['routes_factory'] = $app->factory(function () { + return new RouteCollectionSubClass(); + }); + $this->assertInstanceOf('Silex\Tests\RouteCollectionSubClass', $app['routes']); + } +} + +class FooController +{ + public function barAction(Application $app, $name) + { + return 'Hello '.$app->escape($name); + } + + public function barSpecialAction(SpecialApplication $app, $name) + { + return 'Hello '.$app->escape($name).' in '.get_class($app); + } +} + +class IncorrectControllerCollection implements ControllerProviderInterface +{ + public function connect(Application $app) + { + return; + } +} + +class RouteCollectionSubClass extends RouteCollection +{ +} + +class SpecialApplication extends Application +{ +} diff --git a/vendor/silex/silex/tests/Silex/Tests/CallbackResolverTest.php b/vendor/silex/silex/tests/Silex/Tests/CallbackResolverTest.php new file mode 100644 index 00000000..b637a944 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/CallbackResolverTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Tests; + +use PHPUnit\Framework\TestCase; +use Pimple\Container; +use Silex\CallbackResolver; + +class CallbackResolverTest extends Testcase +{ + private $app; + private $resolver; + + public function setup() + { + $this->app = new Container(); + $this->resolver = new CallbackResolver($this->app); + } + + public function testShouldResolveCallback() + { + $callable = function () {}; + $this->app['some_service'] = function () { return new \ArrayObject(); }; + $this->app['callable_service'] = function () use ($callable) { + return $callable; + }; + + $this->assertTrue($this->resolver->isValid('some_service:methodName')); + $this->assertTrue($this->resolver->isValid('callable_service')); + $this->assertEquals( + array($this->app['some_service'], 'append'), + $this->resolver->convertCallback('some_service:append') + ); + $this->assertSame($callable, $this->resolver->convertCallback('callable_service')); + } + + /** + * @dataProvider nonStringsAreNotValidProvider + */ + public function testNonStringsAreNotValid($name) + { + $this->assertFalse($this->resolver->isValid($name)); + } + + public function nonStringsAreNotValidProvider() + { + return array( + array(null), + array('some_service::methodName'), + array('missing_service'), + ); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessageRegExp /Service "[a-z_]+" is not callable./ + * @dataProvider shouldThrowAnExceptionIfServiceIsNotCallableProvider + */ + public function testShouldThrowAnExceptionIfServiceIsNotCallable($name) + { + $this->app['non_callable_obj'] = function () { return new \stdClass(); }; + $this->app['non_callable'] = function () { return array(); }; + $this->resolver->convertCallback($name); + } + + public function shouldThrowAnExceptionIfServiceIsNotCallableProvider() + { + return array( + array('non_callable_obj:methodA'), + array('non_callable'), + ); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/CallbackServicesTest.php b/vendor/silex/silex/tests/Silex/Tests/CallbackServicesTest.php new file mode 100644 index 00000000..915c0496 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/CallbackServicesTest.php @@ -0,0 +1,110 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; +use Silex\Provider\ServiceControllerServiceProvider; + +/** + * Callback as services test cases. + * + * @author Fabien Potencier + */ +class CallbackServicesTest extends TestCase +{ + public $called = array(); + + public function testCallbacksAsServices() + { + $app = new Application(); + $app->register(new ServiceControllerServiceProvider()); + + $app['service'] = function () { + return new CallbackServicesTest(); + }; + + $app->before('service:beforeApp'); + $app->after('service:afterApp'); + $app->finish('service:finishApp'); + $app->error('service:error'); + $app->on('kernel.request', 'service:onRequest'); + + $app + ->match('/', 'service:controller') + ->convert('foo', 'service:convert') + ->before('service:before') + ->after('service:after') + ; + + $request = Request::create('/'); + $response = $app->handle($request); + $app->terminate($request, $response); + + $this->assertEquals(array( + 'BEFORE APP', + 'ON REQUEST', + 'BEFORE', + 'CONVERT', + 'ERROR', + 'AFTER', + 'AFTER APP', + 'FINISH APP', + ), $app['service']->called); + } + + public function controller(Application $app) + { + $app->abort(404); + } + + public function before() + { + $this->called[] = 'BEFORE'; + } + + public function after() + { + $this->called[] = 'AFTER'; + } + + public function beforeApp() + { + $this->called[] = 'BEFORE APP'; + } + + public function afterApp() + { + $this->called[] = 'AFTER APP'; + } + + public function finishApp() + { + $this->called[] = 'FINISH APP'; + } + + public function error() + { + $this->called[] = 'ERROR'; + } + + public function convert() + { + $this->called[] = 'CONVERT'; + } + + public function onRequest() + { + $this->called[] = 'ON REQUEST'; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ControllerCollectionTest.php b/vendor/silex/silex/tests/Silex/Tests/ControllerCollectionTest.php new file mode 100644 index 00000000..53e33844 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ControllerCollectionTest.php @@ -0,0 +1,328 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Silex\Controller; +use Silex\ControllerCollection; +use Silex\Exception\ControllerFrozenException; +use Silex\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * ControllerCollection test cases. + * + * @author Igor Wiedler + */ +class ControllerCollectionTest extends TestCase +{ + public function testGetRouteCollectionWithNoRoutes() + { + $controllers = new ControllerCollection(new Route()); + $routes = $controllers->flush(); + $this->assertEquals(0, count($routes->all())); + } + + public function testGetRouteCollectionWithRoutes() + { + $controllers = new ControllerCollection(new Route()); + $controllers->match('/foo', function () {}); + $controllers->match('/bar', function () {}); + + $routes = $controllers->flush(); + $this->assertEquals(2, count($routes->all())); + } + + public function testControllerFreezing() + { + $controllers = new ControllerCollection(new Route()); + + $fooController = $controllers->match('/foo', function () {})->bind('foo'); + $barController = $controllers->match('/bar', function () {})->bind('bar'); + + $controllers->flush(); + + try { + $fooController->bind('foo2'); + $this->fail(); + } catch (ControllerFrozenException $e) { + } + + try { + $barController->bind('bar2'); + $this->fail(); + } catch (ControllerFrozenException $e) { + } + } + + public function testConflictingRouteNames() + { + $controllers = new ControllerCollection(new Route()); + + $mountedRootController = $controllers->match('/', function () {}); + + $mainRootController = new Controller(new Route('/')); + $mainRootController->bind($mainRootController->generateRouteName('main_1')); + + $controllers->flush(); + + $this->assertNotEquals($mainRootController->getRouteName(), $mountedRootController->getRouteName()); + } + + public function testUniqueGeneratedRouteNames() + { + $controllers = new ControllerCollection(new Route()); + + $controllers->match('/a-a', function () {}); + $controllers->match('/a_a', function () {}); + $controllers->match('/a/a', function () {}); + + $routes = $controllers->flush(); + + $this->assertCount(3, $routes->all()); + $this->assertEquals(array('_a_a', '_a_a_1', '_a_a_2'), array_keys($routes->all())); + } + + public function testUniqueGeneratedRouteNamesAmongMounts() + { + $controllers = new ControllerCollection(new Route()); + + $controllers->mount('/root-a', $rootA = new ControllerCollection(new Route())); + $controllers->mount('/root_a', $rootB = new ControllerCollection(new Route())); + + $rootA->match('/leaf', function () {}); + $rootB->match('/leaf', function () {}); + + $routes = $controllers->flush(); + + $this->assertCount(2, $routes->all()); + $this->assertEquals(array('_root_a_leaf', '_root_a_leaf_1'), array_keys($routes->all())); + } + + public function testUniqueGeneratedRouteNamesAmongNestedMounts() + { + $controllers = new ControllerCollection(new Route()); + + $controllers->mount('/root-a', $rootA = new ControllerCollection(new Route())); + $controllers->mount('/root_a', $rootB = new ControllerCollection(new Route())); + + $rootA->mount('/tree', $treeA = new ControllerCollection(new Route())); + $rootB->mount('/tree', $treeB = new ControllerCollection(new Route())); + + $treeA->match('/leaf', function () {}); + $treeB->match('/leaf', function () {}); + + $routes = $controllers->flush(); + + $this->assertCount(2, $routes->all()); + $this->assertEquals(array('_root_a_tree_leaf', '_root_a_tree_leaf_1'), array_keys($routes->all())); + } + + public function testMountCallable() + { + $controllers = new ControllerCollection(new Route()); + $controllers->mount('/prefix', function (ControllerCollection $coll) { + $coll->mount('/path', function ($coll) { + $coll->get('/part'); + }); + }); + + $routes = $controllers->flush(); + $this->assertEquals('/prefix/path/part', current($routes->all())->getPath()); + } + + public function testMountCallableProperClone() + { + $controllers = new ControllerCollection(new Route(), new RouteCollection()); + $controllers->get('/'); + + $subControllers = null; + $controllers->mount('/prefix', function (ControllerCollection $coll) use (&$subControllers) { + $subControllers = $coll; + $coll->get('/'); + }); + + $routes = $controllers->flush(); + $subRoutes = $subControllers->flush(); + $this->assertTrue($routes->count() == 2 && $subRoutes->count() == 0); + } + + public function testMountControllersFactory() + { + $testControllers = new ControllerCollection(new Route()); + $controllers = new ControllerCollection(new Route(), null, function () use ($testControllers) { + return $testControllers; + }); + + $controllers->mount('/prefix', function ($mounted) use ($testControllers) { + $this->assertSame($mounted, $testControllers); + }); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The "mount" method takes either a "ControllerCollection" instance or callable. + */ + public function testMountCallableException() + { + $controllers = new ControllerCollection(new Route()); + $controllers->mount('/prefix', ''); + } + + public function testAssert() + { + $controllers = new ControllerCollection(new Route()); + $controllers->assert('id', '\d+'); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->assert('name', '\w+')->assert('extra', '.*'); + $controllers->assert('extra', '\w+'); + + $this->assertEquals('\d+', $controller->getRoute()->getRequirement('id')); + $this->assertEquals('\w+', $controller->getRoute()->getRequirement('name')); + $this->assertEquals('\w+', $controller->getRoute()->getRequirement('extra')); + } + + public function testValue() + { + $controllers = new ControllerCollection(new Route()); + $controllers->value('id', '1'); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->value('name', 'Fabien')->value('extra', 'Symfony'); + $controllers->value('extra', 'Twig'); + + $this->assertEquals('1', $controller->getRoute()->getDefault('id')); + $this->assertEquals('Fabien', $controller->getRoute()->getDefault('name')); + $this->assertEquals('Twig', $controller->getRoute()->getDefault('extra')); + } + + public function testConvert() + { + $controllers = new ControllerCollection(new Route()); + $controllers->convert('id', '1'); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->convert('name', 'Fabien')->convert('extra', 'Symfony'); + $controllers->convert('extra', 'Twig'); + + $this->assertEquals(array('id' => '1', 'name' => 'Fabien', 'extra' => 'Twig'), $controller->getRoute()->getOption('_converters')); + } + + public function testRequireHttp() + { + $controllers = new ControllerCollection(new Route()); + $controllers->requireHttp(); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->requireHttps(); + + $this->assertEquals(array('https'), $controller->getRoute()->getSchemes()); + + $controllers->requireHttp(); + + $this->assertEquals(array('http'), $controller->getRoute()->getSchemes()); + } + + public function testBefore() + { + $controllers = new ControllerCollection(new Route()); + $controllers->before('mid1'); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->before('mid2'); + $controllers->before('mid3'); + + $this->assertEquals(array('mid1', 'mid2', 'mid3'), $controller->getRoute()->getOption('_before_middlewares')); + } + + public function testAfter() + { + $controllers = new ControllerCollection(new Route()); + $controllers->after('mid1'); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->after('mid2'); + $controllers->after('mid3'); + + $this->assertEquals(array('mid1', 'mid2', 'mid3'), $controller->getRoute()->getOption('_after_middlewares')); + } + + public function testWhen() + { + $controllers = new ControllerCollection(new Route()); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->when('request.isSecure() == true'); + + $this->assertEquals('request.isSecure() == true', $controller->getRoute()->getCondition()); + } + + public function testRouteExtension() + { + $route = new MyRoute1(); + + $controller = new ControllerCollection($route); + $controller->foo('foo'); + + $this->assertEquals('foo', $route->foo); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRouteMethodDoesNotExist() + { + $route = new MyRoute1(); + + $controller = new ControllerCollection($route); + $controller->bar(); + } + + public function testNestedCollectionRouteCallbacks() + { + $cl1 = new ControllerCollection(new MyRoute1()); + $cl2 = new ControllerCollection(new MyRoute1()); + + $c1 = $cl2->match('/c1', function () {}); + $cl1->mount('/foo', $cl2); + $c2 = $cl2->match('/c2', function () {}); + $cl1->before('before'); + $c3 = $cl2->match('/c3', function () {}); + + $cl1->flush(); + + $this->assertEquals(array('before'), $c1->getRoute()->getOption('_before_middlewares')); + $this->assertEquals(array('before'), $c2->getRoute()->getOption('_before_middlewares')); + $this->assertEquals(array('before'), $c3->getRoute()->getOption('_before_middlewares')); + } + + public function testRoutesFactoryOmitted() + { + $controllers = new ControllerCollection(new Route()); + $routes = $controllers->flush(); + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $routes); + } + + public function testRoutesFactoryInConstructor() + { + $app = new Application(); + $app['routes_factory'] = $app->factory(function () { + return new RouteCollectionSubClass2(); + }); + + $controllers = new ControllerCollection(new Route(), $app['routes_factory']); + $routes = $controllers->flush(); + $this->assertInstanceOf('Silex\Tests\RouteCollectionSubClass2', $routes); + } +} + +class MyRoute1 extends Route +{ + public $foo; + + public function foo($value) + { + $this->foo = $value; + } +} + +class RouteCollectionSubClass2 extends RouteCollection +{ +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ControllerResolverTest.php b/vendor/silex/silex/tests/Silex/Tests/ControllerResolverTest.php new file mode 100644 index 00000000..efc39c99 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ControllerResolverTest.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use PHPUnit\Framework\TestCase; +use Silex\ControllerResolver; +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; + +/** + * ControllerResolver test cases. + * + * @author Fabien Potencier + */ +class ControllerResolverTest extends TestCase +{ + /** + * @group legacy + */ + public function testGetArguments() + { + $app = new Application(); + $resolver = new ControllerResolver($app); + + $controller = function (Application $app) {}; + + $args = $resolver->getArguments(Request::create('/'), $controller); + $this->assertSame($app, $args[0]); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ControllerTest.php b/vendor/silex/silex/tests/Silex/Tests/ControllerTest.php new file mode 100644 index 00000000..88c6f0a1 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ControllerTest.php @@ -0,0 +1,133 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use PHPUnit\Framework\TestCase; +use Silex\Controller; +use Silex\Route; + +/** + * Controller test cases. + * + * @author Igor Wiedler + */ +class ControllerTest extends TestCase +{ + public function testBind() + { + $controller = new Controller(new Route('/foo')); + $ret = $controller->bind('foo'); + + $this->assertSame($ret, $controller); + $this->assertEquals('foo', $controller->getRouteName()); + } + + /** + * @expectedException \Silex\Exception\ControllerFrozenException + */ + public function testBindOnFrozenControllerShouldThrowException() + { + $controller = new Controller(new Route('/foo')); + $controller->bind('foo'); + $controller->freeze(); + $controller->bind('bar'); + } + + public function testAssert() + { + $controller = new Controller(new Route('/foo/{bar}')); + $ret = $controller->assert('bar', '\d+'); + + $this->assertSame($ret, $controller); + $this->assertEquals(array('bar' => '\d+'), $controller->getRoute()->getRequirements()); + } + + public function testValue() + { + $controller = new Controller(new Route('/foo/{bar}')); + $ret = $controller->value('bar', 'foo'); + + $this->assertSame($ret, $controller); + $this->assertEquals(array('bar' => 'foo'), $controller->getRoute()->getDefaults()); + } + + public function testConvert() + { + $controller = new Controller(new Route('/foo/{bar}')); + $ret = $controller->convert('bar', $func = function ($bar) { return $bar; }); + + $this->assertSame($ret, $controller); + $this->assertEquals(array('bar' => $func), $controller->getRoute()->getOption('_converters')); + } + + public function testRun() + { + $controller = new Controller(new Route('/foo/{bar}')); + $ret = $controller->run($cb = function () { return 'foo'; }); + + $this->assertSame($ret, $controller); + $this->assertEquals($cb, $controller->getRoute()->getDefault('_controller')); + } + + /** + * @dataProvider provideRouteAndExpectedRouteName + */ + public function testDefaultRouteNameGeneration(Route $route, $prefix, $expectedRouteName) + { + $controller = new Controller($route); + $controller->bind($controller->generateRouteName($prefix)); + + $this->assertEquals($expectedRouteName, $controller->getRouteName()); + } + + public function provideRouteAndExpectedRouteName() + { + return array( + array(new Route('/Invalid%Symbols#Stripped', array(), array(), array(), '', array(), array('POST')), '', 'POST_InvalidSymbolsStripped'), + array(new Route('/post/{id}', array(), array(), array(), '', array(), array('GET')), '', 'GET_post_id'), + array(new Route('/colon:pipe|dashes-escaped'), '', '_colon_pipe_dashes_escaped'), + array(new Route('/underscores_and.periods'), '', '_underscores_and.periods'), + array(new Route('/post/{id}', array(), array(), array(), '', array(), array('GET')), 'prefix', 'GET_prefix_post_id'), + ); + } + + public function testRouteExtension() + { + $route = new MyRoute(); + + $controller = new Controller($route); + $controller->foo('foo'); + + $this->assertEquals('foo', $route->foo); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRouteMethodDoesNotExist() + { + $route = new MyRoute(); + + $controller = new Controller($route); + $controller->bar(); + } +} + +class MyRoute extends Route +{ + public $foo; + + public function foo($value) + { + $this->foo = $value; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/EventListener/LogListenerTest.php b/vendor/silex/silex/tests/Silex/Tests/EventListener/LogListenerTest.php new file mode 100644 index 00000000..b56082e0 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/EventListener/LogListenerTest.php @@ -0,0 +1,94 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LogLevel; +use Silex\EventListener\LogListener; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Exception\HttpException; + +/** + * LogListener. + * + * @author Jérôme Tamarelle + */ +class LogListenerTest extends TestCase +{ + public function testRequestListener() + { + $logger = $this->getMockBuilder('Psr\\Log\\LoggerInterface')->getMock(); + $logger + ->expects($this->once()) + ->method('log') + ->with(LogLevel::DEBUG, '> GET /foo') + ; + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new LogListener($logger)); + + $kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\HttpKernelInterface')->getMock(); + + $dispatcher->dispatch(KernelEvents::REQUEST, new GetResponseEvent($kernel, Request::create('/subrequest'), HttpKernelInterface::SUB_REQUEST), 'Skip sub requests'); + + $dispatcher->dispatch(KernelEvents::REQUEST, new GetResponseEvent($kernel, Request::create('/foo'), HttpKernelInterface::MASTER_REQUEST), 'Log master requests'); + } + + public function testResponseListener() + { + $logger = $this->getMockBuilder('Psr\\Log\\LoggerInterface')->getMock(); + $logger + ->expects($this->once()) + ->method('log') + ->with(LogLevel::DEBUG, '< 301') + ; + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new LogListener($logger)); + + $kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\HttpKernelInterface')->getMock(); + + $dispatcher->dispatch(KernelEvents::RESPONSE, new FilterResponseEvent($kernel, Request::create('/foo'), HttpKernelInterface::SUB_REQUEST, Response::create('subrequest', 200)), 'Skip sub requests'); + + $dispatcher->dispatch(KernelEvents::RESPONSE, new FilterResponseEvent($kernel, Request::create('/foo'), HttpKernelInterface::MASTER_REQUEST, Response::create('bar', 301)), 'Log master requests'); + } + + public function testExceptionListener() + { + $logger = $this->getMockBuilder('Psr\\Log\\LoggerInterface')->getMock(); + $logger + ->expects($this->at(0)) + ->method('log') + ->with(LogLevel::CRITICAL, 'RuntimeException: Fatal error (uncaught exception) at '.__FILE__.' line '.(__LINE__ + 13)) + ; + $logger + ->expects($this->at(1)) + ->method('log') + ->with(LogLevel::ERROR, 'Symfony\Component\HttpKernel\Exception\HttpException: Http error (uncaught exception) at '.__FILE__.' line '.(__LINE__ + 9)) + ; + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new LogListener($logger)); + + $kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\HttpKernelInterface')->getMock(); + + $dispatcher->dispatch(KernelEvents::EXCEPTION, new GetResponseForExceptionEvent($kernel, Request::create('/foo'), HttpKernelInterface::SUB_REQUEST, new \RuntimeException('Fatal error'))); + $dispatcher->dispatch(KernelEvents::EXCEPTION, new GetResponseForExceptionEvent($kernel, Request::create('/foo'), HttpKernelInterface::SUB_REQUEST, new HttpException(400, 'Http error'))); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ExceptionHandlerTest.php b/vendor/silex/silex/tests/Silex/Tests/ExceptionHandlerTest.php new file mode 100644 index 00000000..aea31b08 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ExceptionHandlerTest.php @@ -0,0 +1,404 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Error handler test cases. + * + * @author Igor Wiedler + */ +class ExceptionHandlerTest extends TestCase +{ + public function testExceptionHandlerExceptionNoDebug() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Whoops, looks like something went wrong.', $response->getContent()); + $this->assertEquals(500, $response->getStatusCode()); + } + + public function testExceptionHandlerExceptionDebug() + { + $app = new Application(); + $app['debug'] = true; + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + + $this->assertContains('foo exception', $response->getContent()); + $this->assertEquals(500, $response->getStatusCode()); + } + + public function testExceptionHandlerNotFoundNoDebug() + { + $app = new Application(); + $app['debug'] = false; + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Sorry, the page you are looking for could not be found.', $response->getContent()); + $this->assertEquals(404, $response->getStatusCode()); + } + + public function testExceptionHandlerNotFoundDebug() + { + $app = new Application(); + $app['debug'] = true; + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('No route found for "GET /foo"', html_entity_decode($response->getContent())); + $this->assertEquals(404, $response->getStatusCode()); + } + + public function testExceptionHandlerMethodNotAllowedNoDebug() + { + $app = new Application(); + $app['debug'] = false; + + $app->get('/foo', function () { return 'foo'; }); + + $request = Request::create('/foo', 'POST'); + $response = $app->handle($request); + $this->assertContains('Whoops, looks like something went wrong.', $response->getContent()); + $this->assertEquals(405, $response->getStatusCode()); + $this->assertEquals('GET', $response->headers->get('Allow')); + } + + public function testExceptionHandlerMethodNotAllowedDebug() + { + $app = new Application(); + $app['debug'] = true; + + $app->get('/foo', function () { return 'foo'; }); + + $request = Request::create('/foo', 'POST'); + $response = $app->handle($request); + $this->assertContains('No route found for "POST /foo": Method Not Allowed (Allow: GET)', html_entity_decode($response->getContent())); + $this->assertEquals(405, $response->getStatusCode()); + $this->assertEquals('GET', $response->headers->get('Allow')); + } + + public function testNoExceptionHandler() + { + $app = new Application(); + unset($app['exception_handler']); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + try { + $request = Request::create('/foo'); + $app->handle($request); + $this->fail('->handle() should not catch exceptions where no error handler was supplied'); + } catch (\RuntimeException $e) { + $this->assertEquals('foo exception', $e->getMessage()); + } + } + + public function testOneExceptionHandler() + { + $app = new Application(); + + $app->match('/500', function () { + throw new \RuntimeException('foo exception'); + }); + + $app->match('/404', function () { + throw new NotFoundHttpException('foo exception'); + }); + + $app->get('/405', function () { return 'foo'; }); + + $app->error(function ($e, $code) { + return new Response('foo exception handler'); + }); + + $response = $this->checkRouteResponse($app, '/500', 'foo exception handler'); + $this->assertEquals(500, $response->getStatusCode()); + + $response = $app->handle(Request::create('/404')); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $app->handle(Request::create('/405', 'POST')); + $this->assertEquals(405, $response->getStatusCode()); + $this->assertEquals('GET', $response->headers->get('Allow')); + } + + public function testMultipleExceptionHandlers() + { + $app = new Application(); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $errors = 0; + + $app->error(function ($e) use (&$errors) { + ++$errors; + }); + + $app->error(function ($e) use (&$errors) { + ++$errors; + }); + + $app->error(function ($e) use (&$errors) { + ++$errors; + + return new Response('foo exception handler'); + }); + + $app->error(function ($e) use (&$errors) { + // should not execute + ++$errors; + }); + + $request = Request::create('/foo'); + $this->checkRouteResponse($app, '/foo', 'foo exception handler', 'should return the first response returned by an exception handler'); + + $this->assertEquals(3, $errors, 'should execute error handlers until a response is returned'); + } + + public function testNoResponseExceptionHandler() + { + $app = new Application(); + unset($app['exception_handler']); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $errors = 0; + + $app->error(function ($e) use (&$errors) { + ++$errors; + }); + + try { + $request = Request::create('/foo'); + $app->handle($request); + $this->fail('->handle() should not catch exceptions where an empty error handler was supplied'); + } catch (\RuntimeException $e) { + $this->assertEquals('foo exception', $e->getMessage()); + } catch (\LogicException $e) { + $this->assertEquals('foo exception', $e->getPrevious()->getMessage()); + } + + $this->assertEquals(1, $errors, 'should execute the error handler'); + } + + public function testStringResponseExceptionHandler() + { + $app = new Application(); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $app->error(function ($e) { + return 'foo exception handler'; + }); + + $request = Request::create('/foo'); + $this->checkRouteResponse($app, '/foo', 'foo exception handler', 'should accept a string response from the error handler'); + } + + public function testExceptionHandlerException() + { + $app = new Application(); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $app->error(function ($e) { + throw new \RuntimeException('foo exception handler exception'); + }); + + try { + $request = Request::create('/foo'); + $app->handle($request); + $this->fail('->handle() should not catch exceptions thrown from an error handler'); + } catch (\RuntimeException $e) { + $this->assertEquals('foo exception handler exception', $e->getMessage()); + } + } + + public function testRemoveExceptionHandlerAfterDispatcherAccess() + { + $app = new Application(); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $app->before(function () { + // just making sure the dispatcher gets created + }); + + unset($app['exception_handler']); + + try { + $request = Request::create('/foo'); + $app->handle($request); + $this->fail('default exception handler should have been removed'); + } catch (\RuntimeException $e) { + $this->assertEquals('foo exception', $e->getMessage()); + } + } + + public function testExceptionHandlerWithDefaultException() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + throw new \Exception(); + }); + + $app->error(function (\Exception $e) { + return new Response('Exception thrown', 500); + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Exception thrown', $response->getContent()); + $this->assertEquals(500, $response->getStatusCode()); + } + + public function testExceptionHandlerWithStandardException() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + // Throw a normal exception + throw new \Exception(); + }); + + // Register 2 error handlers, each with a specified Exception class + // Since we throw a standard Exception above only + // the second error handler should fire + $app->error(function (\LogicException $e) { // Extends \Exception + return 'Caught LogicException'; + }); + $app->error(function (\Exception $e) { + return 'Caught Exception'; + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Caught Exception', $response->getContent()); + } + + public function testExceptionHandlerWithSpecifiedException() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + // Throw a specified exception + throw new \LogicException(); + }); + + // Register 2 error handlers, each with a specified Exception class + // Since we throw a LogicException above + // the first error handler should fire + $app->error(function (\LogicException $e) { // Extends \Exception + return 'Caught LogicException'; + }); + $app->error(function (\Exception $e) { + return 'Caught Exception'; + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Caught LogicException', $response->getContent()); + } + + public function testExceptionHandlerWithSpecifiedExceptionInReverseOrder() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + // Throw a specified exception + throw new \LogicException(); + }); + + // Register the \Exception error handler first, since the + // error handler works with an instanceof mechanism the + // second more specific error handler should not fire since + // the \Exception error handler is registered first and also + // captures all exceptions that extend it + $app->error(function (\Exception $e) { + return 'Caught Exception'; + }); + $app->error(function (\LogicException $e) { // Extends \Exception + return 'Caught LogicException'; + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Caught Exception', $response->getContent()); + } + + public function testExceptionHandlerWithArrayStyleCallback() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + throw new \Exception(); + }); + + // Array style callback for error handler + $app->error(array($this, 'exceptionHandler')); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Caught Exception', $response->getContent()); + } + + protected function checkRouteResponse($app, $path, $expectedContent, $method = 'get', $message = null) + { + $request = Request::create($path, $method); + $response = $app->handle($request); + $this->assertEquals($expectedContent, $response->getContent(), $message); + + return $response; + } + + public function exceptionHandler() + { + return 'Caught Exception'; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Fixtures/Php7Controller.php b/vendor/silex/silex/tests/Silex/Tests/Fixtures/Php7Controller.php new file mode 100644 index 00000000..c16f14ed --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Fixtures/Php7Controller.php @@ -0,0 +1,22 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Fixtures; + +use Silex\Application; + +class Php7Controller +{ + public function typehintedAction(Application $application, string $name) + { + return 'Hello '.$application->escape($name).' in '.get_class($application); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Fixtures/manifest.json b/vendor/silex/silex/tests/Silex/Tests/Fixtures/manifest.json new file mode 100644 index 00000000..78c3bd61 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Fixtures/manifest.json @@ -0,0 +1,3 @@ +{ + "app.js": "some-random-hash.js" +} \ No newline at end of file diff --git a/vendor/silex/silex/tests/Silex/Tests/FunctionalTest.php b/vendor/silex/silex/tests/Silex/Tests/FunctionalTest.php new file mode 100644 index 00000000..c6ee3359 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/FunctionalTest.php @@ -0,0 +1,59 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Silex\Route; +use Silex\ControllerCollection; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Functional test cases. + * + * @author Igor Wiedler + */ +class FunctionalTest extends TestCase +{ + public function testBind() + { + $app = new Application(); + + $app->get('/', function () { + return 'hello'; + }) + ->bind('homepage'); + + $app->get('/foo', function () { + return 'foo'; + }) + ->bind('foo_abc'); + + $app->flush(); + $routes = $app['routes']; + $this->assertInstanceOf('Symfony\Component\Routing\Route', $routes->get('homepage')); + $this->assertInstanceOf('Symfony\Component\Routing\Route', $routes->get('foo_abc')); + } + + public function testMount() + { + $mounted = new ControllerCollection(new Route()); + $mounted->get('/{name}', function ($name) { return new Response($name); }); + + $app = new Application(); + $app->mount('/hello', $mounted); + + $response = $app->handle(Request::create('/hello/Silex')); + $this->assertEquals('Silex', $response->getContent()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/JsonTest.php b/vendor/silex/silex/tests/Silex/Tests/JsonTest.php new file mode 100644 index 00000000..950437c3 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/JsonTest.php @@ -0,0 +1,57 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use PHPUnit\Framework\TestCase; +use Silex\Application; + +/** + * JSON test cases. + * + * @author Igor Wiedler + */ +class JsonTest extends TestCase +{ + public function testJsonReturnsJsonResponse() + { + $app = new Application(); + + $response = $app->json(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $response = json_decode($response->getContent(), true); + $this->assertSame(array(), $response); + } + + public function testJsonUsesData() + { + $app = new Application(); + + $response = $app->json(array('foo' => 'bar')); + $this->assertSame('{"foo":"bar"}', $response->getContent()); + } + + public function testJsonUsesStatus() + { + $app = new Application(); + + $response = $app->json(array(), 202); + $this->assertSame(202, $response->getStatusCode()); + } + + public function testJsonUsesHeaders() + { + $app = new Application(); + + $response = $app->json(array(), 200, array('ETag' => 'foo')); + $this->assertSame('foo', $response->headers->get('ETag')); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/LazyDispatcherTest.php b/vendor/silex/silex/tests/Silex/Tests/LazyDispatcherTest.php new file mode 100644 index 00000000..fcbbc62a --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/LazyDispatcherTest.php @@ -0,0 +1,60 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; + +class LazyDispatcherTest extends TestCase +{ + /** @test */ + public function beforeMiddlewareShouldNotCreateDispatcherEarly() + { + $dispatcherCreated = false; + + $app = new Application(); + $app->extend('dispatcher', function ($dispatcher, $app) use (&$dispatcherCreated) { + $dispatcherCreated = true; + + return $dispatcher; + }); + + $app->before(function () {}); + + $this->assertFalse($dispatcherCreated); + + $request = Request::create('/'); + $app->handle($request); + + $this->assertTrue($dispatcherCreated); + } + + /** @test */ + public function eventHelpersShouldDirectlyAddListenersAfterBoot() + { + $app = new Application(); + + $fired = false; + $app->get('/', function () use ($app, &$fired) { + $app->finish(function () use (&$fired) { + $fired = true; + }); + }); + + $request = Request::create('/'); + $response = $app->handle($request); + $app->terminate($request, $response); + + $this->assertTrue($fired, 'Event was not fired'); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/LazyRequestMatcherTest.php b/vendor/silex/silex/tests/Silex/Tests/LazyRequestMatcherTest.php new file mode 100644 index 00000000..879d46fa --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/LazyRequestMatcherTest.php @@ -0,0 +1,78 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Silex\Provider\Routing\LazyRequestMatcher; + +/** + * LazyRequestMatcher test case. + * + * @author Leszek Prabucki + */ +class LazyRequestMatcherTest extends TestCase +{ + /** + * @covers \Silex\LazyRequestMatcher::getRequestMatcher + */ + public function testUserMatcherIsCreatedLazily() + { + $callCounter = 0; + $requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + + $matcher = new LazyRequestMatcher(function () use ($requestMatcher, &$callCounter) { + ++$callCounter; + + return $requestMatcher; + }); + + $this->assertEquals(0, $callCounter); + $request = Request::create('path'); + $matcher->matchRequest($request); + $this->assertEquals(1, $callCounter); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Factory supplied to LazyRequestMatcher must return implementation of Symfony\Component\Routing\RequestMatcherInterface. + */ + public function testThatCanInjectRequestMatcherOnly() + { + $matcher = new LazyRequestMatcher(function () { + return 'someMatcher'; + }); + + $request = Request::create('path'); + $matcher->matchRequest($request); + } + + /** + * @covers \Silex\LazyRequestMatcher::matchRequest + */ + public function testMatchIsProxy() + { + $request = Request::create('path'); + $matcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $matcher->expects($this->once()) + ->method('matchRequest') + ->with($request) + ->will($this->returnValue('matcherReturnValue')); + + $matcher = new LazyRequestMatcher(function () use ($matcher) { + return $matcher; + }); + $result = $matcher->matchRequest($request); + + $this->assertEquals('matcherReturnValue', $result); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/LocaleTest.php b/vendor/silex/silex/tests/Silex/Tests/LocaleTest.php new file mode 100644 index 00000000..81bf0422 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/LocaleTest.php @@ -0,0 +1,84 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Silex\Provider\LocaleServiceProvider; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Locale test cases. + * + * @author Fabien Potencier + */ +class LocaleTest extends TestCase +{ + public function testLocale() + { + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app->get('/', function (Request $request) { return $request->getLocale(); }); + $response = $app->handle(Request::create('/')); + $this->assertEquals('en', $response->getContent()); + + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app['locale'] = 'fr'; + $app->get('/', function (Request $request) { return $request->getLocale(); }); + $response = $app->handle(Request::create('/')); + $this->assertEquals('fr', $response->getContent()); + + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app->get('/{_locale}', function (Request $request) { return $request->getLocale(); }); + $response = $app->handle(Request::create('/es')); + $this->assertEquals('es', $response->getContent()); + } + + public function testLocaleInSubRequests() + { + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app->get('/embed/{_locale}', function (Request $request) { return $request->getLocale(); }); + $app->get('/{_locale}', function (Request $request) use ($app) { + return $request->getLocale().$app->handle(Request::create('/embed/es'), HttpKernelInterface::SUB_REQUEST)->getContent().$request->getLocale(); + }); + $response = $app->handle(Request::create('/fr')); + $this->assertEquals('fresfr', $response->getContent()); + + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app->get('/embed', function (Request $request) { return $request->getLocale(); }); + $app->get('/{_locale}', function (Request $request) use ($app) { + return $request->getLocale().$app->handle(Request::create('/embed'), HttpKernelInterface::SUB_REQUEST)->getContent().$request->getLocale(); + }); + $response = $app->handle(Request::create('/fr')); + // locale in sub-request must be "en" as this is the value if the sub-request is converted to an ESI + $this->assertEquals('frenfr', $response->getContent()); + } + + public function testLocaleWithBefore() + { + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app->before(function (Request $request) use ($app) { $request->setLocale('fr'); }); + $app->get('/embed', function (Request $request) { return $request->getLocale(); }); + $app->get('/', function (Request $request) use ($app) { + return $request->getLocale().$app->handle(Request::create('/embed'), HttpKernelInterface::SUB_REQUEST)->getContent().$request->getLocale(); + }); + $response = $app->handle(Request::create('/')); + // locale in sub-request is "en" as the before filter is only executed for the main request + $this->assertEquals('frenfr', $response->getContent()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/MiddlewareTest.php b/vendor/silex/silex/tests/Silex/Tests/MiddlewareTest.php new file mode 100644 index 00000000..6335dad3 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/MiddlewareTest.php @@ -0,0 +1,308 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Middleware test cases. + * + * @author Igor Wiedler + */ +class MiddlewareTest extends TestCase +{ + public function testBeforeAndAfterFilter() + { + $i = 0; + $test = $this; + + $app = new Application(); + + $app->before(function () use (&$i, $test) { + $test->assertEquals(0, $i); + ++$i; + }); + + $app->match('/foo', function () use (&$i, $test) { + $test->assertEquals(1, $i); + ++$i; + }); + + $app->after(function () use (&$i, $test) { + $test->assertEquals(2, $i); + ++$i; + }); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertEquals(3, $i); + } + + public function testAfterFilterWithResponseObject() + { + $i = 0; + + $app = new Application(); + + $app->match('/foo', function () use (&$i) { + ++$i; + + return new Response('foo'); + }); + + $app->after(function () use (&$i) { + ++$i; + }); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertEquals(2, $i); + } + + public function testMultipleFilters() + { + $i = 0; + $test = $this; + + $app = new Application(); + + $app->before(function () use (&$i, $test) { + $test->assertEquals(0, $i); + ++$i; + }); + + $app->before(function () use (&$i, $test) { + $test->assertEquals(1, $i); + ++$i; + }); + + $app->match('/foo', function () use (&$i, $test) { + $test->assertEquals(2, $i); + ++$i; + }); + + $app->after(function () use (&$i, $test) { + $test->assertEquals(3, $i); + ++$i; + }); + + $app->after(function () use (&$i, $test) { + $test->assertEquals(4, $i); + ++$i; + }); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertEquals(5, $i); + } + + public function testFiltersShouldFireOnException() + { + $i = 0; + + $app = new Application(); + + $app->before(function () use (&$i) { + ++$i; + }); + + $app->match('/foo', function () { + throw new \RuntimeException(); + }); + + $app->after(function () use (&$i) { + ++$i; + }); + + $app->error(function () { + return 'error handled'; + }); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertEquals(2, $i); + } + + public function testFiltersShouldFireOnHttpException() + { + $i = 0; + + $app = new Application(); + + $app->before(function () use (&$i) { + ++$i; + }, Application::EARLY_EVENT); + + $app->after(function () use (&$i) { + ++$i; + }); + + $app->error(function () { + return 'error handled'; + }); + + $request = Request::create('/nowhere'); + $app->handle($request); + + $this->assertEquals(2, $i); + } + + public function testBeforeFilterPreventsBeforeMiddlewaresToBeExecuted() + { + $app = new Application(); + + $app->before(function () { return new Response('app before'); }); + + $app->get('/', function () { + return new Response('test'); + })->before(function () { + return new Response('middleware before'); + }); + + $this->assertEquals('app before', $app->handle(Request::create('/'))->getContent()); + } + + public function testBeforeFilterExceptionsWhenHandlingAnException() + { + $app = new Application(); + + $app->before(function () { throw new \RuntimeException(''); }); + + // even if the before filter throws an exception, we must have the 404 + $this->assertEquals(404, $app->handle(Request::create('/'))->getStatusCode()); + } + + public function testRequestShouldBePopulatedOnBefore() + { + $app = new Application(); + + $app->before(function (Request $request) use ($app) { + $app['project'] = $request->get('project'); + }); + + $app->match('/foo/{project}', function () use ($app) { + return $app['project']; + }); + + $request = Request::create('/foo/bar'); + $this->assertEquals('bar', $app->handle($request)->getContent()); + + $request = Request::create('/foo/baz'); + $this->assertEquals('baz', $app->handle($request)->getContent()); + } + + public function testBeforeFilterAccessesRequestAndCanReturnResponse() + { + $app = new Application(); + + $app->before(function (Request $request) { + return new Response($request->get('name')); + }); + + $app->match('/', function () use ($app) { throw new \Exception('Should never be executed'); }); + + $request = Request::create('/?name=Fabien'); + $this->assertEquals('Fabien', $app->handle($request)->getContent()); + } + + public function testAfterFilterAccessRequestResponse() + { + $app = new Application(); + + $app->after(function (Request $request, Response $response) { + $response->setContent($response->getContent().'---'); + }); + + $app->match('/', function () { return new Response('foo'); }); + + $request = Request::create('/'); + $this->assertEquals('foo---', $app->handle($request)->getContent()); + } + + public function testAfterFilterCanReturnResponse() + { + $app = new Application(); + + $app->after(function (Request $request, Response $response) { + return new Response('bar'); + }); + + $app->match('/', function () { return new Response('foo'); }); + + $request = Request::create('/'); + $this->assertEquals('bar', $app->handle($request)->getContent()); + } + + public function testRouteAndApplicationMiddlewareParameterInjection() + { + $app = new Application(); + + $test = $this; + + $middlewareTarget = array(); + $applicationBeforeMiddleware = function ($request, $app) use (&$middlewareTarget, $test) { + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $request); + $test->assertInstanceOf('\Silex\Application', $app); + $middlewareTarget[] = 'application_before_middleware_triggered'; + }; + + $applicationAfterMiddleware = function ($request, $response, $app) use (&$middlewareTarget, $test) { + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $request); + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $response); + $test->assertInstanceOf('\Silex\Application', $app); + $middlewareTarget[] = 'application_after_middleware_triggered'; + }; + + $applicationFinishMiddleware = function ($request, $response, $app) use (&$middlewareTarget, $test) { + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $request); + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $response); + $test->assertInstanceOf('\Silex\Application', $app); + $middlewareTarget[] = 'application_finish_middleware_triggered'; + }; + + $routeBeforeMiddleware = function ($request, $app) use (&$middlewareTarget, $test) { + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $request); + $test->assertInstanceOf('\Silex\Application', $app); + $middlewareTarget[] = 'route_before_middleware_triggered'; + }; + + $routeAfterMiddleware = function ($request, $response, $app) use (&$middlewareTarget, $test) { + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $request); + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $response); + $test->assertInstanceOf('\Silex\Application', $app); + $middlewareTarget[] = 'route_after_middleware_triggered'; + }; + + $app->before($applicationBeforeMiddleware); + $app->after($applicationAfterMiddleware); + $app->finish($applicationFinishMiddleware); + + $app->match('/', function () { + return new Response('foo'); + }) + ->before($routeBeforeMiddleware) + ->after($routeAfterMiddleware); + + $request = Request::create('/'); + $response = $app->handle($request); + $app->terminate($request, $response); + + $this->assertSame(array('application_before_middleware_triggered', 'route_before_middleware_triggered', 'route_after_middleware_triggered', 'application_after_middleware_triggered', 'application_finish_middleware_triggered'), $middlewareTarget); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/AssetServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/AssetServiceProviderTest.php new file mode 100644 index 00000000..940fdca3 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/AssetServiceProviderTest.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Silex\Provider\AssetServiceProvider; + +class AssetServiceProviderTest extends TestCase +{ + public function testGenerateAssetUrl() + { + $app = new Application(); + $app->register(new AssetServiceProvider(), array( + 'assets.version' => 'v1', + 'assets.version_format' => '%s?version=%s', + 'assets.named_packages' => array( + 'css' => array('version' => 'css2', 'base_path' => '/whatever-makes-sense'), + 'images' => array('base_urls' => array('https://img.example.com')), + ), + )); + + $this->assertEquals('/foo.png?version=v1', $app['assets.packages']->getUrl('/foo.png')); + $this->assertEquals('/whatever-makes-sense/foo.css?css2', $app['assets.packages']->getUrl('foo.css', 'css')); + $this->assertEquals('https://img.example.com/foo.png', $app['assets.packages']->getUrl('/foo.png', 'images')); + } + + public function testJsonManifestVersionStrategy() + { + if (!class_exists('Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy')) { + $this->markTestSkipped('JsonManifestVersionStrategy class is not available.'); + + return; + } + + $app = new Application(); + $app->register(new AssetServiceProvider(), array( + 'assets.json_manifest_path' => __DIR__.'/../Fixtures/manifest.json', + )); + + $this->assertEquals('/some-random-hash.js', $app['assets.packages']->getUrl('app.js')); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/DoctrineServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/DoctrineServiceProviderTest.php new file mode 100644 index 00000000..9da8b8e0 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/DoctrineServiceProviderTest.php @@ -0,0 +1,117 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use PHPUnit\Framework\TestCase; +use Pimple\Container; +use Silex\Application; +use Silex\Provider\DoctrineServiceProvider; + +/** + * DoctrineProvider test cases. + * + * Fabien Potencier + */ +class DoctrineServiceProviderTest extends TestCase +{ + public function testOptionsInitializer() + { + $app = new Application(); + $app->register(new DoctrineServiceProvider()); + + $this->assertEquals($app['db.default_options'], $app['db']->getParams()); + } + + public function testSingleConnection() + { + if (!in_array('sqlite', \PDO::getAvailableDrivers())) { + $this->markTestSkipped('pdo_sqlite is not available'); + } + + $app = new Application(); + $app->register(new DoctrineServiceProvider(), array( + 'db.options' => array('driver' => 'pdo_sqlite', 'memory' => true), + )); + + $db = $app['db']; + $params = $db->getParams(); + $this->assertArrayHasKey('memory', $params); + $this->assertTrue($params['memory']); + $this->assertInstanceof('Doctrine\DBAL\Driver\PDOSqlite\Driver', $db->getDriver()); + $this->assertEquals(22, $app['db']->fetchColumn('SELECT 22')); + + $this->assertSame($app['dbs']['default'], $db); + } + + public function testMultipleConnections() + { + if (!in_array('sqlite', \PDO::getAvailableDrivers())) { + $this->markTestSkipped('pdo_sqlite is not available'); + } + + $app = new Application(); + $app->register(new DoctrineServiceProvider(), array( + 'dbs.options' => array( + 'sqlite1' => array('driver' => 'pdo_sqlite', 'memory' => true), + 'sqlite2' => array('driver' => 'pdo_sqlite', 'path' => sys_get_temp_dir().'/silex'), + ), + )); + + $db = $app['db']; + $params = $db->getParams(); + $this->assertArrayHasKey('memory', $params); + $this->assertTrue($params['memory']); + $this->assertInstanceof('Doctrine\DBAL\Driver\PDOSqlite\Driver', $db->getDriver()); + $this->assertEquals(22, $app['db']->fetchColumn('SELECT 22')); + + $this->assertSame($app['dbs']['sqlite1'], $db); + + $db2 = $app['dbs']['sqlite2']; + $params = $db2->getParams(); + $this->assertArrayHasKey('path', $params); + $this->assertEquals(sys_get_temp_dir().'/silex', $params['path']); + } + + public function testLoggerLoading() + { + if (!in_array('sqlite', \PDO::getAvailableDrivers())) { + $this->markTestSkipped('pdo_sqlite is not available'); + } + + $app = new Application(); + $this->assertTrue(isset($app['logger'])); + $this->assertNull($app['logger']); + $app->register(new DoctrineServiceProvider(), array( + 'dbs.options' => array( + 'sqlite1' => array('driver' => 'pdo_sqlite', 'memory' => true), + ), + )); + $this->assertEquals(22, $app['db']->fetchColumn('SELECT 22')); + $this->assertNull($app['db']->getConfiguration()->getSQLLogger()); + } + + public function testLoggerNotLoaded() + { + if (!in_array('sqlite', \PDO::getAvailableDrivers())) { + $this->markTestSkipped('pdo_sqlite is not available'); + } + + $app = new Container(); + $app->register(new DoctrineServiceProvider(), array( + 'dbs.options' => array( + 'sqlite1' => array('driver' => 'pdo_sqlite', 'memory' => true), + ), + )); + $this->assertEquals(22, $app['db']->fetchColumn('SELECT 22')); + $this->assertNull($app['db']->getConfiguration()->getSQLLogger()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest.php new file mode 100644 index 00000000..981826e6 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest.php @@ -0,0 +1,363 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Silex\Provider\FormServiceProvider; +use Silex\Provider\CsrfServiceProvider; +use Silex\Provider\SessionServiceProvider; +use Silex\Provider\TranslationServiceProvider; +use Silex\Provider\ValidatorServiceProvider; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\FormTypeGuesserChain; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\OptionsResolver\OptionsResolverInterface; +use Symfony\Component\Translation\Exception\NotFoundResourceException; + +class FormServiceProviderTest extends TestCase +{ + public function testFormFactoryServiceIsFormFactory() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $this->assertInstanceOf('Symfony\Component\Form\FormFactory', $app['form.factory']); + } + + public function testFormRegistryServiceIsFormRegistry() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $this->assertInstanceOf('Symfony\Component\Form\FormRegistry', $app['form.registry']); + } + + public function testFormServiceProviderWillLoadTypes() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.types', function ($extensions) { + $extensions[] = new DummyFormType(); + + return $extensions; + }); + + $form = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('dummy', 'Silex\Tests\Provider\DummyFormType') + ->getForm(); + + $this->assertInstanceOf('Symfony\Component\Form\Form', $form); + } + + public function testFormServiceProviderWillLoadTypesServices() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app['dummy'] = function () { + return new DummyFormType(); + }; + $app->extend('form.types', function ($extensions) { + $extensions[] = 'dummy'; + + return $extensions; + }); + + $form = $app['form.factory'] + ->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('dummy', 'dummy') + ->getForm(); + + $this->assertInstanceOf('Symfony\Component\Form\Form', $form); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid form type. The silex service "dummy" does not exist. + */ + public function testNonExistentTypeService() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.types', function ($extensions) { + $extensions[] = 'dummy'; + + return $extensions; + }); + + $app['form.factory'] + ->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('dummy', 'dummy') + ->getForm(); + } + + public function testFormServiceProviderWillLoadTypeExtensions() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.type.extensions', function ($extensions) { + $extensions[] = new DummyFormTypeExtension(); + + return $extensions; + }); + + $form = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType', array('image_path' => 'webPath')) + ->getForm(); + + $this->assertInstanceOf('Symfony\Component\Form\Form', $form); + } + + public function testFormServiceProviderWillLoadTypeExtensionsServices() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app['dummy.form.type.extension'] = function () { + return new DummyFormTypeExtension(); + }; + $app->extend('form.type.extensions', function ($extensions) { + $extensions[] = 'dummy.form.type.extension'; + + return $extensions; + }); + + $form = $app['form.factory'] + ->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType', array('image_path' => 'webPath')) + ->getForm(); + + $this->assertInstanceOf('Symfony\Component\Form\Form', $form); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid form type extension. The silex service "dummy.form.type.extension" does not exist. + */ + public function testNonExistentTypeExtensionService() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.type.extensions', function ($extensions) { + $extensions[] = 'dummy.form.type.extension'; + + return $extensions; + }); + + $app['form.factory'] + ->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('dummy', 'dummy.form.type') + ->getForm(); + } + + public function testFormServiceProviderWillLoadTypeGuessers() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.type.guessers', function ($guessers) { + $guessers[] = new FormTypeGuesserChain(array()); + + return $guessers; + }); + + $this->assertInstanceOf('Symfony\Component\Form\FormFactory', $app['form.factory']); + } + + public function testFormServiceProviderWillLoadTypeGuessersServices() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app['dummy.form.type.guesser'] = function () { + return new FormTypeGuesserChain(array()); + }; + $app->extend('form.type.guessers', function ($guessers) { + $guessers[] = 'dummy.form.type.guesser'; + + return $guessers; + }); + + $this->assertInstanceOf('Symfony\Component\Form\FormFactory', $app['form.factory']); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid form type guesser. The silex service "dummy.form.type.guesser" does not exist. + */ + public function testNonExistentTypeGuesserService() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.type.guessers', function ($extensions) { + $extensions[] = 'dummy.form.type.guesser'; + + return $extensions; + }); + + $factory = $app['form.factory']; + } + + public function testFormServiceProviderWillUseTranslatorIfAvailable() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + $app->register(new TranslationServiceProvider()); + $app['translator.domains'] = array( + 'messages' => array( + 'de' => array( + 'The CSRF token is invalid. Please try to resubmit the form.' => 'German translation', + ), + ), + ); + $app['locale'] = 'de'; + + $app['csrf.token_manager'] = function () { + return $this->getMockBuilder('Symfony\Component\Security\Csrf\CsrfTokenManagerInterface')->getMock(); + }; + + $form = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->getForm(); + + $form->handleRequest($req = Request::create('/', 'POST', array('form' => array( + '_token' => 'the wrong token', + )))); + + $this->assertFalse($form->isValid()); + $r = new \ReflectionMethod($form, 'getErrors'); + if (!$r->getNumberOfParameters()) { + $this->assertContains('ERROR: German translation', $form->getErrorsAsString()); + } else { + // as of 2.5 + $this->assertContains('ERROR: German translation', (string) $form->getErrors(true, false)); + } + } + + public function testFormServiceProviderWillNotAddNonexistentTranslationFiles() + { + $app = new Application(array( + 'locale' => 'nonexistent', + )); + + $app->register(new FormServiceProvider()); + $app->register(new ValidatorServiceProvider()); + $app->register(new TranslationServiceProvider(), array( + 'locale_fallbacks' => array(), + )); + + $app['form.factory']; + $translator = $app['translator']; + + try { + $translator->trans('test'); + } catch (NotFoundResourceException $e) { + $this->fail('Form factory should not add a translation resource that does not exist'); + } + } + + public function testFormCsrf() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $app->register(new SessionServiceProvider()); + $app->register(new CsrfServiceProvider()); + $app['session.test'] = true; + + $form = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array())->getForm(); + + $this->assertTrue(isset($form->createView()['_token'])); + } + + public function testUserExtensionCanConfigureDefaultExtensions() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $app->register(new SessionServiceProvider()); + $app->register(new CsrfServiceProvider()); + $app['session.test'] = true; + + $app->extend('form.type.extensions', function ($extensions) { + $extensions[] = new FormServiceProviderTest\DisableCsrfExtension(); + + return $extensions; + }); + $form = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array())->getForm(); + + $this->assertFalse($form->getConfig()->getOption('csrf_protection')); + } +} + +if (!class_exists('Symfony\Component\Form\Deprecated\FormEvents')) { + class DummyFormType extends AbstractType + { + } +} else { + // FormTypeInterface::getName() is needed by the form component 2.8.x + class DummyFormType extends AbstractType + { + /** + * @return string The name of this type + */ + public function getName() + { + return 'dummy'; + } + } +} + +if (method_exists('Symfony\Component\Form\AbstractType', 'configureOptions')) { + class DummyFormTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return 'Symfony\Component\Form\Extension\Core\Type\FileType'; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefined(array('image_path')); + } + } +} else { + class DummyFormTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return 'Symfony\Component\Form\Extension\Core\Type\FileType'; + } + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + if (!method_exists($resolver, 'setDefined')) { + $resolver->setOptional(array('image_path')); + } else { + $resolver->setDefined(array('image_path')); + } + } + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest/DisableCsrfExtension.php b/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest/DisableCsrfExtension.php new file mode 100644 index 00000000..8c82237d --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest/DisableCsrfExtension.php @@ -0,0 +1,22 @@ +setDefaults(array( + 'csrf_protection' => false, + )); + } + + public function getExtendedType() + { + return FormType::class; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/HttpCacheServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/HttpCacheServiceProviderTest.php new file mode 100644 index 00000000..dca3f6a8 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/HttpCacheServiceProviderTest.php @@ -0,0 +1,81 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Silex\Provider\HttpCacheServiceProvider; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * HttpCacheProvider test cases. + * + * @author Igor Wiedler + */ +class HttpCacheServiceProviderTest extends TestCase +{ + public function testRegister() + { + $app = new Application(); + + $app->register(new HttpCacheServiceProvider(), array( + 'http_cache.cache_dir' => sys_get_temp_dir().'/silex_http_cache_'.uniqid(), + )); + + $this->assertInstanceOf('Silex\Provider\HttpCache\HttpCache', $app['http_cache']); + + return $app; + } + + /** + * @depends testRegister + */ + public function testRunCallsShutdown($app) + { + $finished = false; + + $app->finish(function () use (&$finished) { + $finished = true; + }); + + $app->get('/', function () use ($app) { + return new UnsendableResponse('will do something after finish'); + }); + + $request = Request::create('/'); + $app['http_cache']->run($request); + + $this->assertTrue($finished); + } + + public function testDebugDefaultsToThatOfApp() + { + $app = new Application(); + + $app->register(new HttpCacheServiceProvider(), array( + 'http_cache.cache_dir' => sys_get_temp_dir().'/silex_http_cache_'.uniqid(), + )); + + $app['debug'] = true; + $app['http_cache']; + $this->assertTrue($app['http_cache.options']['debug']); + } +} + +class UnsendableResponse extends Response +{ + public function send() + { + // do nothing + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/HttpFragmentServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/HttpFragmentServiceProviderTest.php new file mode 100644 index 00000000..6e12fcc5 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/HttpFragmentServiceProviderTest.php @@ -0,0 +1,49 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Silex\Provider\HttpCacheServiceProvider; +use Silex\Provider\HttpFragmentServiceProvider; +use Silex\Provider\TwigServiceProvider; +use Symfony\Component\HttpFoundation\Request; + +class HttpFragmentServiceProviderTest extends TestCase +{ + public function testRenderFunction() + { + $app = new Application(); + unset($app['exception_handler']); + + $app->register(new HttpFragmentServiceProvider()); + $app->register(new HttpCacheServiceProvider(), array('http_cache.cache_dir' => sys_get_temp_dir())); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array( + 'hello' => '{{ render("/foo") }}{{ render_esi("/foo") }}{{ render_hinclude("/foo") }}', + 'foo' => 'foo', + ), + )); + + $app->get('/hello', function () use ($app) { + return $app['twig']->render('hello'); + }); + + $app->get('/foo', function () use ($app) { + return $app['twig']->render('foo'); + }); + + $response = $app['http_cache']->handle(Request::create('/hello')); + + $this->assertEquals('foofoo', $response->getContent()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/MonologServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/MonologServiceProviderTest.php new file mode 100644 index 00000000..4585378f --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/MonologServiceProviderTest.php @@ -0,0 +1,257 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Monolog\Formatter\JsonFormatter; +use Monolog\Handler\TestHandler; +use Monolog\Logger; +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Silex\Provider\MonologServiceProvider; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Kernel; + +/** + * MonologProvider test cases. + * + * @author Igor Wiedler + */ +class MonologServiceProviderTest extends TestCase +{ + private $currErrorHandler; + + protected function setUp() + { + $this->currErrorHandler = set_error_handler('var_dump'); + restore_error_handler(); + } + + protected function tearDown() + { + set_error_handler($this->currErrorHandler); + } + + public function testRequestLogging() + { + $app = $this->getApplication(); + + $app->get('/foo', function () use ($app) { + return 'foo'; + }); + + $this->assertFalse($app['monolog.handler']->hasInfoRecords()); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertTrue($app['monolog.handler']->hasDebug('> GET /foo')); + $this->assertTrue($app['monolog.handler']->hasDebug('< 200')); + + $records = $app['monolog.handler']->getRecords(); + if (Kernel::VERSION_ID < 30100) { + $this->assertContains('Matched route "GET_foo"', $records[0]['message']); + } else { + $this->assertContains('Matched route "{route}".', $records[0]['message']); + $this->assertSame('GET_foo', $records[0]['context']['route']); + } + } + + public function testManualLogging() + { + $app = $this->getApplication(); + + $app->get('/log', function () use ($app) { + $app['monolog']->addDebug('logging a message'); + }); + + $this->assertFalse($app['monolog.handler']->hasDebugRecords()); + + $request = Request::create('/log'); + $app->handle($request); + + $this->assertTrue($app['monolog.handler']->hasDebug('logging a message')); + } + + public function testOverrideFormatter() + { + $app = new Application(); + + $app->register(new MonologServiceProvider(), array( + 'monolog.formatter' => new JsonFormatter(), + 'monolog.logfile' => 'php://memory', + )); + + $this->assertInstanceOf('Monolog\Formatter\JsonFormatter', $app['monolog.handler']->getFormatter()); + } + + public function testErrorLogging() + { + $app = $this->getApplication(); + + $app->error(function (\Exception $e) { + return 'error handled'; + }); + + /* + * Simulate 404, logged to error level + */ + $this->assertFalse($app['monolog.handler']->hasErrorRecords()); + + $request = Request::create('/error'); + $app->handle($request); + + $pattern = "#Symfony\\\\Component\\\\HttpKernel\\\\Exception\\\\NotFoundHttpException: No route found for \"GET /error\" \(uncaught exception\) at .* line \d+#"; + $this->assertMatchingRecord($pattern, Logger::ERROR, $app['monolog.handler']); + + /* + * Simulate unhandled exception, logged to critical + */ + $app->get('/error', function () { + throw new \RuntimeException('very bad error'); + }); + + $this->assertFalse($app['monolog.handler']->hasCriticalRecords()); + + $request = Request::create('/error'); + $app->handle($request); + + $pattern = "#RuntimeException: very bad error \(uncaught exception\) at .* line \d+#"; + $this->assertMatchingRecord($pattern, Logger::CRITICAL, $app['monolog.handler']); + } + + public function testRedirectLogging() + { + $app = $this->getApplication(); + + $app->get('/foo', function () use ($app) { + return new RedirectResponse('/bar', 302); + }); + + $this->assertFalse($app['monolog.handler']->hasInfoRecords()); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertTrue($app['monolog.handler']->hasDebug('< 302 /bar')); + } + + public function testErrorLoggingGivesWayToSecurityExceptionHandling() + { + $app = $this->getApplication(); + $app['monolog.level'] = Logger::ERROR; + + $app->register(new \Silex\Provider\SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'admin' => array( + 'pattern' => '^/admin', + 'http' => true, + 'users' => array(), + ), + ), + )); + + $app->get('/admin', function () { + return 'SECURE!'; + }); + + $request = Request::create('/admin'); + $app->run($request); + + $this->assertEmpty($app['monolog.handler']->getRecords(), 'Expected no logging to occur'); + } + + public function testStringErrorLevel() + { + $app = $this->getApplication(); + $app['monolog.level'] = 'info'; + + $this->assertSame(Logger::INFO, $app['monolog.handler']->getLevel()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Provided logging level 'foo' does not exist. Must be a valid monolog logging level. + */ + public function testNonExistentStringErrorLevel() + { + $app = $this->getApplication(); + $app['monolog.level'] = 'foo'; + + $app['monolog.handler']->getLevel(); + } + + public function testDisableListener() + { + $app = $this->getApplication(); + unset($app['monolog.listener']); + + $app->handle(Request::create('/404')); + + $this->assertEmpty($app['monolog.handler']->getRecords(), 'Expected no logging to occur'); + } + + public function testExceptionFiltering() + { + $app = new Application(); + $app->get('/foo', function () use ($app) { + throw new NotFoundHttpException(); + }); + + $level = Logger::ERROR; + $app->register(new MonologServiceProvider(), array( + 'monolog.exception.logger_filter' => $app->protect(function () { + return Logger::DEBUG; + }), + 'monolog.handler' => function () use ($app) { + return new TestHandler($app['monolog.level']); + }, + 'monolog.level' => $level, + 'monolog.logfile' => 'php://memory', + )); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertCount(0, $app['monolog.handler']->getRecords(), 'Expected no logging to occur'); + } + + protected function assertMatchingRecord($pattern, $level, TestHandler $handler) + { + $found = false; + $records = $handler->getRecords(); + foreach ($records as $record) { + if (preg_match($pattern, $record['message']) && $record['level'] == $level) { + $found = true; + continue; + } + } + $this->assertTrue($found, "Trying to find record matching $pattern with level $level"); + } + + protected function getApplication() + { + $app = new Application(); + + $app->register(new MonologServiceProvider(), array( + 'monolog.handler' => function () use ($app) { + $level = MonologServiceProvider::translateLevel($app['monolog.level']); + + return new TestHandler($level); + }, + 'monolog.logfile' => 'php://memory', + )); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/RememberMeServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/RememberMeServiceProviderTest.php new file mode 100644 index 00000000..b027497c --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/RememberMeServiceProviderTest.php @@ -0,0 +1,107 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\WebTestCase; +use Silex\Provider\RememberMeServiceProvider; +use Silex\Provider\SecurityServiceProvider; +use Silex\Provider\SessionServiceProvider; +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\Security\Http\SecurityEvents; + +/** + * SecurityServiceProvider. + * + * @author Fabien Potencier + */ +class RememberMeServiceProviderTest extends WebTestCase +{ + public function testRememberMeAuthentication() + { + $app = $this->createApplication(); + + $interactiveLogin = new InteractiveLoginTriggered(); + $app->on(SecurityEvents::INTERACTIVE_LOGIN, array($interactiveLogin, 'onInteractiveLogin')); + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertFalse($interactiveLogin->triggered, 'The interactive login has not been triggered yet'); + $client->request('post', '/login_check', array('_username' => 'fabien', '_password' => 'foo', '_remember_me' => 'true')); + $client->followRedirect(); + $this->assertEquals('AUTHENTICATED_FULLY', $client->getResponse()->getContent()); + $this->assertTrue($interactiveLogin->triggered, 'The interactive login has been triggered'); + + $this->assertNotNull($client->getCookiejar()->get('REMEMBERME'), 'The REMEMBERME cookie is set'); + $event = false; + + $client->getCookiejar()->expire('MOCKSESSID'); + + $client->request('get', '/'); + $this->assertEquals('AUTHENTICATED_REMEMBERED', $client->getResponse()->getContent()); + $this->assertTrue($interactiveLogin->triggered, 'The interactive login has been triggered'); + + $client->request('get', '/logout'); + $client->followRedirect(); + + $this->assertNull($client->getCookiejar()->get('REMEMBERME'), 'The REMEMBERME cookie has been removed'); + } + + public function createApplication($authenticationMethod = 'form') + { + $app = new Application(); + + $app['debug'] = true; + unset($app['exception_handler']); + + $app->register(new SessionServiceProvider(), array( + 'session.test' => true, + )); + $app->register(new SecurityServiceProvider()); + $app->register(new RememberMeServiceProvider()); + + $app['security.firewalls'] = array( + 'http-auth' => array( + 'pattern' => '^.*$', + 'form' => true, + 'remember_me' => array(), + 'logout' => true, + 'users' => array( + 'fabien' => array('ROLE_USER', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + ), + ), + ); + + $app->get('/', function () use ($app) { + if ($app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_FULLY')) { + return 'AUTHENTICATED_FULLY'; + } elseif ($app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_REMEMBERED')) { + return 'AUTHENTICATED_REMEMBERED'; + } else { + return 'AUTHENTICATED_ANONYMOUSLY'; + } + }); + + return $app; + } +} + +class InteractiveLoginTriggered +{ + public $triggered = false; + + public function onInteractiveLogin() + { + $this->triggered = true; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/RoutingServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/RoutingServiceProviderTest.php new file mode 100644 index 00000000..2faaa683 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/RoutingServiceProviderTest.php @@ -0,0 +1,122 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use PHPUnit\Framework\TestCase; +use Pimple\Container; +use Silex\Application; +use Silex\Provider\RoutingServiceProvider; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * RoutingProvider test cases. + * + * @author Igor Wiedler + */ +class RoutingServiceProviderTest extends TestCase +{ + public function testRegister() + { + $app = new Application(); + + $app->get('/hello/{name}', function ($name) {}) + ->bind('hello'); + + $app->get('/', function () {}); + + $request = Request::create('/'); + $app->handle($request); + + $this->assertInstanceOf('Symfony\Component\Routing\Generator\UrlGenerator', $app['url_generator']); + } + + public function testUrlGeneration() + { + $app = new Application(); + + $app->get('/hello/{name}', function ($name) {}) + ->bind('hello'); + + $app->get('/', function () use ($app) { + return $app['url_generator']->generate('hello', array('name' => 'john')); + }); + + $request = Request::create('/'); + $response = $app->handle($request); + + $this->assertEquals('/hello/john', $response->getContent()); + } + + public function testAbsoluteUrlGeneration() + { + $app = new Application(); + + $app->get('/hello/{name}', function ($name) {}) + ->bind('hello'); + + $app->get('/', function () use ($app) { + return $app['url_generator']->generate('hello', array('name' => 'john'), UrlGeneratorInterface::ABSOLUTE_URL); + }); + + $request = Request::create('https://localhost:81/'); + $response = $app->handle($request); + + $this->assertEquals('https://localhost:81/hello/john', $response->getContent()); + } + + public function testUrlGenerationWithHttp() + { + $app = new Application(); + + $app->get('/insecure', function () {}) + ->bind('insecure_page') + ->requireHttp(); + + $app->get('/', function () use ($app) { + return $app['url_generator']->generate('insecure_page'); + }); + + $request = Request::create('https://localhost/'); + $response = $app->handle($request); + + $this->assertEquals('http://localhost/insecure', $response->getContent()); + } + + public function testUrlGenerationWithHttps() + { + $app = new Application(); + + $app->get('/secure', function () {}) + ->bind('secure_page') + ->requireHttps(); + + $app->get('/', function () use ($app) { + return $app['url_generator']->generate('secure_page'); + }); + + $request = Request::create('http://localhost/'); + $response = $app->handle($request); + + $this->assertEquals('https://localhost/secure', $response->getContent()); + } + + public function testControllersFactory() + { + $app = new Container(); + $app->register(new RoutingServiceProvider()); + $coll = $app['controllers_factory']; + $coll->mount('/blog', function ($blog) { + $this->assertInstanceOf('Silex\ControllerCollection', $blog); + }); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest.php new file mode 100644 index 00000000..3436c53b --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest.php @@ -0,0 +1,491 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\WebTestCase; +use Silex\Provider\SecurityServiceProvider; +use Silex\Provider\SessionServiceProvider; +use Silex\Provider\ValidatorServiceProvider; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\HttpFoundation\Request; + +/** + * SecurityServiceProvider. + * + * @author Fabien Potencier + */ +class SecurityServiceProviderTest extends WebTestCase +{ + /** + * @expectedException \LogicException + */ + public function testWrongAuthenticationType() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'wrong' => array( + 'foobar' => true, + 'users' => array(), + ), + ), + )); + $app->get('/', function () {}); + $app->handle(Request::create('/')); + } + + public function testFormAuthentication() + { + $app = $this->createApplication('form'); + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertEquals('ANONYMOUS', $client->getResponse()->getContent()); + + $client->request('post', '/login_check', array('_username' => 'fabien', '_password' => 'bar')); + $this->assertContains('Bad credentials', $app['security.last_error']($client->getRequest())); + // hack to re-close the session as the previous assertions re-opens it + $client->getRequest()->getSession()->save(); + + $client->request('post', '/login_check', array('_username' => 'fabien', '_password' => 'foo')); + $this->assertEquals('', $app['security.last_error']($client->getRequest())); + $client->getRequest()->getSession()->save(); + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + $this->assertEquals('http://localhost/', $client->getResponse()->getTargetUrl()); + + $client->request('get', '/'); + $this->assertEquals('fabienAUTHENTICATED', $client->getResponse()->getContent()); + $client->request('get', '/admin'); + $this->assertEquals(403, $client->getResponse()->getStatusCode()); + + $client->request('get', '/logout'); + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + $this->assertEquals('http://localhost/', $client->getResponse()->getTargetUrl()); + + $client->request('get', '/'); + $this->assertEquals('ANONYMOUS', $client->getResponse()->getContent()); + + $client->request('get', '/admin'); + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + $this->assertEquals('http://localhost/login', $client->getResponse()->getTargetUrl()); + + $client->request('post', '/login_check', array('_username' => 'admin', '_password' => 'foo')); + $this->assertEquals('', $app['security.last_error']($client->getRequest())); + $client->getRequest()->getSession()->save(); + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + $this->assertEquals('http://localhost/admin', $client->getResponse()->getTargetUrl()); + + $client->request('get', '/'); + $this->assertEquals('adminAUTHENTICATEDADMIN', $client->getResponse()->getContent()); + $client->request('get', '/admin'); + $this->assertEquals('admin', $client->getResponse()->getContent()); + } + + public function testHttpAuthentication() + { + $app = $this->createApplication('http'); + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertEquals(401, $client->getResponse()->getStatusCode()); + $this->assertEquals('Basic realm="Secured"', $client->getResponse()->headers->get('www-authenticate')); + + $client->request('get', '/', array(), array(), array('PHP_AUTH_USER' => 'dennis', 'PHP_AUTH_PW' => 'foo')); + $this->assertEquals('dennisAUTHENTICATED', $client->getResponse()->getContent()); + $client->request('get', '/admin'); + $this->assertEquals(403, $client->getResponse()->getStatusCode()); + + $client->restart(); + + $client->request('get', '/'); + $this->assertEquals(401, $client->getResponse()->getStatusCode()); + $this->assertEquals('Basic realm="Secured"', $client->getResponse()->headers->get('www-authenticate')); + + $client->request('get', '/', array(), array(), array('PHP_AUTH_USER' => 'admin', 'PHP_AUTH_PW' => 'foo')); + $this->assertEquals('adminAUTHENTICATEDADMIN', $client->getResponse()->getContent()); + $client->request('get', '/admin'); + $this->assertEquals('admin', $client->getResponse()->getContent()); + } + + public function testGuardAuthentication() + { + $app = $this->createApplication('guard'); + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertEquals(401, $client->getResponse()->getStatusCode(), 'The entry point is configured'); + $this->assertEquals('{"message":"Authentication Required"}', $client->getResponse()->getContent()); + + $client->request('get', '/', array(), array(), array('HTTP_X_AUTH_TOKEN' => 'lili:not the secret')); + $this->assertEquals(403, $client->getResponse()->getStatusCode(), 'User not found'); + $this->assertEquals('{"message":"Username could not be found."}', $client->getResponse()->getContent()); + + $client->request('get', '/', array(), array(), array('HTTP_X_AUTH_TOKEN' => 'victoria:not the secret')); + $this->assertEquals(403, $client->getResponse()->getStatusCode(), 'Invalid credentials'); + $this->assertEquals('{"message":"Invalid credentials."}', $client->getResponse()->getContent()); + + $client->request('get', '/', array(), array(), array('HTTP_X_AUTH_TOKEN' => 'victoria:victoriasecret')); + $this->assertEquals('victoria', $client->getResponse()->getContent()); + } + + public function testUserPasswordValidatorIsRegistered() + { + $app = new Application(); + + $app->register(new ValidatorServiceProvider()); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'admin' => array( + 'pattern' => '^/admin', + 'http' => true, + 'users' => array( + 'admin' => array('ROLE_ADMIN', '513aeb0121909'), + ), + ), + ), + )); + + $app->boot(); + + $this->assertInstanceOf('Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator', $app['security.validator.user_password_validator']); + } + + public function testExposedExceptions() + { + $app = $this->createApplication('form'); + $app['security.hide_user_not_found'] = false; + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertEquals('ANONYMOUS', $client->getResponse()->getContent()); + + $client->request('post', '/login_check', array('_username' => 'fabien', '_password' => 'bar')); + $this->assertEquals('The presented password is invalid.', $app['security.last_error']($client->getRequest())); + $client->getRequest()->getSession()->save(); + + $client->request('post', '/login_check', array('_username' => 'unknown', '_password' => 'bar')); + $this->assertEquals('Username "unknown" does not exist.', $app['security.last_error']($client->getRequest())); + $client->getRequest()->getSession()->save(); + } + + public function testFakeRoutesAreSerializable() + { + $app = new Application(); + + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'admin' => array( + 'logout' => true, + ), + ), + )); + + $app->boot(); + $app->flush(); + + $this->assertCount(1, unserialize(serialize($app['routes']))); + } + + public function testFirewallWithMethod() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'pattern' => '/', + 'http' => true, + 'methods' => array('POST'), + ), + ), + )); + $app->match('/', function () { return 'foo'; }) + ->method('POST|GET'); + + $request = Request::create('/', 'GET'); + $response = $app->handle($request); + $this->assertEquals(200, $response->getStatusCode()); + + $request = Request::create('/', 'POST'); + $response = $app->handle($request); + $this->assertEquals(401, $response->getStatusCode()); + } + + public function testFirewallWithHost() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'pattern' => '/', + 'http' => true, + 'hosts' => 'localhost2', + ), + ), + )); + $app->get('/', function () { return 'foo'; }) + ->host('localhost2'); + + $app->get('/', function () { return 'foo'; }) + ->host('localhost1'); + + $request = Request::create('http://localhost2/'); + $response = $app->handle($request); + $this->assertEquals(401, $response->getStatusCode()); + + $request = Request::create('http://localhost1/'); + $response = $app->handle($request); + $this->assertEquals(200, $response->getStatusCode()); + } + + public function testUser() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + 'users' => array( + 'fabien' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + ), + ), + ), + )); + $app->get('/', function () { return 'foo'; }); + + $request = Request::create('/'); + $app->handle($request); + $this->assertNull($app['user']); + + $request->headers->set('PHP_AUTH_USER', 'fabien'); + $request->headers->set('PHP_AUTH_PW', 'foo'); + $app->handle($request); + $this->assertInstanceOf('Symfony\Component\Security\Core\User\UserInterface', $app['user']); + $this->assertEquals('fabien', $app['user']->getUsername()); + } + + public function testUserWithNoToken() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + ), + ), + )); + + $request = Request::create('/'); + + $app->get('/', function () { return 'foo'; }); + $app->handle($request); + $this->assertNull($app['user']); + } + + public function testUserWithInvalidUser() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + ), + ), + )); + + $request = Request::create('/'); + $app->boot(); + $app['security.token_storage']->setToken(new UsernamePasswordToken('foo', 'foo', 'foo')); + + $app->get('/', function () { return 'foo'; }); + $app->handle($request); + $this->assertNull($app['user']); + } + + public function testAccessRulePathArray() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + ), + ), + 'security.access_rules' => array( + array(array( + 'path' => '^/admin', + ), 'ROLE_ADMIN'), + ), + )); + + $request = Request::create('/admin'); + $app->boot(); + $accessMap = $app['security.access_map']; + $this->assertEquals($accessMap->getPatterns($request), array( + array('ROLE_ADMIN'), + '', + )); + } + + public function createApplication($authenticationMethod = 'form') + { + $app = new Application(); + $app->register(new SessionServiceProvider()); + + $app = call_user_func(array($this, 'add'.ucfirst($authenticationMethod).'Authentication'), $app); + + $app['session.test'] = true; + + return $app; + } + + private function addFormAuthentication($app) + { + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'login' => array( + 'pattern' => '^/login$', + ), + 'default' => array( + 'pattern' => '^.*$', + 'anonymous' => true, + 'form' => array( + 'require_previous_session' => false, + ), + 'logout' => true, + 'users' => array( + // password is foo + 'fabien' => array('ROLE_USER', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + 'admin' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + ), + ), + ), + 'security.access_rules' => array( + array('^/admin', 'ROLE_ADMIN'), + ), + 'security.role_hierarchy' => array( + 'ROLE_ADMIN' => array('ROLE_USER'), + ), + )); + + $app->get('/login', function (Request $request) use ($app) { + $app['session']->start(); + + return $app['security.last_error']($request); + }); + + $app->get('/', function () use ($app) { + $user = $app['security.token_storage']->getToken()->getUser(); + + $content = is_object($user) ? $user->getUsername() : 'ANONYMOUS'; + + if ($app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_FULLY')) { + $content .= 'AUTHENTICATED'; + } + + if ($app['security.authorization_checker']->isGranted('ROLE_ADMIN')) { + $content .= 'ADMIN'; + } + + return $content; + }); + + $app->get('/admin', function () use ($app) { + return 'admin'; + }); + + return $app; + } + + private function addHttpAuthentication($app) + { + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'http-auth' => array( + 'pattern' => '^.*$', + 'http' => true, + 'users' => array( + // password is foo + 'dennis' => array('ROLE_USER', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + 'admin' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + ), + ), + ), + 'security.access_rules' => array( + array('^/admin', 'ROLE_ADMIN'), + ), + 'security.role_hierarchy' => array( + 'ROLE_ADMIN' => array('ROLE_USER'), + ), + )); + + $app->get('/', function () use ($app) { + $user = $app['security.token_storage']->getToken()->getUser(); + $content = is_object($user) ? $user->getUsername() : 'ANONYMOUS'; + + if ($app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_FULLY')) { + $content .= 'AUTHENTICATED'; + } + + if ($app['security.authorization_checker']->isGranted('ROLE_ADMIN')) { + $content .= 'ADMIN'; + } + + return $content; + }); + + $app->get('/admin', function () use ($app) { + return 'admin'; + }); + + return $app; + } + + private function addGuardAuthentication($app) + { + $app['app.authenticator.token'] = function ($app) { + return new SecurityServiceProviderTest\TokenAuthenticator($app); + }; + + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'guard' => array( + 'pattern' => '^.*$', + 'form' => true, + 'guard' => array( + 'authenticators' => array( + 'app.authenticator.token', + ), + ), + 'users' => array( + 'victoria' => array('ROLE_USER', 'victoriasecret'), + ), + ), + ), + )); + + $app->get('/', function () use ($app) { + $user = $app['security.token_storage']->getToken()->getUser(); + + $content = is_object($user) ? $user->getUsername() : 'ANONYMOUS'; + + return $content; + })->bind('homepage'); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest/TokenAuthenticator.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest/TokenAuthenticator.php new file mode 100644 index 00000000..c569428b --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest/TokenAuthenticator.php @@ -0,0 +1,79 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider\SecurityServiceProviderTest; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +/** + * This class is used to test "guard" authentication with the SecurityServiceProvider. + */ +class TokenAuthenticator extends AbstractGuardAuthenticator +{ + public function getCredentials(Request $request) + { + if (!$token = $request->headers->get('X-AUTH-TOKEN')) { + return; + } + + list($username, $secret) = explode(':', $token); + + return array( + 'username' => $username, + 'secret' => $secret, + ); + } + + public function getUser($credentials, UserProviderInterface $userProvider) + { + return $userProvider->loadUserByUsername($credentials['username']); + } + + public function checkCredentials($credentials, UserInterface $user) + { + // This is not a safe way of validating a password. + return $user->getPassword() === $credentials['secret']; + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) + { + return; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception) + { + $data = array( + 'message' => strtr($exception->getMessageKey(), $exception->getMessageData()), + ); + + return new JsonResponse($data, 403); + } + + public function start(Request $request, AuthenticationException $authException = null) + { + $data = array( + 'message' => 'Authentication Required', + ); + + return new JsonResponse($data, 401); + } + + public function supportsRememberMe() + { + return false; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SerializerServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SerializerServiceProviderTest.php new file mode 100644 index 00000000..c33cea7b --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SerializerServiceProviderTest.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Silex\Provider\SerializerServiceProvider; + +/** + * SerializerServiceProvider test cases. + * + * @author Fabien Potencier + */ +class SerializerServiceProviderTest extends TestCase +{ + public function testRegister() + { + $app = new Application(); + + $app->register(new SerializerServiceProvider()); + + $this->assertInstanceOf("Symfony\Component\Serializer\Serializer", $app['serializer']); + $this->assertTrue($app['serializer']->supportsEncoding('xml')); + $this->assertTrue($app['serializer']->supportsEncoding('json')); + $this->assertTrue($app['serializer']->supportsDecoding('xml')); + $this->assertTrue($app['serializer']->supportsDecoding('json')); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SessionServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SessionServiceProviderTest.php new file mode 100644 index 00000000..fb7ae0cf --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SessionServiceProviderTest.php @@ -0,0 +1,126 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\WebTestCase; +use Silex\Provider\SessionServiceProvider; +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\HttpFoundation\Session; + +/** + * SessionProvider test cases. + * + * @author Igor Wiedler + * @author Fabien Potencier + */ +class SessionServiceProviderTest extends WebTestCase +{ + public function testRegister() + { + /* + * Smoke test + */ + $defaultStorage = $this->app['session.storage.native']; + + $client = $this->createClient(); + + $client->request('get', '/login'); + $this->assertEquals('Logged in successfully.', $client->getResponse()->getContent()); + + $client->request('get', '/account'); + $this->assertEquals('This is your account.', $client->getResponse()->getContent()); + + $client->request('get', '/logout'); + $this->assertEquals('Logged out successfully.', $client->getResponse()->getContent()); + + $client->request('get', '/account'); + $this->assertEquals('You are not logged in.', $client->getResponse()->getContent()); + } + + public function createApplication() + { + $app = new Application(); + + $app->register(new SessionServiceProvider(), array( + 'session.test' => true, + )); + + $app->get('/login', function () use ($app) { + $app['session']->set('logged_in', true); + + return 'Logged in successfully.'; + }); + + $app->get('/account', function () use ($app) { + if (!$app['session']->get('logged_in')) { + return 'You are not logged in.'; + } + + return 'This is your account.'; + }); + + $app->get('/logout', function () use ($app) { + $app['session']->invalidate(); + + return 'Logged out successfully.'; + }); + + return $app; + } + + public function testWithRoutesThatDoesNotUseSession() + { + $app = new Application(); + + $app->register(new SessionServiceProvider(), array( + 'session.test' => true, + )); + + $app->get('/', function () { + return 'A welcome page.'; + }); + + $app->get('/robots.txt', function () { + return 'Informations for robots.'; + }); + + $app['debug'] = true; + unset($app['exception_handler']); + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertEquals('A welcome page.', $client->getResponse()->getContent()); + + $client->request('get', '/robots.txt'); + $this->assertEquals('Informations for robots.', $client->getResponse()->getContent()); + } + + public function testSessionRegister() + { + $app = new Application(); + + $attrs = new Session\Attribute\AttributeBag(); + $flash = new Session\Flash\FlashBag(); + $app->register(new SessionServiceProvider(), array( + 'session.attribute_bag' => $attrs, + 'session.flash_bag' => $flash, + 'session.test' => true, + )); + + $session = $app['session']; + + $this->assertSame($flash, $session->getBag('flashes')); + $this->assertSame($attrs, $session->getBag('attributes')); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SpoolStub.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SpoolStub.php new file mode 100644 index 00000000..c1eb2b0f --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SpoolStub.php @@ -0,0 +1,47 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +class SpoolStub implements \Swift_Spool +{ + private $messages = array(); + public $hasFlushed = false; + + public function getMessages() + { + return $this->messages; + } + + public function start() + { + } + + public function stop() + { + } + + public function isStarted() + { + return count($this->messages) > 0; + } + + public function queueMessage(\Swift_Mime_Message $message) + { + $this->messages[] = clone $message; + } + + public function flushQueue(\Swift_Transport $transport, &$failedRecipients = null) + { + $this->hasFlushed = true; + $this->messages = array(); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SwiftmailerServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SwiftmailerServiceProviderTest.php new file mode 100644 index 00000000..f80c2256 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SwiftmailerServiceProviderTest.php @@ -0,0 +1,134 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Silex\Provider\SwiftmailerServiceProvider; +use Symfony\Component\HttpFoundation\Request; + +class SwiftmailerServiceProviderTest extends TestCase +{ + public function testSwiftMailerServiceIsSwiftMailer() + { + $app = new Application(); + + $app->register(new SwiftmailerServiceProvider()); + $app->boot(); + + $this->assertInstanceOf('Swift_Mailer', $app['mailer']); + } + + public function testSwiftMailerIgnoresSpoolIfDisabled() + { + $app = new Application(); + + $app->register(new SwiftmailerServiceProvider()); + $app->boot(); + + $app['swiftmailer.use_spool'] = false; + + $app['swiftmailer.spooltransport'] = function () { + throw new \Exception('Should not be instantiated'); + }; + + $this->assertInstanceOf('Swift_Mailer', $app['mailer']); + } + + public function testSwiftMailerSendsMailsOnFinish() + { + $app = new Application(); + + $app->register(new SwiftmailerServiceProvider()); + $app->boot(); + + $app['swiftmailer.spool'] = function () { + return new SpoolStub(); + }; + + $app->get('/', function () use ($app) { + $app['mailer']->send(\Swift_Message::newInstance()); + }); + + $this->assertCount(0, $app['swiftmailer.spool']->getMessages()); + + $request = Request::create('/'); + $response = $app->handle($request); + $this->assertCount(1, $app['swiftmailer.spool']->getMessages()); + + $app->terminate($request, $response); + $this->assertTrue($app['swiftmailer.spool']->hasFlushed); + $this->assertCount(0, $app['swiftmailer.spool']->getMessages()); + } + + public function testSwiftMailerAvoidsFlushesIfMailerIsUnused() + { + $app = new Application(); + + $app->register(new SwiftmailerServiceProvider()); + $app->boot(); + + $app['swiftmailer.spool'] = function () { + return new SpoolStub(); + }; + + $app->get('/', function () use ($app) { }); + + $request = Request::create('/'); + $response = $app->handle($request); + $this->assertCount(0, $app['swiftmailer.spool']->getMessages()); + + $app->terminate($request, $response); + $this->assertFalse($app['swiftmailer.spool']->hasFlushed); + } + + public function testSwiftMailerSenderAddress() + { + $app = new Application(); + + $app->register(new SwiftmailerServiceProvider()); + $app->boot(); + + $app['swiftmailer.spool'] = function () { + return new SpoolStub(); + }; + + $app['swiftmailer.sender_address'] = 'foo@example.com'; + + $app['mailer']->send(\Swift_Message::newInstance()); + + $messages = $app['swiftmailer.spool']->getMessages(); + $this->assertCount(1, $messages); + + list($message) = $messages; + $this->assertEquals('foo@example.com', $message->getReturnPath()); + } + + public function testSwiftMailerPlugins() + { + $plugin = $this->getMockBuilder('Swift_Events_TransportChangeListener')->getMock(); + $plugin->expects($this->once())->method('beforeTransportStarted'); + + $app = new Application(); + $app->boot(); + + $app->register(new SwiftmailerServiceProvider()); + + $app['swiftmailer.plugins'] = function ($app) use ($plugin) { + return array($plugin); + }; + + $dispatcher = $app['swiftmailer.transport.eventdispatcher']; + $event = $dispatcher->createTransportChangeEvent(new \Swift_Transport_NullTransport($dispatcher)); + $dispatcher->dispatchEvent($event, 'beforeTransportStarted'); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/TranslationServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/TranslationServiceProviderTest.php new file mode 100644 index 00000000..b4132f9e --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/TranslationServiceProviderTest.php @@ -0,0 +1,182 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Silex\Provider\TranslationServiceProvider; +use Silex\Provider\LocaleServiceProvider; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * TranslationProvider test cases. + * + * @author Daniel Tschinder + */ +class TranslationServiceProviderTest extends TestCase +{ + /** + * @return Application + */ + protected function getPreparedApp() + { + $app = new Application(); + + $app->register(new LocaleServiceProvider()); + $app->register(new TranslationServiceProvider()); + $app['translator.domains'] = array( + 'messages' => array( + 'en' => array( + 'key1' => 'The translation', + 'key_only_english' => 'Foo', + 'key2' => 'One apple|%count% apples', + 'test' => array( + 'key' => 'It works', + ), + ), + 'de' => array( + 'key1' => 'The german translation', + 'key2' => 'One german apple|%count% german apples', + 'test' => array( + 'key' => 'It works in german', + ), + ), + ), + ); + + return $app; + } + + public function transChoiceProvider() + { + return array( + array('key2', 0, null, '0 apples'), + array('key2', 1, null, 'One apple'), + array('key2', 2, null, '2 apples'), + array('key2', 0, 'de', '0 german apples'), + array('key2', 1, 'de', 'One german apple'), + array('key2', 2, 'de', '2 german apples'), + array('key2', 0, 'ru', '0 apples'), // fallback + array('key2', 1, 'ru', 'One apple'), // fallback + array('key2', 2, 'ru', '2 apples'), // fallback + ); + } + + public function transProvider() + { + return array( + array('key1', null, 'The translation'), + array('key1', 'de', 'The german translation'), + array('key1', 'ru', 'The translation'), // fallback + array('test.key', null, 'It works'), + array('test.key', 'de', 'It works in german'), + array('test.key', 'ru', 'It works'), // fallback + ); + } + + /** + * @dataProvider transProvider + */ + public function testTransForDefaultLanguage($key, $locale, $expected) + { + $app = $this->getPreparedApp(); + + $result = $app['translator']->trans($key, array(), null, $locale); + + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider transChoiceProvider + */ + public function testTransChoiceForDefaultLanguage($key, $number, $locale, $expected) + { + $app = $this->getPreparedApp(); + + $result = $app['translator']->transChoice($key, $number, array('%count%' => $number), null, $locale); + $this->assertEquals($expected, $result); + } + + public function testFallbacks() + { + $app = $this->getPreparedApp(); + $app['locale_fallbacks'] = array('de', 'en'); + + // fallback to english + $result = $app['translator']->trans('key_only_english', array(), null, 'ru'); + $this->assertEquals('Foo', $result); + + // fallback to german + $result = $app['translator']->trans('key1', array(), null, 'ru'); + $this->assertEquals('The german translation', $result); + } + + public function testLocale() + { + $app = $this->getPreparedApp(); + $app->get('/', function () use ($app) { return $app['translator']->getLocale(); }); + $response = $app->handle(Request::create('/')); + $this->assertEquals('en', $response->getContent()); + + $app = $this->getPreparedApp(); + $app->get('/', function () use ($app) { return $app['translator']->getLocale(); }); + $request = Request::create('/'); + $request->setLocale('fr'); + $response = $app->handle($request); + $this->assertEquals('fr', $response->getContent()); + + $app = $this->getPreparedApp(); + $app->get('/{_locale}', function () use ($app) { return $app['translator']->getLocale(); }); + $response = $app->handle(Request::create('/es')); + $this->assertEquals('es', $response->getContent()); + } + + public function testLocaleInSubRequests() + { + $app = $this->getPreparedApp(); + $app->get('/embed/{_locale}', function () use ($app) { return $app['translator']->getLocale(); }); + $app->get('/{_locale}', function () use ($app) { + return $app['translator']->getLocale(). + $app->handle(Request::create('/embed/es'), HttpKernelInterface::SUB_REQUEST)->getContent(). + $app['translator']->getLocale(); + }); + $response = $app->handle(Request::create('/fr')); + $this->assertEquals('fresfr', $response->getContent()); + + $app = $this->getPreparedApp(); + $app->get('/embed', function () use ($app) { return $app['translator']->getLocale(); }); + $app->get('/{_locale}', function () use ($app) { + return $app['translator']->getLocale(). + $app->handle(Request::create('/embed'), HttpKernelInterface::SUB_REQUEST)->getContent(). + $app['translator']->getLocale(); + }); + $response = $app->handle(Request::create('/fr')); + // locale in sub-request must be "en" as this is the value if the sub-request is converted to an ESI + $this->assertEquals('frenfr', $response->getContent()); + } + + public function testLocaleWithBefore() + { + $app = $this->getPreparedApp(); + $app->before(function (Request $request) { $request->setLocale('fr'); }, Application::EARLY_EVENT); + $app->get('/embed', function () use ($app) { return $app['translator']->getLocale(); }); + $app->get('/', function () use ($app) { + return $app['translator']->getLocale(). + $app->handle(Request::create('/embed'), HttpKernelInterface::SUB_REQUEST)->getContent(). + $app['translator']->getLocale(); + }); + $response = $app->handle(Request::create('/')); + // locale in sub-request is "en" as the before filter is only executed for the main request + $this->assertEquals('frenfr', $response->getContent()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/TwigServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/TwigServiceProviderTest.php new file mode 100644 index 00000000..1b5aef2c --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/TwigServiceProviderTest.php @@ -0,0 +1,165 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Fig\Link\Link; +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Silex\Provider\CsrfServiceProvider; +use Silex\Provider\FormServiceProvider; +use Silex\Provider\TwigServiceProvider; +use Silex\Provider\AssetServiceProvider; +use Symfony\Bridge\Twig\Extension\WebLinkExtension; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\WebLink\HttpHeaderSerializer; + +/** + * TwigProvider test cases. + * + * @author Igor Wiedler + */ +class TwigServiceProviderTest extends TestCase +{ + public function testRegisterAndRender() + { + $app = new Application(); + + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array('hello' => 'Hello {{ name }}!'), + )); + + $app->get('/hello/{name}', function ($name) use ($app) { + return $app['twig']->render('hello', array('name' => $name)); + }); + + $request = Request::create('/hello/john'); + $response = $app->handle($request); + $this->assertEquals('Hello john!', $response->getContent()); + } + + public function testLoaderPriority() + { + $app = new Application(); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array('foo' => 'foo'), + )); + $loader = $this->getMockBuilder('\Twig_LoaderInterface')->getMock(); + $loader->expects($this->never())->method('getSourceContext'); + $app['twig.loader.filesystem'] = function ($app) use ($loader) { + return $loader; + }; + $this->assertEquals('foo', $app['twig.loader']->getSourceContext('foo')->getCode()); + } + + public function testHttpFoundationIntegration() + { + $app = new Application(); + $app['request_stack']->push(Request::create('/dir1/dir2/file')); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array( + 'absolute' => '{{ absolute_url("foo.css") }}', + 'relative' => '{{ relative_path("/dir1/foo.css") }}', + ), + )); + + $this->assertEquals('http://localhost/dir1/dir2/foo.css', $app['twig']->render('absolute')); + $this->assertEquals('../foo.css', $app['twig']->render('relative')); + } + + public function testAssetIntegration() + { + $app = new Application(); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array('hello' => '{{ asset("/foo.css") }}'), + )); + $app->register(new AssetServiceProvider(), array( + 'assets.version' => 1, + )); + + $this->assertEquals('/foo.css?1', $app['twig']->render('hello')); + } + + public function testGlobalVariable() + { + $app = new Application(); + $app['request_stack']->push(Request::create('/?name=Fabien')); + + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array('hello' => '{{ global.request.get("name") }}'), + )); + + $this->assertEquals('Fabien', $app['twig']->render('hello')); + } + + public function testFormFactory() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $app->register(new CsrfServiceProvider()); + $app->register(new TwigServiceProvider()); + + $this->assertInstanceOf('Twig_Environment', $app['twig'], 'Service twig is created successful.'); + $this->assertInstanceOf('Symfony\Bridge\Twig\Form\TwigRendererEngine', $app['twig.form.engine'], 'Service twig.form.engine is created successful.'); + $this->assertInstanceOf('Symfony\Bridge\Twig\Form\TwigRenderer', $app['twig.form.renderer'], 'Service twig.form.renderer is created successful.'); + } + + public function testFormWithoutCsrf() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $app->register(new TwigServiceProvider()); + + $this->assertInstanceOf('Twig_Environment', $app['twig']); + } + + public function testFormatParameters() + { + $app = new Application(); + + $timezone = new \DateTimeZone('Europe/Paris'); + + $app->register(new TwigServiceProvider(), array( + 'twig.date.format' => 'Y-m-d', + 'twig.date.interval_format' => '%h hours', + 'twig.date.timezone' => $timezone, + 'twig.number_format.decimals' => 2, + 'twig.number_format.decimal_point' => ',', + 'twig.number_format.thousands_separator' => ' ', + )); + + $twig = $app['twig']; + + $this->assertSame(array('Y-m-d', '%h hours'), $twig->getExtension('Twig_Extension_Core')->getDateFormat()); + $this->assertSame($timezone, $twig->getExtension('Twig_Extension_Core')->getTimezone()); + $this->assertSame(array(2, ',', ' '), $twig->getExtension('Twig_Extension_Core')->getNumberFormat()); + } + + public function testWebLinkIntegration() + { + if (!class_exists(HttpHeaderSerializer::class) || !class_exists(WebLinkExtension::class)) { + $this->markTestSkipped('Twig WebLink extension not available.'); + } + + $app = new Application(); + $app['request_stack']->push($request = Request::create('/')); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array( + 'preload' => '{{ preload("/foo.css") }}', + ), + )); + + $this->assertEquals('/foo.css', $app['twig']->render('preload')); + + $link = new Link('preload', '/foo.css'); + $this->assertEquals(array($link), array_values($request->attributes->get('_links')->getLinks())); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest.php new file mode 100644 index 00000000..6abb2644 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest.php @@ -0,0 +1,206 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Silex\Provider\TranslationServiceProvider; +use Silex\Provider\ValidatorServiceProvider; +use Silex\Provider\FormServiceProvider; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Validator\Constraints as Assert; +use Silex\Tests\Provider\ValidatorServiceProviderTest\Constraint\Custom; +use Silex\Tests\Provider\ValidatorServiceProviderTest\Constraint\CustomValidator; +use Symfony\Component\Validator\ValidatorInterface as LegacyValidatorInterface; +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * ValidatorServiceProvider. + * + * Javier Lopez + */ +class ValidatorServiceProviderTest extends TestCase +{ + public function testRegister() + { + $app = new Application(); + $app->register(new ValidatorServiceProvider()); + $app->register(new FormServiceProvider()); + + return $app; + } + + public function testRegisterWithCustomValidators() + { + $app = new Application(); + + $app['custom.validator'] = function () { + return new CustomValidator(); + }; + + $app->register(new ValidatorServiceProvider(), array( + 'validator.validator_service_ids' => array( + 'test.custom.validator' => 'custom.validator', + ), + )); + + return $app; + } + + /** + * @depends testRegisterWithCustomValidators + */ + public function testConstraintValidatorFactory($app) + { + $this->assertInstanceOf('Silex\Provider\Validator\ConstraintValidatorFactory', $app['validator.validator_factory']); + + $validator = $app['validator.validator_factory']->getInstance(new Custom()); + $this->assertInstanceOf('Silex\Tests\Provider\ValidatorServiceProviderTest\Constraint\CustomValidator', $validator); + } + + /** + * @depends testRegister + */ + public function testConstraintValidatorFactoryWithExpression($app) + { + $constraint = new Assert\Expression('true'); + $validator = $app['validator.validator_factory']->getInstance($constraint); + $this->assertInstanceOf('Symfony\Component\Validator\Constraints\ExpressionValidator', $validator); + } + + /** + * @depends testRegister + */ + public function testValidatorServiceIsAValidator($app) + { + $this->assertTrue($app['validator'] instanceof ValidatorInterface || $app['validator'] instanceof LegacyValidatorInterface); + } + + /** + * @depends testRegister + * @dataProvider getTestValidatorConstraintProvider + */ + public function testValidatorConstraint($email, $isValid, $nbGlobalError, $nbEmailError, $app) + { + $constraints = new Assert\Collection(array( + 'email' => array( + new Assert\NotBlank(), + new Assert\Email(), + ), + )); + + $builder = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array(), array( + 'constraints' => $constraints, + )); + + $form = $builder + ->add('email', 'Symfony\Component\Form\Extension\Core\Type\EmailType', array('label' => 'Email')) + ->getForm() + ; + + $form->submit(array('email' => $email)); + + $this->assertEquals($isValid, $form->isValid()); + $this->assertEquals($nbGlobalError, count($form->getErrors())); + $this->assertEquals($nbEmailError, count($form->offsetGet('email')->getErrors())); + } + + public function testValidatorWillNotAddNonexistentTranslationFiles() + { + $app = new Application(array( + 'locale' => 'nonexistent', + )); + + $app->register(new ValidatorServiceProvider()); + $app->register(new TranslationServiceProvider(), array( + 'locale_fallbacks' => array(), + )); + + $app['validator']; + $translator = $app['translator']; + + try { + $translator->trans('test'); + } catch (NotFoundResourceException $e) { + $this->fail('Validator should not add a translation resource that does not exist'); + } + } + + public function getTestValidatorConstraintProvider() + { + // Email, form is valid, nb global error, nb email error + return array( + array('', false, 0, 1), + array('not an email', false, 0, 1), + array('email@sample.com', true, 0, 0), + ); + } + + /** + * @dataProvider getAddResourceData + */ + public function testAddResource($registerValidatorFirst) + { + $app = new Application(); + $app['locale'] = 'fr'; + + $app->register(new ValidatorServiceProvider()); + $app->register(new TranslationServiceProvider()); + $app['translator'] = $app->extend('translator', function ($translator, $app) { + $translator->addResource('array', array('This value should not be blank.' => 'Pas vide'), 'fr', 'validators'); + + return $translator; + }); + + if ($registerValidatorFirst) { + $app['validator']; + } + + $this->assertEquals('Pas vide', $app['translator']->trans('This value should not be blank.', array(), 'validators', 'fr')); + } + + public function getAddResourceData() + { + return array(array(false), array(true)); + } + + public function testAddResourceAlternate() + { + $app = new Application(); + $app['locale'] = 'fr'; + + $app->register(new ValidatorServiceProvider()); + $app->register(new TranslationServiceProvider()); + $app->factory($app->extend('translator.resources', function ($resources, $app) { + $resources = array_merge($resources, array( + array('array', array('This value should not be blank.' => 'Pas vide'), 'fr', 'validators'), + )); + + return $resources; + })); + + $app['validator']; + + $this->assertEquals('Pas vide', $app['translator']->trans('This value should not be blank.', array(), 'validators', 'fr')); + } + + public function testTranslatorResourcesIsArray() + { + $app = new Application(); + $app['locale'] = 'fr'; + + $app->register(new ValidatorServiceProvider()); + $app->register(new TranslationServiceProvider()); + + $this->assertInternalType('array', $app['translator.resources']); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/Custom.php b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/Custom.php new file mode 100644 index 00000000..bef911aa --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/Custom.php @@ -0,0 +1,29 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider\ValidatorServiceProviderTest\Constraint; + +use Symfony\Component\Validator\Constraint; + +/** + * @author Alex Kalyvitis + */ +class Custom extends Constraint +{ + public $message = 'This field must be ...'; + public $table; + public $field; + + public function validatedBy() + { + return 'test.custom.validator'; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/CustomValidator.php b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/CustomValidator.php new file mode 100644 index 00000000..856927db --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/CustomValidator.php @@ -0,0 +1,32 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider\ValidatorServiceProviderTest\Constraint; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; + +/** + * @author Alex Kalyvitis + */ +class CustomValidator extends ConstraintValidator +{ + public function isValid($value, Constraint $constraint) + { + // Validate... + return true; + } + + public function validate($value, Constraint $constraint) + { + return $this->isValid($value, $constraint); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Route/SecurityRoute.php b/vendor/silex/silex/tests/Silex/Tests/Route/SecurityRoute.php new file mode 100644 index 00000000..48237194 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Route/SecurityRoute.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Route; + +use Silex\Route; + +class SecurityRoute extends Route +{ + use Route\SecurityTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Route/SecurityTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Route/SecurityTraitTest.php new file mode 100644 index 00000000..e984fefb --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Route/SecurityTraitTest.php @@ -0,0 +1,86 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Route; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Silex\Provider\SecurityServiceProvider; +use Symfony\Component\HttpFoundation\Request; + +/** + * SecurityTrait test cases. + * + * @author Fabien Potencier + */ +class SecurityTraitTest extends TestCase +{ + public function testSecureWithNoAuthenticatedUser() + { + $app = $this->createApplication(); + + $app->get('/', function () { return 'foo'; }) + ->secure('ROLE_ADMIN') + ; + + $request = Request::create('/'); + $response = $app->handle($request); + $this->assertEquals(401, $response->getStatusCode()); + } + + public function testSecureWithAuthorizedRoles() + { + $app = $this->createApplication(); + + $app->get('/', function () { return 'foo'; }) + ->secure('ROLE_ADMIN') + ; + + $request = Request::create('/'); + $request->headers->set('PHP_AUTH_USER', 'fabien'); + $request->headers->set('PHP_AUTH_PW', 'foo'); + $response = $app->handle($request); + $this->assertEquals(200, $response->getStatusCode()); + } + + public function testSecureWithUnauthorizedRoles() + { + $app = $this->createApplication(); + + $app->get('/', function () { return 'foo'; }) + ->secure('ROLE_SUPER_ADMIN') + ; + + $request = Request::create('/'); + $request->headers->set('PHP_AUTH_USER', 'fabien'); + $request->headers->set('PHP_AUTH_PW', 'foo'); + $response = $app->handle($request); + $this->assertEquals(403, $response->getStatusCode()); + } + + private function createApplication() + { + $app = new Application(); + $app['route_class'] = 'Silex\Tests\Route\SecurityRoute'; + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + 'users' => array( + 'fabien' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + ), + ), + ), + )); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/RouterTest.php b/vendor/silex/silex/tests/Silex/Tests/RouterTest.php new file mode 100644 index 00000000..0d0bf518 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/RouterTest.php @@ -0,0 +1,286 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; + +/** + * Router test cases. + * + * @author Igor Wiedler + */ +class RouterTest extends TestCase +{ + public function testMapRouting() + { + $app = new Application(); + + $app->match('/foo', function () { + return 'foo'; + }); + + $app->match('/bar', function () { + return 'bar'; + }); + + $app->match('/', function () { + return 'root'; + }); + + $this->checkRouteResponse($app, '/foo', 'foo'); + $this->checkRouteResponse($app, '/bar', 'bar'); + $this->checkRouteResponse($app, '/', 'root'); + } + + public function testStatusCode() + { + $app = new Application(); + + $app->put('/created', function () { + return new Response('', 201); + }); + + $app->match('/forbidden', function () { + return new Response('', 403); + }); + + $app->match('/not_found', function () { + return new Response('', 404); + }); + + $request = Request::create('/created', 'put'); + $response = $app->handle($request); + $this->assertEquals(201, $response->getStatusCode()); + + $request = Request::create('/forbidden'); + $response = $app->handle($request); + $this->assertEquals(403, $response->getStatusCode()); + + $request = Request::create('/not_found'); + $response = $app->handle($request); + $this->assertEquals(404, $response->getStatusCode()); + } + + public function testRedirect() + { + $app = new Application(); + + $app->match('/redirect', function () { + return new RedirectResponse('/target'); + }); + + $app->match('/redirect2', function () use ($app) { + return $app->redirect('/target2'); + }); + + $request = Request::create('/redirect'); + $response = $app->handle($request); + $this->assertTrue($response->isRedirect('/target')); + + $request = Request::create('/redirect2'); + $response = $app->handle($request); + $this->assertTrue($response->isRedirect('/target2')); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testMissingRoute() + { + $app = new Application(); + unset($app['exception_handler']); + + $request = Request::create('/baz'); + $app->handle($request); + } + + public function testMethodRouting() + { + $app = new Application(); + + $app->match('/foo', function () { + return 'foo'; + }); + + $app->match('/bar', function () { + return 'bar'; + })->method('GET|POST'); + + $app->get('/resource', function () { + return 'get resource'; + }); + + $app->post('/resource', function () { + return 'post resource'; + }); + + $app->put('/resource', function () { + return 'put resource'; + }); + + $app->patch('/resource', function () { + return 'patch resource'; + }); + + $app->delete('/resource', function () { + return 'delete resource'; + }); + + $this->checkRouteResponse($app, '/foo', 'foo'); + $this->checkRouteResponse($app, '/bar', 'bar'); + $this->checkRouteResponse($app, '/bar', 'bar', 'post'); + $this->checkRouteResponse($app, '/resource', 'get resource'); + $this->checkRouteResponse($app, '/resource', 'post resource', 'post'); + $this->checkRouteResponse($app, '/resource', 'put resource', 'put'); + $this->checkRouteResponse($app, '/resource', 'patch resource', 'patch'); + $this->checkRouteResponse($app, '/resource', 'delete resource', 'delete'); + } + + public function testRequestShouldBeStoredRegardlessOfRouting() + { + $app = new Application(); + + $app->get('/foo', function (Request $request) use ($app) { + return new Response($request->getRequestUri()); + }); + + $app->error(function ($e, Request $request, $code) use ($app) { + return new Response($request->getRequestUri()); + }); + + foreach (array('/foo', '/bar') as $path) { + $request = Request::create($path); + $response = $app->handle($request); + $this->assertContains($path, $response->getContent()); + } + } + + public function testTrailingSlashBehavior() + { + $app = new Application(); + + $app->get('/foo/', function () use ($app) { + return new Response('ok'); + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + + $this->assertEquals(301, $response->getStatusCode()); + $this->assertEquals('/foo/', $response->getTargetUrl()); + } + + public function testHostSpecification() + { + $route = new \Silex\Route(); + + $this->assertSame($route, $route->host('{locale}.example.com')); + $this->assertEquals('{locale}.example.com', $route->getHost()); + } + + public function testRequireHttpRedirect() + { + $app = new Application(); + + $app->match('/secured', function () { + return 'secured content'; + }) + ->requireHttp(); + + $request = Request::create('https://example.com/secured'); + $response = $app->handle($request); + $this->assertTrue($response->isRedirect('http://example.com/secured')); + } + + public function testRequireHttpsRedirect() + { + $app = new Application(); + + $app->match('/secured', function () { + return 'secured content'; + }) + ->requireHttps(); + + $request = Request::create('http://example.com/secured'); + $response = $app->handle($request); + $this->assertTrue($response->isRedirect('https://example.com/secured')); + } + + public function testRequireHttpsRedirectIncludesQueryString() + { + $app = new Application(); + + $app->match('/secured', function () { + return 'secured content'; + }) + ->requireHttps(); + + $request = Request::create('http://example.com/secured?query=string'); + $response = $app->handle($request); + $this->assertTrue($response->isRedirect('https://example.com/secured?query=string')); + } + + public function testConditionOnRoute() + { + $app = new Application(); + $app->match('/secured', function () { + return 'secured content'; + }) + ->when('request.isSecure() == true'); + + $request = Request::create('http://example.com/secured'); + $response = $app->handle($request); + $this->assertEquals(404, $response->getStatusCode()); + } + + public function testClassNameControllerSyntax() + { + $app = new Application(); + + $app->get('/foo', 'Silex\Tests\MyController::getFoo'); + + $this->checkRouteResponse($app, '/foo', 'foo'); + } + + public function testClassNameControllerSyntaxWithStaticMethod() + { + $app = new Application(); + + $app->get('/bar', 'Silex\Tests\MyController::getBar'); + + $this->checkRouteResponse($app, '/bar', 'bar'); + } + + protected function checkRouteResponse(Application $app, $path, $expectedContent, $method = 'get', $message = null) + { + $request = Request::create($path, $method); + $response = $app->handle($request); + $this->assertEquals($expectedContent, $response->getContent(), $message); + } +} + +class MyController +{ + public function getFoo() + { + return 'foo'; + } + + public static function getBar() + { + return 'bar'; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverRouterTest.php b/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverRouterTest.php new file mode 100644 index 00000000..4bc88a45 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverRouterTest.php @@ -0,0 +1,43 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Silex\Provider\ServiceControllerServiceProvider; +use Symfony\Component\HttpFoundation\Request; + +/** + * Router test cases, using the ServiceControllerResolver. + */ +class ServiceControllerResolverRouterTest extends RouterTest +{ + public function testServiceNameControllerSyntax() + { + $app = new Application(); + $app->register(new ServiceControllerServiceProvider()); + + $app['service_name'] = function () { + return new MyController(); + }; + + $app->get('/bar', 'service_name:getBar'); + + $this->checkRouteResponse($app, '/bar', 'bar'); + } + + protected function checkRouteResponse(Application $app, $path, $expectedContent, $method = 'get', $message = null) + { + $request = Request::create($path, $method); + $response = $app->handle($request); + $this->assertEquals($expectedContent, $response->getContent(), $message); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverTest.php b/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverTest.php new file mode 100644 index 00000000..398e2cef --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverTest.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Tests; + +use PHPUnit\Framework\TestCase; +use Silex\ServiceControllerResolver; +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; + +/** + * Unit tests for ServiceControllerResolver, see ServiceControllerResolverRouterTest for some + * integration tests. + */ +class ServiceControllerResolverTest extends Testcase +{ + private $app; + private $mockCallbackResolver; + private $mockResolver; + private $resolver; + + public function setup() + { + $this->mockResolver = $this->getMockBuilder('Symfony\Component\HttpKernel\Controller\ControllerResolverInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->mockCallbackResolver = $this->getMockBuilder('Silex\CallbackResolver') + ->disableOriginalConstructor() + ->getMock(); + + $this->app = new Application(); + $this->resolver = new ServiceControllerResolver($this->mockResolver, $this->mockCallbackResolver); + } + + public function testShouldResolveServiceController() + { + $this->mockCallbackResolver->expects($this->once()) + ->method('isValid') + ->will($this->returnValue(true)); + + $this->mockCallbackResolver->expects($this->once()) + ->method('convertCallback') + ->with('some_service:methodName') + ->will($this->returnValue(array('callback'))); + + $this->app['some_service'] = function () { return new \stdClass(); }; + + $req = Request::create('/'); + $req->attributes->set('_controller', 'some_service:methodName'); + + $this->assertEquals(array('callback'), $this->resolver->getController($req)); + } + + public function testShouldUnresolvedControllerNames() + { + $req = Request::create('/'); + $req->attributes->set('_controller', 'some_class::methodName'); + + $this->mockCallbackResolver->expects($this->once()) + ->method('isValid') + ->with('some_class::methodName') + ->will($this->returnValue(false)); + + $this->mockResolver->expects($this->once()) + ->method('getController') + ->with($req) + ->will($this->returnValue(123)); + + $this->assertEquals(123, $this->resolver->getController($req)); + } + + public function testShouldDelegateGetArguments() + { + $req = Request::create('/'); + $this->mockResolver->expects($this->once()) + ->method('getArguments') + ->with($req) + ->will($this->returnValue(123)); + + $this->assertEquals(123, $this->resolver->getArguments($req, function () {})); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/StreamTest.php b/vendor/silex/silex/tests/Silex/Tests/StreamTest.php new file mode 100644 index 00000000..c494d5b4 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/StreamTest.php @@ -0,0 +1,53 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use PHPUnit\Framework\TestCase; +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; + +/** + * Stream test cases. + * + * @author Igor Wiedler + */ +class StreamTest extends TestCase +{ + public function testStreamReturnsStreamingResponse() + { + $app = new Application(); + + $response = $app->stream(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response); + $this->assertSame(false, $response->getContent()); + } + + public function testStreamActuallyStreams() + { + $i = 0; + + $stream = function () use (&$i) { + ++$i; + }; + + $app = new Application(); + $response = $app->stream($stream); + + $this->assertEquals(0, $i); + + $request = Request::create('/stream'); + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(1, $i); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/WebTestCaseTest.php b/vendor/silex/silex/tests/Silex/Tests/WebTestCaseTest.php new file mode 100644 index 00000000..474ffc38 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/WebTestCaseTest.php @@ -0,0 +1,78 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Silex\WebTestCase; +use Symfony\Component\HttpFoundation\Request; + +/** + * Functional test cases. + * + * @author Igor Wiedler + */ +class WebTestCaseTest extends WebTestCase +{ + public function createApplication() + { + $app = new Application(); + + $app->match('/hello', function () { + return 'world'; + }); + + $app->match('/html', function () { + return '

title

'; + }); + + $app->match('/server', function (Request $request) use ($app) { + $user = $request->server->get('PHP_AUTH_USER'); + $pass = $request->server->get('PHP_AUTH_PW'); + + return "

$user:$pass

"; + }); + + return $app; + } + + public function testGetHello() + { + $client = $this->createClient(); + + $client->request('GET', '/hello'); + $response = $client->getResponse(); + $this->assertTrue($response->isSuccessful()); + $this->assertEquals('world', $response->getContent()); + } + + public function testCrawlerFilter() + { + $client = $this->createClient(); + + $crawler = $client->request('GET', '/html'); + $this->assertEquals('title', $crawler->filter('h1')->text()); + } + + public function testServerVariables() + { + $user = 'klaus'; + $pass = '123456'; + + $client = $this->createClient(array( + 'PHP_AUTH_USER' => $user, + 'PHP_AUTH_PW' => $pass, + )); + + $crawler = $client->request('GET', '/server'); + $this->assertEquals("$user:$pass", $crawler->filter('h1')->text()); + } +} diff --git a/vendor/symfony/debug/.gitignore b/vendor/symfony/debug/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/debug/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/debug/BufferingLogger.php b/vendor/symfony/debug/BufferingLogger.php new file mode 100644 index 00000000..a2ed75b9 --- /dev/null +++ b/vendor/symfony/debug/BufferingLogger.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +use Psr\Log\AbstractLogger; + +/** + * A buffering logger that stacks logs for later. + * + * @author Nicolas Grekas + */ +class BufferingLogger extends AbstractLogger +{ + private $logs = array(); + + public function log($level, $message, array $context = array()) + { + $this->logs[] = array($level, $message, $context); + } + + public function cleanLogs() + { + $logs = $this->logs; + $this->logs = array(); + + return $logs; + } +} diff --git a/vendor/symfony/debug/CHANGELOG.md b/vendor/symfony/debug/CHANGELOG.md new file mode 100644 index 00000000..a853b7a0 --- /dev/null +++ b/vendor/symfony/debug/CHANGELOG.md @@ -0,0 +1,59 @@ +CHANGELOG +========= + +3.3.0 +----- + +* deprecated the `ContextErrorException` class: use \ErrorException directly now + +3.2.0 +----- + +* `FlattenException::getTrace()` now returns additional type descriptions + `integer` and `float`. + + +3.0.0 +----- + +* removed classes, methods and interfaces deprecated in 2.x + +2.8.0 +----- + +* added BufferingLogger for errors that happen before a proper logger is configured +* allow throwing from `__toString()` with `return trigger_error($e, E_USER_ERROR);` +* deprecate ExceptionHandler::createResponse + +2.7.0 +----- + +* added deprecations checking for parent interfaces/classes to DebugClassLoader +* added ZTS support to symfony_debug extension +* added symfony_debug_backtrace() to symfony_debug extension + to track the backtrace of fatal errors + +2.6.0 +----- + +* generalized ErrorHandler and ExceptionHandler, + with some new methods and others deprecated +* enhanced error messages for uncaught exceptions + +2.5.0 +----- + +* added ExceptionHandler::setHandler() +* added UndefinedMethodFatalErrorHandler +* deprecated DummyException + +2.4.0 +----- + + * added a DebugClassLoader able to wrap any autoloader providing a findFile method + * improved error messages for not found classes and functions + +2.3.0 +----- + + * added the component diff --git a/vendor/symfony/debug/Debug.php b/vendor/symfony/debug/Debug.php new file mode 100644 index 00000000..e3665ae5 --- /dev/null +++ b/vendor/symfony/debug/Debug.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +/** + * Registers all the debug tools. + * + * @author Fabien Potencier + */ +class Debug +{ + private static $enabled = false; + + /** + * Enables the debug tools. + * + * This method registers an error handler and an exception handler. + * + * If the Symfony ClassLoader component is available, a special + * class loader is also registered. + * + * @param int $errorReportingLevel The level of error reporting you want + * @param bool $displayErrors Whether to display errors (for development) or just log them (for production) + */ + public static function enable($errorReportingLevel = E_ALL, $displayErrors = true) + { + if (static::$enabled) { + return; + } + + static::$enabled = true; + + if (null !== $errorReportingLevel) { + error_reporting($errorReportingLevel); + } else { + error_reporting(E_ALL); + } + + if ('cli' !== PHP_SAPI) { + ini_set('display_errors', 0); + ExceptionHandler::register(); + } elseif ($displayErrors && (!ini_get('log_errors') || ini_get('error_log'))) { + // CLI - display errors only if they're not already logged to STDERR + ini_set('display_errors', 1); + } + if ($displayErrors) { + ErrorHandler::register(new ErrorHandler(new BufferingLogger())); + } else { + ErrorHandler::register()->throwAt(0, true); + } + + DebugClassLoader::enable(); + } +} diff --git a/vendor/symfony/debug/DebugClassLoader.php b/vendor/symfony/debug/DebugClassLoader.php new file mode 100644 index 00000000..3a8b7d24 --- /dev/null +++ b/vendor/symfony/debug/DebugClassLoader.php @@ -0,0 +1,354 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +/** + * Autoloader checking if the class is really defined in the file found. + * + * The ClassLoader will wrap all registered autoloaders + * and will throw an exception if a file is found but does + * not declare the class. + * + * @author Fabien Potencier + * @author Christophe Coevoet + * @author Nicolas Grekas + */ +class DebugClassLoader +{ + private $classLoader; + private $isFinder; + private $loaded = array(); + private static $caseCheck; + private static $final = array(); + private static $finalMethods = array(); + private static $deprecated = array(); + private static $php7Reserved = array('int', 'float', 'bool', 'string', 'true', 'false', 'null'); + private static $darwinCache = array('/' => array('/', array())); + + /** + * Constructor. + * + * @param callable $classLoader A class loader + */ + public function __construct(callable $classLoader) + { + $this->classLoader = $classLoader; + $this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile'); + + if (!isset(self::$caseCheck)) { + $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), DIRECTORY_SEPARATOR); + $i = strrpos($file, DIRECTORY_SEPARATOR); + $dir = substr($file, 0, 1 + $i); + $file = substr($file, 1 + $i); + $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file); + $test = realpath($dir.$test); + + if (false === $test || false === $i) { + // filesystem is case sensitive + self::$caseCheck = 0; + } elseif (substr($test, -strlen($file)) === $file) { + // filesystem is case insensitive and realpath() normalizes the case of characters + self::$caseCheck = 1; + } elseif (false !== stripos(PHP_OS, 'darwin')) { + // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters + self::$caseCheck = 2; + } else { + // filesystem case checks failed, fallback to disabling them + self::$caseCheck = 0; + } + } + } + + /** + * Gets the wrapped class loader. + * + * @return callable The wrapped class loader + */ + public function getClassLoader() + { + return $this->classLoader; + } + + /** + * Wraps all autoloaders. + */ + public static function enable() + { + // Ensures we don't hit https://bugs.php.net/42098 + class_exists('Symfony\Component\Debug\ErrorHandler'); + class_exists('Psr\Log\LogLevel'); + + if (!is_array($functions = spl_autoload_functions())) { + return; + } + + foreach ($functions as $function) { + spl_autoload_unregister($function); + } + + foreach ($functions as $function) { + if (!is_array($function) || !$function[0] instanceof self) { + $function = array(new static($function), 'loadClass'); + } + + spl_autoload_register($function); + } + } + + /** + * Disables the wrapping. + */ + public static function disable() + { + if (!is_array($functions = spl_autoload_functions())) { + return; + } + + foreach ($functions as $function) { + spl_autoload_unregister($function); + } + + foreach ($functions as $function) { + if (is_array($function) && $function[0] instanceof self) { + $function = $function[0]->getClassLoader(); + } + + spl_autoload_register($function); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * + * @return bool|null True, if loaded + * + * @throws \RuntimeException + */ + public function loadClass($class) + { + ErrorHandler::stackErrors(); + + try { + if ($this->isFinder && !isset($this->loaded[$class])) { + $this->loaded[$class] = true; + if ($file = $this->classLoader[0]->findFile($class)) { + require $file; + } + } else { + call_user_func($this->classLoader, $class); + $file = false; + } + } finally { + ErrorHandler::unstackErrors(); + } + + $exists = class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); + + if ($class && '\\' === $class[0]) { + $class = substr($class, 1); + } + + if ($exists) { + $refl = new \ReflectionClass($class); + $name = $refl->getName(); + + if ($name !== $class && 0 === strcasecmp($name, $class)) { + throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name)); + } + + $parent = get_parent_class($class); + + // Not an interface nor a trait + if (class_exists($name, false)) { + if (preg_match('#\n \* @final(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) { + self::$final[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : ''; + } + + if ($parent && isset(self::$final[$parent])) { + @trigger_error(sprintf('The "%s" class is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $name), E_USER_DEPRECATED); + } + + // Inherit @final annotations + self::$finalMethods[$name] = $parent && isset(self::$finalMethods[$parent]) ? self::$finalMethods[$parent] : array(); + + foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) { + if ($method->class !== $name) { + continue; + } + + if ($parent && isset(self::$finalMethods[$parent][$method->name])) { + @trigger_error(sprintf('%s It may change without further notice as of its next major version. You should not extend it from "%s".', self::$finalMethods[$parent][$method->name], $name), E_USER_DEPRECATED); + } + + $doc = $method->getDocComment(); + if (false === $doc || false === strpos($doc, '@final')) { + continue; + } + + if (preg_match('#\n\s+\* @final(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) { + $message = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : ''; + self::$finalMethods[$name][$method->name] = sprintf('The "%s::%s()" method is considered final%s.', $name, $method->name, $message); + } + } + } + + if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) { + @trigger_error(sprintf('The "%s" class uses the reserved name "%s", it will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED); + } elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) { + self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]); + } else { + // Don't trigger deprecations for classes in the same vendor + if (2 > $len = 1 + (strpos($name, '\\', 1 + strpos($name, '\\')) ?: strpos($name, '_'))) { + $len = 0; + $ns = ''; + } else { + switch ($ns = substr($name, 0, $len)) { + case 'Symfony\Bridge\\': + case 'Symfony\Bundle\\': + case 'Symfony\Component\\': + $ns = 'Symfony\\'; + $len = strlen($ns); + break; + } + } + + if (!$parent || strncmp($ns, $parent, $len)) { + if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) { + @trigger_error(sprintf('The "%s" class extends "%s" that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED); + } + + $parentInterfaces = array(); + $deprecatedInterfaces = array(); + if ($parent) { + foreach (class_implements($parent) as $interface) { + $parentInterfaces[$interface] = 1; + } + } + + foreach ($refl->getInterfaceNames() as $interface) { + if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) { + $deprecatedInterfaces[] = $interface; + } + foreach (class_implements($interface) as $interface) { + $parentInterfaces[$interface] = 1; + } + } + + foreach ($deprecatedInterfaces as $interface) { + if (!isset($parentInterfaces[$interface])) { + @trigger_error(sprintf('The "%s" %s "%s" that is deprecated %s', $name, $refl->isInterface() ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED); + } + } + } + } + } + + if ($file) { + if (!$exists) { + if (false !== strpos($class, '/')) { + throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class)); + } + + throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file)); + } + if (self::$caseCheck) { + $real = explode('\\', $class.strrchr($file, '.')); + $tail = explode(DIRECTORY_SEPARATOR, str_replace('/', DIRECTORY_SEPARATOR, $file)); + + $i = count($tail) - 1; + $j = count($real) - 1; + + while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) { + --$i; + --$j; + } + + array_splice($tail, 0, $i + 1); + } + if (self::$caseCheck && $tail) { + $tail = DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $tail); + $tailLen = strlen($tail); + $real = $refl->getFileName(); + + if (2 === self::$caseCheck) { + // realpath() on MacOSX doesn't normalize the case of characters + + $i = 1 + strrpos($real, '/'); + $file = substr($real, $i); + $real = substr($real, 0, $i); + + if (isset(self::$darwinCache[$real])) { + $kDir = $real; + } else { + $kDir = strtolower($real); + + if (isset(self::$darwinCache[$kDir])) { + $real = self::$darwinCache[$kDir][0]; + } else { + $dir = getcwd(); + chdir($real); + $real = getcwd().'/'; + chdir($dir); + + $dir = $real; + $k = $kDir; + $i = strlen($dir) - 1; + while (!isset(self::$darwinCache[$k])) { + self::$darwinCache[$k] = array($dir, array()); + self::$darwinCache[$dir] = &self::$darwinCache[$k]; + + while ('/' !== $dir[--$i]) { + } + $k = substr($k, 0, ++$i); + $dir = substr($dir, 0, $i--); + } + } + } + + $dirFiles = self::$darwinCache[$kDir][1]; + + if (isset($dirFiles[$file])) { + $kFile = $file; + } else { + $kFile = strtolower($file); + + if (!isset($dirFiles[$kFile])) { + foreach (scandir($real, 2) as $f) { + if ('.' !== $f[0]) { + $dirFiles[$f] = $f; + if ($f === $file) { + $kFile = $k = $file; + } elseif ($f !== $k = strtolower($f)) { + $dirFiles[$k] = $f; + } + } + } + self::$darwinCache[$kDir][1] = $dirFiles; + } + } + + $real .= $dirFiles[$kFile]; + } + + if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true) + && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false) + ) { + throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1))); + } + } + + return true; + } + } +} diff --git a/vendor/symfony/debug/ErrorHandler.php b/vendor/symfony/debug/ErrorHandler.php new file mode 100644 index 00000000..369ed26f --- /dev/null +++ b/vendor/symfony/debug/ErrorHandler.php @@ -0,0 +1,716 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +use Psr\Log\LogLevel; +use Psr\Log\LoggerInterface; +use Symfony\Component\Debug\Exception\ContextErrorException; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\Exception\FatalThrowableError; +use Symfony\Component\Debug\Exception\OutOfMemoryException; +use Symfony\Component\Debug\Exception\SilencedErrorContext; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler; +use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler; +use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface; + +/** + * A generic ErrorHandler for the PHP engine. + * + * Provides five bit fields that control how errors are handled: + * - thrownErrors: errors thrown as \ErrorException + * - loggedErrors: logged errors, when not @-silenced + * - scopedErrors: errors thrown or logged with their local context + * - tracedErrors: errors logged with their stack trace + * - screamedErrors: never @-silenced errors + * + * Each error level can be logged by a dedicated PSR-3 logger object. + * Screaming only applies to logging. + * Throwing takes precedence over logging. + * Uncaught exceptions are logged as E_ERROR. + * E_DEPRECATED and E_USER_DEPRECATED levels never throw. + * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw. + * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so. + * As errors have a performance cost, repeated errors are all logged, so that the developer + * can see them and weight them as more important to fix than others of the same level. + * + * @author Nicolas Grekas + * @author Grégoire Pineau + */ +class ErrorHandler +{ + private $levels = array( + E_DEPRECATED => 'Deprecated', + E_USER_DEPRECATED => 'User Deprecated', + E_NOTICE => 'Notice', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Runtime Notice', + E_WARNING => 'Warning', + E_USER_WARNING => 'User Warning', + E_COMPILE_WARNING => 'Compile Warning', + E_CORE_WARNING => 'Core Warning', + E_USER_ERROR => 'User Error', + E_RECOVERABLE_ERROR => 'Catchable Fatal Error', + E_COMPILE_ERROR => 'Compile Error', + E_PARSE => 'Parse Error', + E_ERROR => 'Error', + E_CORE_ERROR => 'Core Error', + ); + + private $loggers = array( + E_DEPRECATED => array(null, LogLevel::INFO), + E_USER_DEPRECATED => array(null, LogLevel::INFO), + E_NOTICE => array(null, LogLevel::WARNING), + E_USER_NOTICE => array(null, LogLevel::WARNING), + E_STRICT => array(null, LogLevel::WARNING), + E_WARNING => array(null, LogLevel::WARNING), + E_USER_WARNING => array(null, LogLevel::WARNING), + E_COMPILE_WARNING => array(null, LogLevel::WARNING), + E_CORE_WARNING => array(null, LogLevel::WARNING), + E_USER_ERROR => array(null, LogLevel::CRITICAL), + E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL), + E_COMPILE_ERROR => array(null, LogLevel::CRITICAL), + E_PARSE => array(null, LogLevel::CRITICAL), + E_ERROR => array(null, LogLevel::CRITICAL), + E_CORE_ERROR => array(null, LogLevel::CRITICAL), + ); + + private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED + private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED + private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE + private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE + private $loggedErrors = 0; + private $traceReflector; + + private $isRecursive = 0; + private $isRoot = false; + private $exceptionHandler; + private $bootstrappingLogger; + + private static $reservedMemory; + private static $stackedErrors = array(); + private static $stackedErrorLevels = array(); + private static $toStringException = null; + private static $silencedErrorCache = array(); + private static $silencedErrorCount = 0; + private static $exitCode = 0; + + /** + * Registers the error handler. + * + * @param self|null $handler The handler to register + * @param bool $replace Whether to replace or not any existing handler + * + * @return self The registered error handler + */ + public static function register(self $handler = null, $replace = true) + { + if (null === self::$reservedMemory) { + self::$reservedMemory = str_repeat('x', 10240); + register_shutdown_function(__CLASS__.'::handleFatalError'); + } + + if ($handlerIsNew = null === $handler) { + $handler = new static(); + } + + if (null === $prev = set_error_handler(array($handler, 'handleError'))) { + restore_error_handler(); + // Specifying the error types earlier would expose us to https://bugs.php.net/63206 + set_error_handler(array($handler, 'handleError'), $handler->thrownErrors | $handler->loggedErrors); + $handler->isRoot = true; + } + + if ($handlerIsNew && is_array($prev) && $prev[0] instanceof self) { + $handler = $prev[0]; + $replace = false; + } + if ($replace || !$prev) { + $handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException'))); + } else { + restore_error_handler(); + } + + $handler->throwAt(E_ALL & $handler->thrownErrors, true); + + return $handler; + } + + public function __construct(BufferingLogger $bootstrappingLogger = null) + { + if ($bootstrappingLogger) { + $this->bootstrappingLogger = $bootstrappingLogger; + $this->setDefaultLogger($bootstrappingLogger); + } + $this->traceReflector = new \ReflectionProperty('Exception', 'trace'); + $this->traceReflector->setAccessible(true); + } + + /** + * Sets a logger to non assigned errors levels. + * + * @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels + * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param bool $replace Whether to replace or not any existing logger + */ + public function setDefaultLogger(LoggerInterface $logger, $levels = E_ALL, $replace = false) + { + $loggers = array(); + + if (is_array($levels)) { + foreach ($levels as $type => $logLevel) { + if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) { + $loggers[$type] = array($logger, $logLevel); + } + } + } else { + if (null === $levels) { + $levels = E_ALL; + } + foreach ($this->loggers as $type => $log) { + if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) { + $log[0] = $logger; + $loggers[$type] = $log; + } + } + } + + $this->setLoggers($loggers); + } + + /** + * Sets a logger for each error level. + * + * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map + * + * @return array The previous map + * + * @throws \InvalidArgumentException + */ + public function setLoggers(array $loggers) + { + $prevLogged = $this->loggedErrors; + $prev = $this->loggers; + $flush = array(); + + foreach ($loggers as $type => $log) { + if (!isset($prev[$type])) { + throw new \InvalidArgumentException('Unknown error type: '.$type); + } + if (!is_array($log)) { + $log = array($log); + } elseif (!array_key_exists(0, $log)) { + throw new \InvalidArgumentException('No logger provided'); + } + if (null === $log[0]) { + $this->loggedErrors &= ~$type; + } elseif ($log[0] instanceof LoggerInterface) { + $this->loggedErrors |= $type; + } else { + throw new \InvalidArgumentException('Invalid logger provided'); + } + $this->loggers[$type] = $log + $prev[$type]; + + if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) { + $flush[$type] = $type; + } + } + $this->reRegister($prevLogged | $this->thrownErrors); + + if ($flush) { + foreach ($this->bootstrappingLogger->cleanLogs() as $log) { + $type = $log[2]['exception'] instanceof \ErrorException ? $log[2]['exception']->getSeverity() : E_ERROR; + if (!isset($flush[$type])) { + $this->bootstrappingLogger->log($log[0], $log[1], $log[2]); + } elseif ($this->loggers[$type][0]) { + $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]); + } + } + } + + return $prev; + } + + /** + * Sets a user exception handler. + * + * @param callable $handler A handler that will be called on Exception + * + * @return callable|null The previous exception handler + */ + public function setExceptionHandler(callable $handler = null) + { + $prev = $this->exceptionHandler; + $this->exceptionHandler = $handler; + + return $prev; + } + + /** + * Sets the PHP error levels that throw an exception when a PHP error occurs. + * + * @param int $levels A bit field of E_* constants for thrown errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function throwAt($levels, $replace = false) + { + $prev = $this->thrownErrors; + $this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED; + if (!$replace) { + $this->thrownErrors |= $prev; + } + $this->reRegister($prev | $this->loggedErrors); + + return $prev; + } + + /** + * Sets the PHP error levels for which local variables are preserved. + * + * @param int $levels A bit field of E_* constants for scoped errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function scopeAt($levels, $replace = false) + { + $prev = $this->scopedErrors; + $this->scopedErrors = (int) $levels; + if (!$replace) { + $this->scopedErrors |= $prev; + } + + return $prev; + } + + /** + * Sets the PHP error levels for which the stack trace is preserved. + * + * @param int $levels A bit field of E_* constants for traced errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function traceAt($levels, $replace = false) + { + $prev = $this->tracedErrors; + $this->tracedErrors = (int) $levels; + if (!$replace) { + $this->tracedErrors |= $prev; + } + + return $prev; + } + + /** + * Sets the error levels where the @-operator is ignored. + * + * @param int $levels A bit field of E_* constants for screamed errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function screamAt($levels, $replace = false) + { + $prev = $this->screamedErrors; + $this->screamedErrors = (int) $levels; + if (!$replace) { + $this->screamedErrors |= $prev; + } + + return $prev; + } + + /** + * Re-registers as a PHP error handler if levels changed. + */ + private function reRegister($prev) + { + if ($prev !== $this->thrownErrors | $this->loggedErrors) { + $handler = set_error_handler('var_dump'); + $handler = is_array($handler) ? $handler[0] : null; + restore_error_handler(); + if ($handler === $this) { + restore_error_handler(); + if ($this->isRoot) { + set_error_handler(array($this, 'handleError'), $this->thrownErrors | $this->loggedErrors); + } else { + set_error_handler(array($this, 'handleError')); + } + } + } + } + + /** + * Handles errors by filtering then logging them according to the configured bit fields. + * + * @param int $type One of the E_* constants + * @param string $message + * @param string $file + * @param int $line + * + * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself + * + * @throws \ErrorException When $this->thrownErrors requests so + * + * @internal + */ + public function handleError($type, $message, $file, $line) + { + // Level is the current error reporting level to manage silent error. + // Strong errors are not authorized to be silenced. + $level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED; + $log = $this->loggedErrors & $type; + $throw = $this->thrownErrors & $type & $level; + $type &= $level | $this->screamedErrors; + + if (!$type || (!$log && !$throw)) { + return $type && $log; + } + $scope = $this->scopedErrors & $type; + + if (4 < $numArgs = func_num_args()) { + $context = $scope ? (func_get_arg(4) ?: array()) : array(); + $backtrace = 5 < $numArgs ? func_get_arg(5) : null; // defined on HHVM + } else { + $context = array(); + $backtrace = null; + } + + if (isset($context['GLOBALS']) && $scope) { + $e = $context; // Whatever the signature of the method, + unset($e['GLOBALS'], $context); // $context is always a reference in 5.3 + $context = $e; + } + + if (null !== $backtrace && $type & E_ERROR) { + // E_ERROR fatal errors are triggered on HHVM when + // hhvm.error_handling.call_user_handler_on_fatals=1 + // which is the way to get their backtrace. + $this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace')); + + return true; + } + + $logMessage = $this->levels[$type].': '.$message; + + if (null !== self::$toStringException) { + $errorAsException = self::$toStringException; + self::$toStringException = null; + } elseif (!$throw && !($type & $level)) { + if (isset(self::$silencedErrorCache[$message])) { + $lightTrace = null; + $errorAsException = self::$silencedErrorCache[$message]; + ++$errorAsException->count; + } else { + $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), $type, $file, $line, false) : array(); + $errorAsException = new SilencedErrorContext($type, $file, $line, $lightTrace); + } + + if (100 < ++self::$silencedErrorCount) { + self::$silencedErrorCache = $lightTrace = array(); + self::$silencedErrorCount = 1; + } + self::$silencedErrorCache[$message] = $errorAsException; + + if (null === $lightTrace) { + return; + } + } else { + if ($scope) { + $errorAsException = new ContextErrorException($logMessage, 0, $type, $file, $line, $context); + } else { + $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line); + } + + // Clean the trace by removing function arguments and the first frames added by the error handler itself. + if ($throw || $this->tracedErrors & $type) { + $backtrace = $backtrace ?: $errorAsException->getTrace(); + $lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw); + $this->traceReflector->setValue($errorAsException, $lightTrace); + } else { + $this->traceReflector->setValue($errorAsException, array()); + } + } + + if ($throw) { + if (E_USER_ERROR & $type) { + for ($i = 1; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function']) + && '__toString' === $backtrace[$i]['function'] + && '->' === $backtrace[$i]['type'] + && !isset($backtrace[$i - 1]['class']) + && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function']) + ) { + // Here, we know trigger_error() has been called from __toString(). + // HHVM is fine with throwing from __toString() but PHP triggers a fatal error instead. + // A small convention allows working around the limitation: + // given a caught $e exception in __toString(), quitting the method with + // `return trigger_error($e, E_USER_ERROR);` allows this error handler + // to make $e get through the __toString() barrier. + + foreach ($context as $e) { + if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) { + if (1 === $i) { + // On HHVM + $errorAsException = $e; + break; + } + self::$toStringException = $e; + + return true; + } + } + + if (1 < $i) { + // On PHP (not on HHVM), display the original error message instead of the default one. + $this->handleException($errorAsException); + + // Stop the process by giving back the error to the native handler. + return false; + } + } + } + } + + throw $errorAsException; + } + + if ($this->isRecursive) { + $log = 0; + } elseif (self::$stackedErrorLevels) { + self::$stackedErrors[] = array( + $this->loggers[$type][0], + ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, + $logMessage, + array('exception' => $errorAsException), + ); + } else { + try { + $this->isRecursive = true; + $level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG; + $this->loggers[$type][0]->log($level, $logMessage, array('exception' => $errorAsException)); + } finally { + $this->isRecursive = false; + } + } + + return $type && $log; + } + + /** + * Handles an exception by logging then forwarding it to another handler. + * + * @param \Exception|\Throwable $exception An exception to handle + * @param array $error An array as returned by error_get_last() + * + * @internal + */ + public function handleException($exception, array $error = null) + { + if (null === $error) { + self::$exitCode = 255; + } + if (!$exception instanceof \Exception) { + $exception = new FatalThrowableError($exception); + } + $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR; + + if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) { + if ($exception instanceof FatalErrorException) { + if ($exception instanceof FatalThrowableError) { + $error = array( + 'type' => $type, + 'message' => $message = $exception->getMessage(), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + ); + } else { + $message = 'Fatal '.$exception->getMessage(); + } + } elseif ($exception instanceof \ErrorException) { + $message = 'Uncaught '.$exception->getMessage(); + } else { + $message = 'Uncaught Exception: '.$exception->getMessage(); + } + } + if ($this->loggedErrors & $type) { + try { + $this->loggers[$type][0]->log($this->loggers[$type][1], $message, array('exception' => $exception)); + } catch (\Exception $handlerException) { + } catch (\Throwable $handlerException) { + } + } + if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) { + foreach ($this->getFatalErrorHandlers() as $handler) { + if ($e = $handler->handleError($error, $exception)) { + $exception = $e; + break; + } + } + } + if (empty($this->exceptionHandler)) { + throw $exception; // Give back $exception to the native handler + } + try { + call_user_func($this->exceptionHandler, $exception); + } catch (\Exception $handlerException) { + } catch (\Throwable $handlerException) { + } + if (isset($handlerException)) { + $this->exceptionHandler = null; + $this->handleException($handlerException); + } + } + + /** + * Shutdown registered function for handling PHP fatal errors. + * + * @param array $error An array as returned by error_get_last() + * + * @internal + */ + public static function handleFatalError(array $error = null) + { + if (null === self::$reservedMemory) { + return; + } + + self::$reservedMemory = null; + + $handler = set_error_handler('var_dump'); + $handler = is_array($handler) ? $handler[0] : null; + restore_error_handler(); + + if (!$handler instanceof self) { + return; + } + + if ($exit = null === $error) { + $error = error_get_last(); + } + + try { + while (self::$stackedErrorLevels) { + static::unstackErrors(); + } + } catch (\Exception $exception) { + // Handled below + } catch (\Throwable $exception) { + // Handled below + } + + if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) { + // Let's not throw anymore but keep logging + $handler->throwAt(0, true); + $trace = isset($error['backtrace']) ? $error['backtrace'] : null; + + if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) { + $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace); + } else { + $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace); + } + } + + try { + if (isset($exception)) { + self::$exitCode = 255; + $handler->handleException($exception, $error); + } + } catch (FatalErrorException $e) { + // Ignore this re-throw + } + + if ($exit && self::$exitCode) { + $exitCode = self::$exitCode; + register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); }); + } + } + + /** + * Configures the error handler for delayed handling. + * Ensures also that non-catchable fatal errors are never silenced. + * + * As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724 + * PHP has a compile stage where it behaves unusually. To workaround it, + * we plug an error handler that only stacks errors for later. + * + * The most important feature of this is to prevent + * autoloading until unstackErrors() is called. + */ + public static function stackErrors() + { + self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR); + } + + /** + * Unstacks stacked errors and forwards to the logger. + */ + public static function unstackErrors() + { + $level = array_pop(self::$stackedErrorLevels); + + if (null !== $level) { + $errorReportingLevel = error_reporting($level); + if ($errorReportingLevel !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) { + // If the user changed the error level, do not overwrite it + error_reporting($errorReportingLevel); + } + } + + if (empty(self::$stackedErrorLevels)) { + $errors = self::$stackedErrors; + self::$stackedErrors = array(); + + foreach ($errors as $error) { + $error[0]->log($error[1], $error[2], $error[3]); + } + } + } + + /** + * Gets the fatal error handlers. + * + * Override this method if you want to define more fatal error handlers. + * + * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface + */ + protected function getFatalErrorHandlers() + { + return array( + new UndefinedFunctionFatalErrorHandler(), + new UndefinedMethodFatalErrorHandler(), + new ClassNotFoundFatalErrorHandler(), + ); + } + + private function cleanTrace($backtrace, $type, $file, $line, $throw) + { + $lightTrace = $backtrace; + + for ($i = 0; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { + $lightTrace = array_slice($lightTrace, 1 + $i); + break; + } + } + if (!($throw || $this->scopedErrors & $type)) { + for ($i = 0; isset($lightTrace[$i]); ++$i) { + unset($lightTrace[$i]['args']); + } + } + + return $lightTrace; + } +} diff --git a/vendor/symfony/debug/Exception/ClassNotFoundException.php b/vendor/symfony/debug/Exception/ClassNotFoundException.php new file mode 100644 index 00000000..b91bf466 --- /dev/null +++ b/vendor/symfony/debug/Exception/ClassNotFoundException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Class (or Trait or Interface) Not Found Exception. + * + * @author Konstanton Myakshin + */ +class ClassNotFoundException extends FatalErrorException +{ + public function __construct($message, \ErrorException $previous) + { + parent::__construct( + $message, + $previous->getCode(), + $previous->getSeverity(), + $previous->getFile(), + $previous->getLine(), + $previous->getPrevious() + ); + $this->setTrace($previous->getTrace()); + } +} diff --git a/vendor/symfony/debug/Exception/ContextErrorException.php b/vendor/symfony/debug/Exception/ContextErrorException.php new file mode 100644 index 00000000..6561d4df --- /dev/null +++ b/vendor/symfony/debug/Exception/ContextErrorException.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Error Exception with Variable Context. + * + * @author Christian Sciberras + * + * @deprecated since version 3.3. Instead, \ErrorException will be used directly in 4.0. + */ +class ContextErrorException extends \ErrorException +{ + private $context = array(); + + public function __construct($message, $code, $severity, $filename, $lineno, $context = array()) + { + parent::__construct($message, $code, $severity, $filename, $lineno); + $this->context = $context; + } + + /** + * @return array Array of variables that existed when the exception occurred + */ + public function getContext() + { + @trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED); + + return $this->context; + } +} diff --git a/vendor/symfony/debug/Exception/FatalErrorException.php b/vendor/symfony/debug/Exception/FatalErrorException.php new file mode 100644 index 00000000..f24a54e7 --- /dev/null +++ b/vendor/symfony/debug/Exception/FatalErrorException.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Fatal Error Exception. + * + * @author Konstanton Myakshin + */ +class FatalErrorException extends \ErrorException +{ + public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true, array $trace = null) + { + parent::__construct($message, $code, $severity, $filename, $lineno); + + if (null !== $trace) { + if (!$traceArgs) { + foreach ($trace as &$frame) { + unset($frame['args'], $frame['this'], $frame); + } + } + + $this->setTrace($trace); + } elseif (null !== $traceOffset) { + if (function_exists('xdebug_get_function_stack')) { + $trace = xdebug_get_function_stack(); + if (0 < $traceOffset) { + array_splice($trace, -$traceOffset); + } + + foreach ($trace as &$frame) { + if (!isset($frame['type'])) { + // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695 + if (isset($frame['class'])) { + $frame['type'] = '::'; + } + } elseif ('dynamic' === $frame['type']) { + $frame['type'] = '->'; + } elseif ('static' === $frame['type']) { + $frame['type'] = '::'; + } + + // XDebug also has a different name for the parameters array + if (!$traceArgs) { + unset($frame['params'], $frame['args']); + } elseif (isset($frame['params']) && !isset($frame['args'])) { + $frame['args'] = $frame['params']; + unset($frame['params']); + } + } + + unset($frame); + $trace = array_reverse($trace); + } elseif (function_exists('symfony_debug_backtrace')) { + $trace = symfony_debug_backtrace(); + if (0 < $traceOffset) { + array_splice($trace, 0, $traceOffset); + } + } else { + $trace = array(); + } + + $this->setTrace($trace); + } + } + + protected function setTrace($trace) + { + $traceReflector = new \ReflectionProperty('Exception', 'trace'); + $traceReflector->setAccessible(true); + $traceReflector->setValue($this, $trace); + } +} diff --git a/vendor/symfony/debug/Exception/FatalThrowableError.php b/vendor/symfony/debug/Exception/FatalThrowableError.php new file mode 100644 index 00000000..34f43b17 --- /dev/null +++ b/vendor/symfony/debug/Exception/FatalThrowableError.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Fatal Throwable Error. + * + * @author Nicolas Grekas + */ +class FatalThrowableError extends FatalErrorException +{ + public function __construct(\Throwable $e) + { + if ($e instanceof \ParseError) { + $message = 'Parse error: '.$e->getMessage(); + $severity = E_PARSE; + } elseif ($e instanceof \TypeError) { + $message = 'Type error: '.$e->getMessage(); + $severity = E_RECOVERABLE_ERROR; + } else { + $message = $e->getMessage(); + $severity = E_ERROR; + } + + \ErrorException::__construct( + $message, + $e->getCode(), + $severity, + $e->getFile(), + $e->getLine() + ); + + $this->setTrace($e->getTrace()); + } +} diff --git a/vendor/symfony/debug/Exception/FlattenException.php b/vendor/symfony/debug/Exception/FlattenException.php new file mode 100644 index 00000000..24679dca --- /dev/null +++ b/vendor/symfony/debug/Exception/FlattenException.php @@ -0,0 +1,263 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; + +/** + * FlattenException wraps a PHP Exception to be able to serialize it. + * + * Basically, this class removes all objects from the trace. + * + * @author Fabien Potencier + */ +class FlattenException +{ + private $message; + private $code; + private $previous; + private $trace; + private $class; + private $statusCode; + private $headers; + private $file; + private $line; + + public static function create(\Exception $exception, $statusCode = null, array $headers = array()) + { + $e = new static(); + $e->setMessage($exception->getMessage()); + $e->setCode($exception->getCode()); + + if ($exception instanceof HttpExceptionInterface) { + $statusCode = $exception->getStatusCode(); + $headers = array_merge($headers, $exception->getHeaders()); + } elseif ($exception instanceof RequestExceptionInterface) { + $statusCode = 400; + } + + if (null === $statusCode) { + $statusCode = 500; + } + + $e->setStatusCode($statusCode); + $e->setHeaders($headers); + $e->setTraceFromException($exception); + $e->setClass(get_class($exception)); + $e->setFile($exception->getFile()); + $e->setLine($exception->getLine()); + + $previous = $exception->getPrevious(); + + if ($previous instanceof \Exception) { + $e->setPrevious(static::create($previous)); + } elseif ($previous instanceof \Throwable) { + $e->setPrevious(static::create(new FatalThrowableError($previous))); + } + + return $e; + } + + public function toArray() + { + $exceptions = array(); + foreach (array_merge(array($this), $this->getAllPrevious()) as $exception) { + $exceptions[] = array( + 'message' => $exception->getMessage(), + 'class' => $exception->getClass(), + 'trace' => $exception->getTrace(), + ); + } + + return $exceptions; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function setStatusCode($code) + { + $this->statusCode = $code; + } + + public function getHeaders() + { + return $this->headers; + } + + public function setHeaders(array $headers) + { + $this->headers = $headers; + } + + public function getClass() + { + return $this->class; + } + + public function setClass($class) + { + $this->class = $class; + } + + public function getFile() + { + return $this->file; + } + + public function setFile($file) + { + $this->file = $file; + } + + public function getLine() + { + return $this->line; + } + + public function setLine($line) + { + $this->line = $line; + } + + public function getMessage() + { + return $this->message; + } + + public function setMessage($message) + { + $this->message = $message; + } + + public function getCode() + { + return $this->code; + } + + public function setCode($code) + { + $this->code = $code; + } + + public function getPrevious() + { + return $this->previous; + } + + public function setPrevious(FlattenException $previous) + { + $this->previous = $previous; + } + + public function getAllPrevious() + { + $exceptions = array(); + $e = $this; + while ($e = $e->getPrevious()) { + $exceptions[] = $e; + } + + return $exceptions; + } + + public function getTrace() + { + return $this->trace; + } + + public function setTraceFromException(\Exception $exception) + { + $this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine()); + } + + public function setTrace($trace, $file, $line) + { + $this->trace = array(); + $this->trace[] = array( + 'namespace' => '', + 'short_class' => '', + 'class' => '', + 'type' => '', + 'function' => '', + 'file' => $file, + 'line' => $line, + 'args' => array(), + ); + foreach ($trace as $entry) { + $class = ''; + $namespace = ''; + if (isset($entry['class'])) { + $parts = explode('\\', $entry['class']); + $class = array_pop($parts); + $namespace = implode('\\', $parts); + } + + $this->trace[] = array( + 'namespace' => $namespace, + 'short_class' => $class, + 'class' => isset($entry['class']) ? $entry['class'] : '', + 'type' => isset($entry['type']) ? $entry['type'] : '', + 'function' => isset($entry['function']) ? $entry['function'] : null, + 'file' => isset($entry['file']) ? $entry['file'] : null, + 'line' => isset($entry['line']) ? $entry['line'] : null, + 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(), + ); + } + } + + private function flattenArgs($args, $level = 0, &$count = 0) + { + $result = array(); + foreach ($args as $key => $value) { + if (++$count > 1e4) { + return array('array', '*SKIPPED over 10000 entries*'); + } + if ($value instanceof \__PHP_Incomplete_Class) { + // is_object() returns false on PHP<=7.1 + $result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value)); + } elseif (is_object($value)) { + $result[$key] = array('object', get_class($value)); + } elseif (is_array($value)) { + if ($level > 10) { + $result[$key] = array('array', '*DEEP NESTED ARRAY*'); + } else { + $result[$key] = array('array', $this->flattenArgs($value, $level + 1, $count)); + } + } elseif (null === $value) { + $result[$key] = array('null', null); + } elseif (is_bool($value)) { + $result[$key] = array('boolean', $value); + } elseif (is_int($value)) { + $result[$key] = array('integer', $value); + } elseif (is_float($value)) { + $result[$key] = array('float', $value); + } elseif (is_resource($value)) { + $result[$key] = array('resource', get_resource_type($value)); + } else { + $result[$key] = array('string', (string) $value); + } + } + + return $result; + } + + private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) + { + $array = new \ArrayObject($value); + + return $array['__PHP_Incomplete_Class_Name']; + } +} diff --git a/vendor/symfony/debug/Exception/OutOfMemoryException.php b/vendor/symfony/debug/Exception/OutOfMemoryException.php new file mode 100644 index 00000000..fec19798 --- /dev/null +++ b/vendor/symfony/debug/Exception/OutOfMemoryException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Out of memory exception. + * + * @author Nicolas Grekas + */ +class OutOfMemoryException extends FatalErrorException +{ +} diff --git a/vendor/symfony/debug/Exception/SilencedErrorContext.php b/vendor/symfony/debug/Exception/SilencedErrorContext.php new file mode 100644 index 00000000..4be83491 --- /dev/null +++ b/vendor/symfony/debug/Exception/SilencedErrorContext.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Data Object that represents a Silenced Error. + * + * @author Grégoire Pineau + */ +class SilencedErrorContext implements \JsonSerializable +{ + public $count = 1; + + private $severity; + private $file; + private $line; + private $trace; + + public function __construct($severity, $file, $line, array $trace = array(), $count = 1) + { + $this->severity = $severity; + $this->file = $file; + $this->line = $line; + $this->trace = $trace; + $this->count = $count; + } + + public function getSeverity() + { + return $this->severity; + } + + public function getFile() + { + return $this->file; + } + + public function getLine() + { + return $this->line; + } + + public function getTrace() + { + return $this->trace; + } + + public function JsonSerialize() + { + return array( + 'severity' => $this->severity, + 'file' => $this->file, + 'line' => $this->line, + 'trace' => $this->trace, + 'count' => $this->count, + ); + } +} diff --git a/vendor/symfony/debug/Exception/UndefinedFunctionException.php b/vendor/symfony/debug/Exception/UndefinedFunctionException.php new file mode 100644 index 00000000..a66ae2a3 --- /dev/null +++ b/vendor/symfony/debug/Exception/UndefinedFunctionException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Undefined Function Exception. + * + * @author Konstanton Myakshin + */ +class UndefinedFunctionException extends FatalErrorException +{ + public function __construct($message, \ErrorException $previous) + { + parent::__construct( + $message, + $previous->getCode(), + $previous->getSeverity(), + $previous->getFile(), + $previous->getLine(), + $previous->getPrevious() + ); + $this->setTrace($previous->getTrace()); + } +} diff --git a/vendor/symfony/debug/Exception/UndefinedMethodException.php b/vendor/symfony/debug/Exception/UndefinedMethodException.php new file mode 100644 index 00000000..350dc318 --- /dev/null +++ b/vendor/symfony/debug/Exception/UndefinedMethodException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Undefined Method Exception. + * + * @author Grégoire Pineau + */ +class UndefinedMethodException extends FatalErrorException +{ + public function __construct($message, \ErrorException $previous) + { + parent::__construct( + $message, + $previous->getCode(), + $previous->getSeverity(), + $previous->getFile(), + $previous->getLine(), + $previous->getPrevious() + ); + $this->setTrace($previous->getTrace()); + } +} diff --git a/vendor/symfony/debug/ExceptionHandler.php b/vendor/symfony/debug/ExceptionHandler.php new file mode 100644 index 00000000..a82a3246 --- /dev/null +++ b/vendor/symfony/debug/ExceptionHandler.php @@ -0,0 +1,414 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\Debug\Exception\OutOfMemoryException; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; + +/** + * ExceptionHandler converts an exception to a Response object. + * + * It is mostly useful in debug mode to replace the default PHP/XDebug + * output with something prettier and more useful. + * + * As this class is mainly used during Kernel boot, where nothing is yet + * available, the Response content is always HTML. + * + * @author Fabien Potencier + * @author Nicolas Grekas + */ +class ExceptionHandler +{ + private $debug; + private $charset; + private $handler; + private $caughtBuffer; + private $caughtLength; + private $fileLinkFormat; + + public function __construct($debug = true, $charset = null, $fileLinkFormat = null) + { + $this->debug = $debug; + $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8'; + $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + } + + /** + * Registers the exception handler. + * + * @param bool $debug Enable/disable debug mode, where the stack trace is displayed + * @param string|null $charset The charset used by exception messages + * @param string|null $fileLinkFormat The IDE link template + * + * @return static + */ + public static function register($debug = true, $charset = null, $fileLinkFormat = null) + { + $handler = new static($debug, $charset, $fileLinkFormat); + + $prev = set_exception_handler(array($handler, 'handle')); + if (is_array($prev) && $prev[0] instanceof ErrorHandler) { + restore_exception_handler(); + $prev[0]->setExceptionHandler(array($handler, 'handle')); + } + + return $handler; + } + + /** + * Sets a user exception handler. + * + * @param callable $handler An handler that will be called on Exception + * + * @return callable|null The previous exception handler if any + */ + public function setHandler(callable $handler = null) + { + $old = $this->handler; + $this->handler = $handler; + + return $old; + } + + /** + * Sets the format for links to source files. + * + * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files + * + * @return string The previous file link format + */ + public function setFileLinkFormat($fileLinkFormat) + { + $old = $this->fileLinkFormat; + $this->fileLinkFormat = $fileLinkFormat; + + return $old; + } + + /** + * Sends a response for the given Exception. + * + * To be as fail-safe as possible, the exception is first handled + * by our simple exception handler, then by the user exception handler. + * The latter takes precedence and any output from the former is cancelled, + * if and only if nothing bad happens in this handling path. + */ + public function handle(\Exception $exception) + { + if (null === $this->handler || $exception instanceof OutOfMemoryException) { + $this->sendPhpResponse($exception); + + return; + } + + $caughtLength = $this->caughtLength = 0; + + ob_start(function ($buffer) { + $this->caughtBuffer = $buffer; + + return ''; + }); + + $this->sendPhpResponse($exception); + while (null === $this->caughtBuffer && ob_end_flush()) { + // Empty loop, everything is in the condition + } + if (isset($this->caughtBuffer[0])) { + ob_start(function ($buffer) { + if ($this->caughtLength) { + // use substr_replace() instead of substr() for mbstring overloading resistance + $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength); + if (isset($cleanBuffer[0])) { + $buffer = $cleanBuffer; + } + } + + return $buffer; + }); + + echo $this->caughtBuffer; + $caughtLength = ob_get_length(); + } + $this->caughtBuffer = null; + + try { + call_user_func($this->handler, $exception); + $this->caughtLength = $caughtLength; + } catch (\Exception $e) { + if (!$caughtLength) { + // All handlers failed. Let PHP handle that now. + throw $exception; + } + } + } + + /** + * Sends the error associated with the given Exception as a plain PHP response. + * + * This method uses plain PHP functions like header() and echo to output + * the response. + * + * @param \Exception|FlattenException $exception An \Exception or FlattenException instance + */ + public function sendPhpResponse($exception) + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + if (!headers_sent()) { + header(sprintf('HTTP/1.0 %s', $exception->getStatusCode())); + foreach ($exception->getHeaders() as $name => $value) { + header($name.': '.$value, false); + } + header('Content-Type: text/html; charset='.$this->charset); + } + + echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); + } + + /** + * Gets the full HTML content associated with the given exception. + * + * @param \Exception|FlattenException $exception An \Exception or FlattenException instance + * + * @return string The HTML content as a string + */ + public function getHtml($exception) + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + return $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); + } + + /** + * Gets the HTML content associated with the given exception. + * + * @param FlattenException $exception A FlattenException instance + * + * @return string The content as a string + */ + public function getContent(FlattenException $exception) + { + switch ($exception->getStatusCode()) { + case 404: + $title = 'Sorry, the page you are looking for could not be found.'; + break; + default: + $title = 'Whoops, looks like something went wrong.'; + } + + $content = ''; + if ($this->debug) { + try { + $count = count($exception->getAllPrevious()); + $total = $count + 1; + foreach ($exception->toArray() as $position => $e) { + $ind = $count - $position + 1; + $class = $this->formatClass($e['class']); + $message = nl2br($this->escapeHtml($e['message'])); + $content .= sprintf(<<<'EOF' +
+ + + +EOF + , $ind, $total, $class, $message); + foreach ($e['trace'] as $trace) { + $content .= '\n"; + } + + $content .= "\n
+

+ (%d/%d) + %s +

+

%s

+
'; + if ($trace['function']) { + $content .= sprintf('at %s%s%s(%s)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args'])); + } + if (isset($trace['file']) && isset($trace['line'])) { + $content .= $this->formatPath($trace['file'], $trace['line']); + } + $content .= "
\n
\n"; + } + } catch (\Exception $e) { + // something nasty happened and we cannot throw an exception anymore + if ($this->debug) { + $title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($e), $this->escapeHtml($e->getMessage())); + } else { + $title = 'Whoops, looks like something went wrong.'; + } + } + } + + $symfonyGhostImageContents = $this->getSymfonyGhostAsSvg(); + + return << +
+
+

$title

+
$symfonyGhostImageContents
+
+
+ + +
+ $content +
+EOF; + } + + /** + * Gets the stylesheet associated with the given exception. + * + * @param FlattenException $exception A FlattenException instance + * + * @return string The stylesheet as a string + */ + public function getStylesheet(FlattenException $exception) + { + return <<<'EOF' + body { background-color: #F9F9F9; color: #222; font: 14px/1.4 Helvetica, Arial, sans-serif; margin: 0; padding-bottom: 45px; } + + a { cursor: pointer; text-decoration: none; } + a:hover { text-decoration: underline; } + abbr[title] { border-bottom: none; cursor: help; text-decoration: none; } + + code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; } + + table, tr, th, td { background: #FFF; border-collapse: collapse; vertical-align: top; } + table { background: #FFF; border: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; } + table th, table td { border: solid #E0E0E0; border-width: 1px 0; padding: 8px 10px; } + table th { background-color: #E0E0E0; font-weight: bold; text-align: left; } + + .hidden-xs-down { display: none; } + .block { display: block; } + .break-long-words { -ms-word-break: break-all; word-break: break-all; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; } + .text-muted { color: #999; } + + .container { max-width: 1024px; margin: 0 auto; padding: 0 15px; } + .container::after { content: ""; display: table; clear: both; } + + .exception-summary { background: #B0413E; border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 30px; } + + .exception-message-wrapper { display: flex; align-items: center; min-height: 70px; } + .exception-message { flex-grow: 1; padding: 30px 0; } + .exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; } + .exception-message.long { font-size: 18px; } + .exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; } + .exception-message a:hover { border-bottom-color: #ffffff; } + + .exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; } + + .trace + .trace { margin-top: 30px; } + .trace-head .trace-class { color: #222; font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; } + + .trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; } + + .trace-file-path, .trace-file-path a { color: #222; margin-top: 3px; font-size: 13px; } + .trace-class { color: #B0413E; } + .trace-type { padding: 0 2px; } + .trace-method { color: #B0413E; font-weight: bold; } + .trace-arguments { color: #777; font-weight: normal; padding-left: 2px; } + + @media (min-width: 575px) { + .hidden-xs-down { display: initial; } + } +EOF; + } + + private function decorate($content, $css) + { + return << + + + + + + + + $content + + +EOF; + } + + private function formatClass($class) + { + $parts = explode('\\', $class); + + return sprintf('%s', $class, array_pop($parts)); + } + + private function formatPath($path, $line) + { + $file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path); + $fmt = $this->fileLinkFormat; + + if ($fmt && $link = is_string($fmt) ? strtr($fmt, array('%f' => $path, '%l' => $line)) : $fmt->format($path, $line)) { + return sprintf('in %s (line %d)', $this->escapeHtml($link), $file, $line); + } + + return sprintf('in %s (line %d)', $this->escapeHtml($path), $file, $line); + } + + /** + * Formats an array as a string. + * + * @param array $args The argument array + * + * @return string + */ + private function formatArgs(array $args) + { + $result = array(); + foreach ($args as $key => $item) { + if ('object' === $item[0]) { + $formattedValue = sprintf('object(%s)', $this->formatClass($item[1])); + } elseif ('array' === $item[0]) { + $formattedValue = sprintf('array(%s)', is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + } elseif ('null' === $item[0]) { + $formattedValue = 'null'; + } elseif ('boolean' === $item[0]) { + $formattedValue = ''.strtolower(var_export($item[1], true)).''; + } elseif ('resource' === $item[0]) { + $formattedValue = 'resource'; + } else { + $formattedValue = str_replace("\n", '', $this->escapeHtml(var_export($item[1], true))); + } + + $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeHtml($key), $formattedValue); + } + + return implode(', ', $result); + } + + /** + * HTML-encodes a string. + */ + private function escapeHtml($str) + { + return htmlspecialchars($str, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset); + } + + private function getSymfonyGhostAsSvg() + { + return ''; + } +} diff --git a/vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php b/vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php new file mode 100644 index 00000000..32ba9a09 --- /dev/null +++ b/vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\FatalErrorHandler; + +use Symfony\Component\Debug\Exception\ClassNotFoundException; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\DebugClassLoader; +use Composer\Autoload\ClassLoader as ComposerClassLoader; +use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader; + +/** + * ErrorHandler for classes that do not exist. + * + * @author Fabien Potencier + */ +class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handleError(array $error, FatalErrorException $exception) + { + $messageLen = strlen($error['message']); + $notFoundSuffix = '\' not found'; + $notFoundSuffixLen = strlen($notFoundSuffix); + if ($notFoundSuffixLen > $messageLen) { + return; + } + + if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) { + return; + } + + foreach (array('class', 'interface', 'trait') as $typeName) { + $prefix = ucfirst($typeName).' \''; + $prefixLen = strlen($prefix); + if (0 !== strpos($error['message'], $prefix)) { + continue; + } + + $fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen); + if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) { + $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1); + $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex); + $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix); + $tail = ' for another namespace?'; + } else { + $className = $fullyQualifiedClassName; + $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className); + $tail = '?'; + } + + if ($candidates = $this->getClassCandidates($className)) { + $tail = array_pop($candidates).'"?'; + if ($candidates) { + $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail; + } else { + $tail = ' for "'.$tail; + } + } + $message .= "\nDid you forget a \"use\" statement".$tail; + + return new ClassNotFoundException($message, $exception); + } + } + + /** + * Tries to guess the full namespace for a given class name. + * + * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer + * autoloader (that should cover all common cases). + * + * @param string $class A class name (without its namespace) + * + * @return array An array of possible fully qualified class names + */ + private function getClassCandidates($class) + { + if (!is_array($functions = spl_autoload_functions())) { + return array(); + } + + // find Symfony and Composer autoloaders + $classes = array(); + + foreach ($functions as $function) { + if (!is_array($function)) { + continue; + } + // get class loaders wrapped by DebugClassLoader + if ($function[0] instanceof DebugClassLoader) { + $function = $function[0]->getClassLoader(); + + if (!is_array($function)) { + continue; + } + } + + if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) { + foreach ($function[0]->getPrefixes() as $prefix => $paths) { + foreach ($paths as $path) { + $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); + } + } + } + if ($function[0] instanceof ComposerClassLoader) { + foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) { + foreach ($paths as $path) { + $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); + } + } + } + } + + return array_unique($classes); + } + + /** + * @param string $path + * @param string $class + * @param string $prefix + * + * @return array + */ + private function findClassInPath($path, $class, $prefix) + { + if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) { + return array(); + } + + $classes = array(); + $filename = $class.'.php'; + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) { + $classes[] = $class; + } + } + + return $classes; + } + + /** + * @param string $path + * @param string $file + * @param string $prefix + * + * @return string|null + */ + private function convertFileToClass($path, $file, $prefix) + { + $candidates = array( + // namespaced class + $namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file), + // namespaced class (with target dir) + $prefix.$namespacedClass, + // namespaced class (with target dir and separator) + $prefix.'\\'.$namespacedClass, + // PEAR class + str_replace('\\', '_', $namespacedClass), + // PEAR class (with target dir) + str_replace('\\', '_', $prefix.$namespacedClass), + // PEAR class (with target dir and separator) + str_replace('\\', '_', $prefix.'\\'.$namespacedClass), + ); + + if ($prefix) { + $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); }); + } + + // We cannot use the autoloader here as most of them use require; but if the class + // is not found, the new autoloader call will require the file again leading to a + // "cannot redeclare class" error. + foreach ($candidates as $candidate) { + if ($this->classExists($candidate)) { + return $candidate; + } + } + + require_once $file; + + foreach ($candidates as $candidate) { + if ($this->classExists($candidate)) { + return $candidate; + } + } + } + + /** + * @param string $class + * + * @return bool + */ + private function classExists($class) + { + return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); + } +} diff --git a/vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php b/vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php new file mode 100644 index 00000000..6b87eb30 --- /dev/null +++ b/vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\FatalErrorHandler; + +use Symfony\Component\Debug\Exception\FatalErrorException; + +/** + * Attempts to convert fatal errors to exceptions. + * + * @author Fabien Potencier + */ +interface FatalErrorHandlerInterface +{ + /** + * Attempts to convert an error into an exception. + * + * @param array $error An array as returned by error_get_last() + * @param FatalErrorException $exception A FatalErrorException instance + * + * @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise + */ + public function handleError(array $error, FatalErrorException $exception); +} diff --git a/vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php b/vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php new file mode 100644 index 00000000..c6f391a7 --- /dev/null +++ b/vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\FatalErrorHandler; + +use Symfony\Component\Debug\Exception\UndefinedFunctionException; +use Symfony\Component\Debug\Exception\FatalErrorException; + +/** + * ErrorHandler for undefined functions. + * + * @author Fabien Potencier + */ +class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handleError(array $error, FatalErrorException $exception) + { + $messageLen = strlen($error['message']); + $notFoundSuffix = '()'; + $notFoundSuffixLen = strlen($notFoundSuffix); + if ($notFoundSuffixLen > $messageLen) { + return; + } + + if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) { + return; + } + + $prefix = 'Call to undefined function '; + $prefixLen = strlen($prefix); + if (0 !== strpos($error['message'], $prefix)) { + return; + } + + $fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen); + if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) { + $functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1); + $namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex); + $message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix); + } else { + $functionName = $fullyQualifiedFunctionName; + $message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName); + } + + $candidates = array(); + foreach (get_defined_functions() as $type => $definedFunctionNames) { + foreach ($definedFunctionNames as $definedFunctionName) { + if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) { + $definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1); + } else { + $definedFunctionNameBasename = $definedFunctionName; + } + + if ($definedFunctionNameBasename === $functionName) { + $candidates[] = '\\'.$definedFunctionName; + } + } + } + + if ($candidates) { + sort($candidates); + $last = array_pop($candidates).'"?'; + if ($candidates) { + $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; + } else { + $candidates = '"'.$last; + } + $message .= "\nDid you mean to call ".$candidates; + } + + return new UndefinedFunctionException($message, $exception); + } +} diff --git a/vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php b/vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php new file mode 100644 index 00000000..6fa62b6f --- /dev/null +++ b/vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\FatalErrorHandler; + +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\Exception\UndefinedMethodException; + +/** + * ErrorHandler for undefined methods. + * + * @author Grégoire Pineau + */ +class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handleError(array $error, FatalErrorException $exception) + { + preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches); + if (!$matches) { + return; + } + + $className = $matches[1]; + $methodName = $matches[2]; + + $message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className); + + if (!class_exists($className) || null === $methods = get_class_methods($className)) { + // failed to get the class or its methods on which an unknown method was called (for example on an anonymous class) + return new UndefinedMethodException($message, $exception); + } + + $candidates = array(); + foreach ($methods as $definedMethodName) { + $lev = levenshtein($methodName, $definedMethodName); + if ($lev <= strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) { + $candidates[] = $definedMethodName; + } + } + + if ($candidates) { + sort($candidates); + $last = array_pop($candidates).'"?'; + if ($candidates) { + $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; + } else { + $candidates = '"'.$last; + } + + $message .= "\nDid you mean to call ".$candidates; + } + + return new UndefinedMethodException($message, $exception); + } +} diff --git a/vendor/symfony/debug/LICENSE b/vendor/symfony/debug/LICENSE new file mode 100644 index 00000000..17d16a13 --- /dev/null +++ b/vendor/symfony/debug/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2017 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/debug/README.md b/vendor/symfony/debug/README.md new file mode 100644 index 00000000..a1d16175 --- /dev/null +++ b/vendor/symfony/debug/README.md @@ -0,0 +1,13 @@ +Debug Component +=============== + +The Debug component provides tools to ease debugging PHP code. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/debug/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/debug/Resources/ext/README.md b/vendor/symfony/debug/Resources/ext/README.md new file mode 100644 index 00000000..25dccf07 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/README.md @@ -0,0 +1,134 @@ +Symfony Debug Extension for PHP 5 +================================= + +This extension publishes several functions to help building powerful debugging tools. +It is compatible with PHP 5.3, 5.4, 5.5 and 5.6; with ZTS and non-ZTS modes. +It is not required thus not provided for PHP 7. + +symfony_zval_info() +------------------- + +- exposes zval_hash/refcounts, allowing e.g. efficient exploration of arbitrary structures in PHP, +- does work with references, preventing memory copying. + +Its behavior is about the same as: + +```php + gettype($array[$key]), + 'zval_hash' => /* hashed memory address of $array[$key] */, + 'zval_refcount' => /* internal zval refcount of $array[$key] */, + 'zval_isref' => /* is_ref status of $array[$key] */, + ); + + switch ($info['type']) { + case 'object': + $info += array( + 'object_class' => get_class($array[$key]), + 'object_refcount' => /* internal object refcount of $array[$key] */, + 'object_hash' => spl_object_hash($array[$key]), + 'object_handle' => /* internal object handle $array[$key] */, + ); + break; + + case 'resource': + $info += array( + 'resource_handle' => (int) $array[$key], + 'resource_type' => get_resource_type($array[$key]), + 'resource_refcount' => /* internal resource refcount of $array[$key] */, + ); + break; + + case 'array': + $info += array( + 'array_count' => count($array[$key]), + ); + break; + + case 'string': + $info += array( + 'strlen' => strlen($array[$key]), + ); + break; + } + + return $info; +} +``` + +symfony_debug_backtrace() +------------------------- + +This function works like debug_backtrace(), except that it can fetch the full backtrace in case of fatal errors: + +```php +function foo() { fatal(); } +function bar() { foo(); } + +function sd() { var_dump(symfony_debug_backtrace()); } + +register_shutdown_function('sd'); + +bar(); + +/* Will output +Fatal error: Call to undefined function fatal() in foo.php on line 42 +array(3) { + [0]=> + array(2) { + ["function"]=> + string(2) "sd" + ["args"]=> + array(0) { + } + } + [1]=> + array(4) { + ["file"]=> + string(7) "foo.php" + ["line"]=> + int(1) + ["function"]=> + string(3) "foo" + ["args"]=> + array(0) { + } + } + [2]=> + array(4) { + ["file"]=> + string(102) "foo.php" + ["line"]=> + int(2) + ["function"]=> + string(3) "bar" + ["args"]=> + array(0) { + } + } +} +*/ +``` + +Usage +----- + +To enable the extension from source, run: + +``` + phpize + ./configure + make + sudo make install +``` diff --git a/vendor/symfony/debug/Resources/ext/config.m4 b/vendor/symfony/debug/Resources/ext/config.m4 new file mode 100644 index 00000000..3c560471 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/config.m4 @@ -0,0 +1,63 @@ +dnl $Id$ +dnl config.m4 for extension symfony_debug + +dnl Comments in this file start with the string 'dnl'. +dnl Remove where necessary. This file will not work +dnl without editing. + +dnl If your extension references something external, use with: + +dnl PHP_ARG_WITH(symfony_debug, for symfony_debug support, +dnl Make sure that the comment is aligned: +dnl [ --with-symfony_debug Include symfony_debug support]) + +dnl Otherwise use enable: + +PHP_ARG_ENABLE(symfony_debug, whether to enable symfony_debug support, +dnl Make sure that the comment is aligned: +[ --enable-symfony_debug Enable symfony_debug support]) + +if test "$PHP_SYMFONY_DEBUG" != "no"; then + dnl Write more examples of tests here... + + dnl # --with-symfony_debug -> check with-path + dnl SEARCH_PATH="/usr/local /usr" # you might want to change this + dnl SEARCH_FOR="/include/symfony_debug.h" # you most likely want to change this + dnl if test -r $PHP_SYMFONY_DEBUG/$SEARCH_FOR; then # path given as parameter + dnl SYMFONY_DEBUG_DIR=$PHP_SYMFONY_DEBUG + dnl else # search default path list + dnl AC_MSG_CHECKING([for symfony_debug files in default path]) + dnl for i in $SEARCH_PATH ; do + dnl if test -r $i/$SEARCH_FOR; then + dnl SYMFONY_DEBUG_DIR=$i + dnl AC_MSG_RESULT(found in $i) + dnl fi + dnl done + dnl fi + dnl + dnl if test -z "$SYMFONY_DEBUG_DIR"; then + dnl AC_MSG_RESULT([not found]) + dnl AC_MSG_ERROR([Please reinstall the symfony_debug distribution]) + dnl fi + + dnl # --with-symfony_debug -> add include path + dnl PHP_ADD_INCLUDE($SYMFONY_DEBUG_DIR/include) + + dnl # --with-symfony_debug -> check for lib and symbol presence + dnl LIBNAME=symfony_debug # you may want to change this + dnl LIBSYMBOL=symfony_debug # you most likely want to change this + + dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, + dnl [ + dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $SYMFONY_DEBUG_DIR/lib, SYMFONY_DEBUG_SHARED_LIBADD) + dnl AC_DEFINE(HAVE_SYMFONY_DEBUGLIB,1,[ ]) + dnl ],[ + dnl AC_MSG_ERROR([wrong symfony_debug lib version or lib not found]) + dnl ],[ + dnl -L$SYMFONY_DEBUG_DIR/lib -lm + dnl ]) + dnl + dnl PHP_SUBST(SYMFONY_DEBUG_SHARED_LIBADD) + + PHP_NEW_EXTENSION(symfony_debug, symfony_debug.c, $ext_shared) +fi diff --git a/vendor/symfony/debug/Resources/ext/config.w32 b/vendor/symfony/debug/Resources/ext/config.w32 new file mode 100644 index 00000000..487e6913 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/config.w32 @@ -0,0 +1,13 @@ +// $Id$ +// vim:ft=javascript + +// If your extension references something external, use ARG_WITH +// ARG_WITH("symfony_debug", "for symfony_debug support", "no"); + +// Otherwise, use ARG_ENABLE +// ARG_ENABLE("symfony_debug", "enable symfony_debug support", "no"); + +if (PHP_SYMFONY_DEBUG != "no") { + EXTENSION("symfony_debug", "symfony_debug.c"); +} + diff --git a/vendor/symfony/debug/Resources/ext/php_symfony_debug.h b/vendor/symfony/debug/Resources/ext/php_symfony_debug.h new file mode 100644 index 00000000..26d0e8c0 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/php_symfony_debug.h @@ -0,0 +1,60 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#ifndef PHP_SYMFONY_DEBUG_H +#define PHP_SYMFONY_DEBUG_H + +extern zend_module_entry symfony_debug_module_entry; +#define phpext_symfony_debug_ptr &symfony_debug_module_entry + +#define PHP_SYMFONY_DEBUG_VERSION "2.7" + +#ifdef PHP_WIN32 +# define PHP_SYMFONY_DEBUG_API __declspec(dllexport) +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_SYMFONY_DEBUG_API __attribute__ ((visibility("default"))) +#else +# define PHP_SYMFONY_DEBUG_API +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +ZEND_BEGIN_MODULE_GLOBALS(symfony_debug) + intptr_t req_rand_init; + void (*old_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); + zval *debug_bt; +ZEND_END_MODULE_GLOBALS(symfony_debug) + +PHP_MINIT_FUNCTION(symfony_debug); +PHP_MSHUTDOWN_FUNCTION(symfony_debug); +PHP_RINIT_FUNCTION(symfony_debug); +PHP_RSHUTDOWN_FUNCTION(symfony_debug); +PHP_MINFO_FUNCTION(symfony_debug); +PHP_GINIT_FUNCTION(symfony_debug); +PHP_GSHUTDOWN_FUNCTION(symfony_debug); + +PHP_FUNCTION(symfony_zval_info); +PHP_FUNCTION(symfony_debug_backtrace); + +static char *_symfony_debug_memory_address_hash(void * TSRMLS_DC); +static const char *_symfony_debug_zval_type(zval *); +static const char* _symfony_debug_get_resource_type(long TSRMLS_DC); +static int _symfony_debug_get_resource_refcount(long TSRMLS_DC); + +void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); + +#ifdef ZTS +#define SYMFONY_DEBUG_G(v) TSRMG(symfony_debug_globals_id, zend_symfony_debug_globals *, v) +#else +#define SYMFONY_DEBUG_G(v) (symfony_debug_globals.v) +#endif + +#endif /* PHP_SYMFONY_DEBUG_H */ diff --git a/vendor/symfony/debug/Resources/ext/symfony_debug.c b/vendor/symfony/debug/Resources/ext/symfony_debug.c new file mode 100644 index 00000000..0d7cb602 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/symfony_debug.c @@ -0,0 +1,283 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#ifdef ZTS +#include "TSRM.h" +#endif +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_symfony_debug.h" +#include "ext/standard/php_rand.h" +#include "ext/standard/php_lcg.h" +#include "ext/spl/php_spl.h" +#include "Zend/zend_gc.h" +#include "Zend/zend_builtin_functions.h" +#include "Zend/zend_extensions.h" /* for ZEND_EXTENSION_API_NO */ +#include "ext/standard/php_array.h" +#include "Zend/zend_interfaces.h" +#include "SAPI.h" + +#define IS_PHP_53 ZEND_EXTENSION_API_NO == 220090626 + +ZEND_DECLARE_MODULE_GLOBALS(symfony_debug) + +ZEND_BEGIN_ARG_INFO_EX(symfony_zval_arginfo, 0, 0, 2) + ZEND_ARG_INFO(0, key) + ZEND_ARG_ARRAY_INFO(0, array, 0) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +const zend_function_entry symfony_debug_functions[] = { + PHP_FE(symfony_zval_info, symfony_zval_arginfo) + PHP_FE(symfony_debug_backtrace, NULL) + PHP_FE_END +}; + +PHP_FUNCTION(symfony_debug_backtrace) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } +#if IS_PHP_53 + zend_fetch_debug_backtrace(return_value, 1, 0 TSRMLS_CC); +#else + zend_fetch_debug_backtrace(return_value, 1, 0, 0 TSRMLS_CC); +#endif + + if (!SYMFONY_DEBUG_G(debug_bt)) { + return; + } + + php_array_merge(Z_ARRVAL_P(return_value), Z_ARRVAL_P(SYMFONY_DEBUG_G(debug_bt)), 0 TSRMLS_CC); +} + +PHP_FUNCTION(symfony_zval_info) +{ + zval *key = NULL, *arg = NULL; + zval **data = NULL; + HashTable *array = NULL; + long options = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zh|l", &key, &array, &options) == FAILURE) { + return; + } + + switch (Z_TYPE_P(key)) { + case IS_STRING: + if (zend_symtable_find(array, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, (void **)&data) == FAILURE) { + return; + } + break; + case IS_LONG: + if (zend_hash_index_find(array, Z_LVAL_P(key), (void **)&data)) { + return; + } + break; + } + + arg = *data; + + array_init(return_value); + + add_assoc_string(return_value, "type", (char *)_symfony_debug_zval_type(arg), 1); + add_assoc_stringl(return_value, "zval_hash", _symfony_debug_memory_address_hash((void *)arg TSRMLS_CC), 16, 0); + add_assoc_long(return_value, "zval_refcount", Z_REFCOUNT_P(arg)); + add_assoc_bool(return_value, "zval_isref", (zend_bool)Z_ISREF_P(arg)); + + if (Z_TYPE_P(arg) == IS_OBJECT) { + char hash[33] = {0}; + + php_spl_object_hash(arg, (char *)hash TSRMLS_CC); + add_assoc_stringl(return_value, "object_class", (char *)Z_OBJCE_P(arg)->name, Z_OBJCE_P(arg)->name_length, 1); + add_assoc_long(return_value, "object_refcount", EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(arg)].bucket.obj.refcount); + add_assoc_string(return_value, "object_hash", hash, 1); + add_assoc_long(return_value, "object_handle", Z_OBJ_HANDLE_P(arg)); + } else if (Z_TYPE_P(arg) == IS_ARRAY) { + add_assoc_long(return_value, "array_count", zend_hash_num_elements(Z_ARRVAL_P(arg))); + } else if(Z_TYPE_P(arg) == IS_RESOURCE) { + add_assoc_long(return_value, "resource_handle", Z_LVAL_P(arg)); + add_assoc_string(return_value, "resource_type", (char *)_symfony_debug_get_resource_type(Z_LVAL_P(arg) TSRMLS_CC), 1); + add_assoc_long(return_value, "resource_refcount", _symfony_debug_get_resource_refcount(Z_LVAL_P(arg) TSRMLS_CC)); + } else if (Z_TYPE_P(arg) == IS_STRING) { + add_assoc_long(return_value, "strlen", Z_STRLEN_P(arg)); + } +} + +void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args) +{ + TSRMLS_FETCH(); + zval *retval; + + switch (type) { + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_CORE_WARNING: + case E_COMPILE_ERROR: + case E_COMPILE_WARNING: + ALLOC_INIT_ZVAL(retval); +#if IS_PHP_53 + zend_fetch_debug_backtrace(retval, 1, 0 TSRMLS_CC); +#else + zend_fetch_debug_backtrace(retval, 1, 0, 0 TSRMLS_CC); +#endif + SYMFONY_DEBUG_G(debug_bt) = retval; + } + + SYMFONY_DEBUG_G(old_error_cb)(type, error_filename, error_lineno, format, args); +} + +static const char* _symfony_debug_get_resource_type(long rsid TSRMLS_DC) +{ + const char *res_type; + res_type = zend_rsrc_list_get_rsrc_type(rsid TSRMLS_CC); + + if (!res_type) { + return "Unknown"; + } + + return res_type; +} + +static int _symfony_debug_get_resource_refcount(long rsid TSRMLS_DC) +{ + zend_rsrc_list_entry *le; + + if (zend_hash_index_find(&EG(regular_list), rsid, (void **) &le)==SUCCESS) { + return le->refcount; + } + + return 0; +} + +static char *_symfony_debug_memory_address_hash(void *address TSRMLS_DC) +{ + char *result = NULL; + intptr_t address_rand; + + if (!SYMFONY_DEBUG_G(req_rand_init)) { + if (!BG(mt_rand_is_seeded)) { + php_mt_srand(GENERATE_SEED() TSRMLS_CC); + } + SYMFONY_DEBUG_G(req_rand_init) = (intptr_t)php_mt_rand(TSRMLS_C); + } + + address_rand = (intptr_t)address ^ SYMFONY_DEBUG_G(req_rand_init); + + spprintf(&result, 17, "%016zx", address_rand); + + return result; +} + +static const char *_symfony_debug_zval_type(zval *zv) +{ + switch (Z_TYPE_P(zv)) { + case IS_NULL: + return "NULL"; + break; + + case IS_BOOL: + return "boolean"; + break; + + case IS_LONG: + return "integer"; + break; + + case IS_DOUBLE: + return "double"; + break; + + case IS_STRING: + return "string"; + break; + + case IS_ARRAY: + return "array"; + break; + + case IS_OBJECT: + return "object"; + + case IS_RESOURCE: + return "resource"; + + default: + return "unknown type"; + } +} + +zend_module_entry symfony_debug_module_entry = { + STANDARD_MODULE_HEADER, + "symfony_debug", + symfony_debug_functions, + PHP_MINIT(symfony_debug), + PHP_MSHUTDOWN(symfony_debug), + PHP_RINIT(symfony_debug), + PHP_RSHUTDOWN(symfony_debug), + PHP_MINFO(symfony_debug), + PHP_SYMFONY_DEBUG_VERSION, + PHP_MODULE_GLOBALS(symfony_debug), + PHP_GINIT(symfony_debug), + PHP_GSHUTDOWN(symfony_debug), + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; + +#ifdef COMPILE_DL_SYMFONY_DEBUG +ZEND_GET_MODULE(symfony_debug) +#endif + +PHP_GINIT_FUNCTION(symfony_debug) +{ + memset(symfony_debug_globals, 0 , sizeof(*symfony_debug_globals)); +} + +PHP_GSHUTDOWN_FUNCTION(symfony_debug) +{ + +} + +PHP_MINIT_FUNCTION(symfony_debug) +{ + SYMFONY_DEBUG_G(old_error_cb) = zend_error_cb; + zend_error_cb = symfony_debug_error_cb; + + return SUCCESS; +} + +PHP_MSHUTDOWN_FUNCTION(symfony_debug) +{ + zend_error_cb = SYMFONY_DEBUG_G(old_error_cb); + + return SUCCESS; +} + +PHP_RINIT_FUNCTION(symfony_debug) +{ + return SUCCESS; +} + +PHP_RSHUTDOWN_FUNCTION(symfony_debug) +{ + return SUCCESS; +} + +PHP_MINFO_FUNCTION(symfony_debug) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "Symfony Debug support", "enabled"); + php_info_print_table_header(2, "Symfony Debug version", PHP_SYMFONY_DEBUG_VERSION); + php_info_print_table_end(); +} diff --git a/vendor/symfony/debug/Resources/ext/tests/001.phpt b/vendor/symfony/debug/Resources/ext/tests/001.phpt new file mode 100644 index 00000000..15e183a7 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/tests/001.phpt @@ -0,0 +1,153 @@ +--TEST-- +Test symfony_zval_info API +--SKIPIF-- + +--FILE-- + $int, + 'float' => $float, + 'str' => $str, + 'object' => $object, + 'array' => $array, + 'resource' => $resource, + 'null' => $null, + 'bool' => $bool, + 'refcount' => &$refcount2, +); + +var_dump(symfony_zval_info('int', $var)); +var_dump(symfony_zval_info('float', $var)); +var_dump(symfony_zval_info('str', $var)); +var_dump(symfony_zval_info('object', $var)); +var_dump(symfony_zval_info('array', $var)); +var_dump(symfony_zval_info('resource', $var)); +var_dump(symfony_zval_info('null', $var)); +var_dump(symfony_zval_info('bool', $var)); + +var_dump(symfony_zval_info('refcount', $var)); +var_dump(symfony_zval_info('not-exist', $var)); +?> +--EXPECTF-- +array(4) { + ["type"]=> + string(7) "integer" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(4) { + ["type"]=> + string(6) "double" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(5) { + ["type"]=> + string(6) "string" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["strlen"]=> + int(6) +} +array(8) { + ["type"]=> + string(6) "object" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["object_class"]=> + string(8) "stdClass" + ["object_refcount"]=> + int(1) + ["object_hash"]=> + string(32) "%s" + ["object_handle"]=> + int(%d) +} +array(5) { + ["type"]=> + string(5) "array" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["array_count"]=> + int(2) +} +array(7) { + ["type"]=> + string(8) "resource" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["resource_handle"]=> + int(%d) + ["resource_type"]=> + string(6) "stream" + ["resource_refcount"]=> + int(1) +} +array(4) { + ["type"]=> + string(4) "NULL" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(4) { + ["type"]=> + string(7) "boolean" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(4) { + ["type"]=> + string(7) "integer" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(3) + ["zval_isref"]=> + bool(true) +} +NULL diff --git a/vendor/symfony/debug/Resources/ext/tests/002.phpt b/vendor/symfony/debug/Resources/ext/tests/002.phpt new file mode 100644 index 00000000..2bc6d712 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/tests/002.phpt @@ -0,0 +1,63 @@ +--TEST-- +Test symfony_debug_backtrace in case of fatal error +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Call to undefined function notexist() in %s on line %d +Array +( + [0] => Array + ( + [function] => bt + [args] => Array + ( + ) + + ) + + [1] => Array + ( + [file] => %s + [line] => %d + [function] => foo + [args] => Array + ( + ) + + ) + + [2] => Array + ( + [file] => %s + [line] => %d + [function] => bar + [args] => Array + ( + ) + + ) + +) diff --git a/vendor/symfony/debug/Resources/ext/tests/002_1.phpt b/vendor/symfony/debug/Resources/ext/tests/002_1.phpt new file mode 100644 index 00000000..4e9e34f1 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/tests/002_1.phpt @@ -0,0 +1,46 @@ +--TEST-- +Test symfony_debug_backtrace in case of non fatal error +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Array +( + [0] => Array + ( + [file] => %s + [line] => %d + [function] => bt + [args] => Array + ( + ) + + ) + + [1] => Array + ( + [file] => %s + [line] => %d + [function] => bar + [args] => Array + ( + ) + + ) + +) diff --git a/vendor/symfony/debug/Resources/ext/tests/003.phpt b/vendor/symfony/debug/Resources/ext/tests/003.phpt new file mode 100644 index 00000000..2a494e27 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/tests/003.phpt @@ -0,0 +1,85 @@ +--TEST-- +Test ErrorHandler in case of fatal error +--SKIPIF-- + +--FILE-- +setExceptionHandler('print_r'); + +if (function_exists('xdebug_disable')) { + xdebug_disable(); +} + +bar(); +?> +--EXPECTF-- +Fatal error: Call to undefined function Symfony\Component\Debug\notexist() in %s on line %d +Symfony\Component\Debug\Exception\UndefinedFunctionException Object +( + [message:protected] => Attempted to call function "notexist" from namespace "Symfony\Component\Debug". + [string:Exception:private] => + [code:protected] => 0 + [file:protected] => %s + [line:protected] => %d + [trace:Exception:private] => Array + ( + [0] => Array + ( +%A [function] => Symfony\Component\Debug\foo +%A [args] => Array + ( + ) + + ) + + [1] => Array + ( +%A [function] => Symfony\Component\Debug\bar +%A [args] => Array + ( + ) + + ) +%A + ) + + [previous:Exception:private] => + [severity:protected] => 1 +) diff --git a/vendor/symfony/debug/Tests/DebugClassLoaderTest.php b/vendor/symfony/debug/Tests/DebugClassLoaderTest.php new file mode 100644 index 00000000..af88d17f --- /dev/null +++ b/vendor/symfony/debug/Tests/DebugClassLoaderTest.php @@ -0,0 +1,387 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\DebugClassLoader; +use Symfony\Component\Debug\ErrorHandler; + +class DebugClassLoaderTest extends TestCase +{ + /** + * @var int Error reporting level before running tests + */ + private $errorReporting; + + private $loader; + + protected function setUp() + { + $this->errorReporting = error_reporting(E_ALL); + $this->loader = new ClassLoader(); + spl_autoload_register(array($this->loader, 'loadClass'), true, true); + DebugClassLoader::enable(); + } + + protected function tearDown() + { + DebugClassLoader::disable(); + spl_autoload_unregister(array($this->loader, 'loadClass')); + error_reporting($this->errorReporting); + } + + public function testIdempotence() + { + DebugClassLoader::enable(); + + $functions = spl_autoload_functions(); + foreach ($functions as $function) { + if (is_array($function) && $function[0] instanceof DebugClassLoader) { + $reflClass = new \ReflectionClass($function[0]); + $reflProp = $reflClass->getProperty('classLoader'); + $reflProp->setAccessible(true); + + $this->assertNotInstanceOf('Symfony\Component\Debug\DebugClassLoader', $reflProp->getValue($function[0])); + + return; + } + } + + $this->fail('DebugClassLoader did not register'); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage boo + */ + public function testThrowingClass() + { + try { + class_exists(__NAMESPACE__.'\Fixtures\Throwing'); + $this->fail('Exception expected'); + } catch (\Exception $e) { + $this->assertSame('boo', $e->getMessage()); + } + + // the second call also should throw + class_exists(__NAMESPACE__.'\Fixtures\Throwing'); + } + + public function testUnsilencing() + { + if (\PHP_VERSION_ID >= 70000) { + $this->markTestSkipped('PHP7 throws exceptions, unsilencing is not required anymore.'); + } + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('HHVM is not handled in this test case.'); + } + + ob_start(); + + $this->iniSet('log_errors', 0); + $this->iniSet('display_errors', 1); + + // See below: this will fail with parse error + // but this should not be @-silenced. + @class_exists(__NAMESPACE__.'\TestingUnsilencing', true); + + $output = ob_get_clean(); + + $this->assertStringMatchesFormat('%aParse error%a', $output); + } + + public function testStacking() + { + // the ContextErrorException must not be loaded to test the workaround + // for https://bugs.php.net/65322. + if (class_exists('Symfony\Component\Debug\Exception\ContextErrorException', false)) { + $this->markTestSkipped('The ContextErrorException class is already loaded.'); + } + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('HHVM is not handled in this test case.'); + } + + ErrorHandler::register(); + + try { + // Trigger autoloading + E_STRICT at compile time + // which in turn triggers $errorHandler->handle() + // that again triggers autoloading for ContextErrorException. + // Error stacking works around the bug above and everything is fine. + + eval(' + namespace '.__NAMESPACE__.'; + class ChildTestingStacking extends TestingStacking { function foo($bar) {} } + '); + $this->fail('ContextErrorException expected'); + } catch (\ErrorException $exception) { + // if an exception is thrown, the test passed + $this->assertStringStartsWith(__FILE__, $exception->getFile()); + if (\PHP_VERSION_ID < 70000) { + $this->assertRegExp('/^Runtime Notice: Declaration/', $exception->getMessage()); + $this->assertEquals(E_STRICT, $exception->getSeverity()); + } else { + $this->assertRegExp('/^Warning: Declaration/', $exception->getMessage()); + $this->assertEquals(E_WARNING, $exception->getSeverity()); + } + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Case mismatch between loaded and declared class names + */ + public function testNameCaseMismatch() + { + class_exists(__NAMESPACE__.'\TestingCaseMismatch', true); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Case mismatch between class and real file names + */ + public function testFileCaseMismatch() + { + if (!file_exists(__DIR__.'/Fixtures/CaseMismatch.php')) { + $this->markTestSkipped('Can only be run on case insensitive filesystems'); + } + + class_exists(__NAMESPACE__.'\Fixtures\CaseMismatch', true); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Case mismatch between loaded and declared class names + */ + public function testPsr4CaseMismatch() + { + class_exists(__NAMESPACE__.'\Fixtures\Psr4CaseMismatch', true); + } + + public function testNotPsr0() + { + $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0', true)); + } + + public function testNotPsr0Bis() + { + $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0bis', true)); + } + + public function testClassAlias() + { + $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\ClassAlias', true)); + } + + /** + * @dataProvider provideDeprecatedSuper + */ + public function testDeprecatedSuper($class, $super, $type) + { + set_error_handler(function () { return false; }); + $e = error_reporting(0); + trigger_error('', E_USER_DEPRECATED); + + class_exists('Test\\'.__NAMESPACE__.'\\'.$class, true); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $xError = array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The "Test\Symfony\Component\Debug\Tests\\'.$class.'" class '.$type.' "Symfony\Component\Debug\Tests\Fixtures\\'.$super.'" that is deprecated but this is a test deprecation notice.', + ); + + $this->assertSame($xError, $lastError); + } + + public function provideDeprecatedSuper() + { + return array( + array('DeprecatedInterfaceClass', 'DeprecatedInterface', 'implements'), + array('DeprecatedParentClass', 'DeprecatedClass', 'extends'), + ); + } + + public function testInterfaceExtendsDeprecatedInterface() + { + set_error_handler(function () { return false; }); + $e = error_reporting(0); + trigger_error('', E_USER_NOTICE); + + class_exists('Test\\'.__NAMESPACE__.'\\NonDeprecatedInterfaceClass', true); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $xError = array( + 'type' => E_USER_NOTICE, + 'message' => '', + ); + + $this->assertSame($xError, $lastError); + } + + public function testDeprecatedSuperInSameNamespace() + { + set_error_handler(function () { return false; }); + $e = error_reporting(0); + trigger_error('', E_USER_NOTICE); + + class_exists('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent', true); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $xError = array( + 'type' => E_USER_NOTICE, + 'message' => '', + ); + + $this->assertSame($xError, $lastError); + } + + public function testReservedForPhp7() + { + if (\PHP_VERSION_ID >= 70000) { + $this->markTestSkipped('PHP7 already prevents using reserved names.'); + } + + set_error_handler(function () { return false; }); + $e = error_reporting(0); + trigger_error('', E_USER_NOTICE); + + class_exists('Test\\'.__NAMESPACE__.'\\Float', true); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $xError = array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The "Test\Symfony\Component\Debug\Tests\Float" class uses the reserved name "Float", it will break on PHP 7 and higher', + ); + + $this->assertSame($xError, $lastError); + } + + public function testExtendedFinalClass() + { + set_error_handler(function () { return false; }); + $e = error_reporting(0); + trigger_error('', E_USER_NOTICE); + + class_exists('Test\\'.__NAMESPACE__.'\\ExtendsFinalClass', true); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $xError = array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass" class is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass".', + ); + + $this->assertSame($xError, $lastError); + } + + public function testExtendedFinalMethod() + { + set_error_handler(function () { return false; }); + $e = error_reporting(0); + trigger_error('', E_USER_NOTICE); + + class_exists(__NAMESPACE__.'\\Fixtures\\ExtendedFinalMethod', true); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $xError = array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".', + ); + + $this->assertSame($xError, $lastError); + } +} + +class ClassLoader +{ + public function loadClass($class) + { + } + + public function getClassMap() + { + return array(__NAMESPACE__.'\Fixtures\NotPSR0bis' => __DIR__.'/Fixtures/notPsr0Bis.php'); + } + + public function findFile($class) + { + $fixtureDir = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR; + + if (__NAMESPACE__.'\TestingUnsilencing' === $class) { + eval('-- parse error --'); + } elseif (__NAMESPACE__.'\TestingStacking' === $class) { + eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }'); + } elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) { + eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}'); + } elseif (__NAMESPACE__.'\Fixtures\CaseMismatch' === $class) { + return $fixtureDir.'CaseMismatch.php'; + } elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) { + return $fixtureDir.'psr4'.DIRECTORY_SEPARATOR.'Psr4CaseMismatch.php'; + } elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) { + return $fixtureDir.'reallyNotPsr0.php'; + } elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) { + return $fixtureDir.'notPsr0Bis.php'; + } elseif (__NAMESPACE__.'\Fixtures\DeprecatedInterface' === $class) { + return $fixtureDir.'DeprecatedInterface.php'; + } elseif (__NAMESPACE__.'\Fixtures\FinalClass' === $class) { + return $fixtureDir.'FinalClass.php'; + } elseif (__NAMESPACE__.'\Fixtures\FinalMethod' === $class) { + return $fixtureDir.'FinalMethod.php'; + } elseif (__NAMESPACE__.'\Fixtures\ExtendedFinalMethod' === $class) { + return $fixtureDir.'ExtendedFinalMethod.php'; + } elseif ('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent' === $class) { + eval('namespace Symfony\Bridge\Debug\Tests\Fixtures; class ExtendsDeprecatedParent extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}'); + } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedParentClass' === $class) { + eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedParentClass extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}'); + } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedInterfaceClass' === $class) { + eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\DeprecatedInterface {}'); + } elseif ('Test\\'.__NAMESPACE__.'\NonDeprecatedInterfaceClass' === $class) { + eval('namespace Test\\'.__NAMESPACE__.'; class NonDeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\NonDeprecatedInterface {}'); + } elseif ('Test\\'.__NAMESPACE__.'\Float' === $class) { + eval('namespace Test\\'.__NAMESPACE__.'; class Float {}'); + } elseif ('Test\\'.__NAMESPACE__.'\ExtendsFinalClass' === $class) { + eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsFinalClass extends \\'.__NAMESPACE__.'\Fixtures\FinalClass {}'); + } + } +} diff --git a/vendor/symfony/debug/Tests/ErrorHandlerTest.php b/vendor/symfony/debug/Tests/ErrorHandlerTest.php new file mode 100644 index 00000000..a14accf3 --- /dev/null +++ b/vendor/symfony/debug/Tests/ErrorHandlerTest.php @@ -0,0 +1,535 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LogLevel; +use Symfony\Component\Debug\BufferingLogger; +use Symfony\Component\Debug\ErrorHandler; +use Symfony\Component\Debug\Exception\SilencedErrorContext; + +/** + * ErrorHandlerTest. + * + * @author Robert Schönthal + * @author Nicolas Grekas + */ +class ErrorHandlerTest extends TestCase +{ + public function testRegister() + { + $handler = ErrorHandler::register(); + + try { + $this->assertInstanceOf('Symfony\Component\Debug\ErrorHandler', $handler); + $this->assertSame($handler, ErrorHandler::register()); + + $newHandler = new ErrorHandler(); + + $this->assertSame($newHandler, ErrorHandler::register($newHandler, false)); + $h = set_error_handler('var_dump'); + restore_error_handler(); + $this->assertSame(array($handler, 'handleError'), $h); + + try { + $this->assertSame($newHandler, ErrorHandler::register($newHandler, true)); + $h = set_error_handler('var_dump'); + restore_error_handler(); + $this->assertSame(array($newHandler, 'handleError'), $h); + } catch (\Exception $e) { + } + + restore_error_handler(); + restore_exception_handler(); + + if (isset($e)) { + throw $e; + } + } catch (\Exception $e) { + } + + restore_error_handler(); + restore_exception_handler(); + + if (isset($e)) { + throw $e; + } + } + + public function testNotice() + { + ErrorHandler::register(); + + try { + self::triggerNotice($this); + $this->fail('ErrorException expected'); + } catch (\ErrorException $exception) { + // if an exception is thrown, the test passed + $this->assertEquals(E_NOTICE, $exception->getSeverity()); + $this->assertEquals(__FILE__, $exception->getFile()); + $this->assertRegExp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage()); + + $trace = $exception->getTrace(); + + $this->assertEquals(__FILE__, $trace[0]['file']); + $this->assertEquals(__CLASS__, $trace[0]['class']); + $this->assertEquals('triggerNotice', $trace[0]['function']); + $this->assertEquals('::', $trace[0]['type']); + + $this->assertEquals(__FILE__, $trace[0]['file']); + $this->assertEquals(__CLASS__, $trace[1]['class']); + $this->assertEquals(__FUNCTION__, $trace[1]['function']); + $this->assertEquals('->', $trace[1]['type']); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + // dummy function to test trace in error handler. + private static function triggerNotice($that) + { + // dummy variable to check for in error handler. + $foobar = 123; + $that->assertSame('', $foo.$foo.$bar); + } + + public function testConstruct() + { + try { + $handler = ErrorHandler::register(); + $handler->throwAt(3, true); + $this->assertEquals(3 | E_RECOVERABLE_ERROR | E_USER_ERROR, $handler->throwAt(0)); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testDefaultLogger() + { + try { + $handler = ErrorHandler::register(); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $handler->setDefaultLogger($logger, E_NOTICE); + $handler->setDefaultLogger($logger, array(E_USER_NOTICE => LogLevel::CRITICAL)); + + $loggers = array( + E_DEPRECATED => array(null, LogLevel::INFO), + E_USER_DEPRECATED => array(null, LogLevel::INFO), + E_NOTICE => array($logger, LogLevel::WARNING), + E_USER_NOTICE => array($logger, LogLevel::CRITICAL), + E_STRICT => array(null, LogLevel::WARNING), + E_WARNING => array(null, LogLevel::WARNING), + E_USER_WARNING => array(null, LogLevel::WARNING), + E_COMPILE_WARNING => array(null, LogLevel::WARNING), + E_CORE_WARNING => array(null, LogLevel::WARNING), + E_USER_ERROR => array(null, LogLevel::CRITICAL), + E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL), + E_COMPILE_ERROR => array(null, LogLevel::CRITICAL), + E_PARSE => array(null, LogLevel::CRITICAL), + E_ERROR => array(null, LogLevel::CRITICAL), + E_CORE_ERROR => array(null, LogLevel::CRITICAL), + ); + $this->assertSame($loggers, $handler->setLoggers(array())); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testHandleError() + { + try { + $handler = ErrorHandler::register(); + $handler->throwAt(0, true); + $this->assertFalse($handler->handleError(0, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $handler = ErrorHandler::register(); + $handler->throwAt(3, true); + $this->assertFalse($handler->handleError(4, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $handler = ErrorHandler::register(); + $handler->throwAt(3, true); + try { + $handler->handleError(4, 'foo', 'foo.php', 12, array()); + } catch (\ErrorException $e) { + $this->assertSame('Parse Error: foo', $e->getMessage()); + $this->assertSame(4, $e->getSeverity()); + $this->assertSame('foo.php', $e->getFile()); + $this->assertSame(12, $e->getLine()); + } + + restore_error_handler(); + restore_exception_handler(); + + $handler = ErrorHandler::register(); + $handler->throwAt(E_USER_DEPRECATED, true); + $this->assertFalse($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $handler = ErrorHandler::register(); + $handler->throwAt(E_DEPRECATED, true); + $this->assertFalse($handler->handleError(E_DEPRECATED, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $warnArgCheck = function ($logLevel, $message, $context) { + $this->assertEquals('info', $logLevel); + $this->assertEquals('User Deprecated: foo', $message); + $this->assertArrayHasKey('exception', $context); + $exception = $context['exception']; + $this->assertInstanceOf(\ErrorException::class, $exception); + $this->assertSame('User Deprecated: foo', $exception->getMessage()); + $this->assertSame(E_USER_DEPRECATED, $exception->getSeverity()); + }; + + $logger + ->expects($this->once()) + ->method('log') + ->will($this->returnCallback($warnArgCheck)) + ; + + $handler = ErrorHandler::register(); + $handler->setDefaultLogger($logger, E_USER_DEPRECATED); + $this->assertTrue($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $line = null; + $logArgCheck = function ($level, $message, $context) use (&$line) { + $this->assertEquals('Notice: Undefined variable: undefVar', $message); + $this->assertArrayHasKey('exception', $context); + $exception = $context['exception']; + $this->assertInstanceOf(SilencedErrorContext::class, $exception); + $this->assertSame(E_NOTICE, $exception->getSeverity()); + $this->assertSame(__FILE__, $exception->getFile()); + $this->assertSame($line, $exception->getLine()); + $this->assertNotEmpty($exception->getTrace()); + $this->assertSame(1, $exception->count); + }; + + $logger + ->expects($this->once()) + ->method('log') + ->will($this->returnCallback($logArgCheck)) + ; + + $handler = ErrorHandler::register(); + $handler->setDefaultLogger($logger, E_NOTICE); + $handler->screamAt(E_NOTICE); + unset($undefVar); + $line = __LINE__ + 1; + @$undefVar++; + + restore_error_handler(); + restore_exception_handler(); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + + throw $e; + } + } + + public function testHandleUserError() + { + try { + $handler = ErrorHandler::register(); + $handler->throwAt(0, true); + + $e = null; + $x = new \Exception('Foo'); + + try { + $f = new Fixtures\ToStringThrower($x); + $f .= ''; // Trigger $f->__toString() + } catch (\Exception $e) { + } + + $this->assertSame($x, $e); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testHandleDeprecation() + { + $logArgCheck = function ($level, $message, $context) { + $this->assertEquals(LogLevel::INFO, $level); + $this->assertArrayHasKey('exception', $context); + $exception = $context['exception']; + $this->assertInstanceOf(\ErrorException::class, $exception); + $this->assertSame('User Deprecated: Foo deprecation', $exception->getMessage()); + }; + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $logger + ->expects($this->once()) + ->method('log') + ->will($this->returnCallback($logArgCheck)) + ; + + $handler = new ErrorHandler(); + $handler->setDefaultLogger($logger); + @$handler->handleError(E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, array()); + } + + public function testHandleException() + { + try { + $handler = ErrorHandler::register(); + + $exception = new \Exception('foo'); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $logArgCheck = function ($level, $message, $context) { + $this->assertSame('Uncaught Exception: foo', $message); + $this->assertArrayHasKey('exception', $context); + $this->assertInstanceOf(\Exception::class, $context['exception']); + }; + + $logger + ->expects($this->exactly(2)) + ->method('log') + ->will($this->returnCallback($logArgCheck)) + ; + + $handler->setDefaultLogger($logger, E_ERROR); + + try { + $handler->handleException($exception); + $this->fail('Exception expected'); + } catch (\Exception $e) { + $this->assertSame($exception, $e); + } + + $handler->setExceptionHandler(function ($e) use ($exception) { + $this->assertSame($exception, $e); + }); + + $handler->handleException($exception); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testErrorStacking() + { + try { + $handler = ErrorHandler::register(); + $handler->screamAt(E_USER_WARNING); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $logger + ->expects($this->exactly(2)) + ->method('log') + ->withConsecutive( + array($this->equalTo(LogLevel::WARNING), $this->equalTo('Dummy log')), + array($this->equalTo(LogLevel::DEBUG), $this->equalTo('User Warning: Silenced warning')) + ) + ; + + $handler->setDefaultLogger($logger, array(E_USER_WARNING => LogLevel::WARNING)); + + ErrorHandler::stackErrors(); + @trigger_error('Silenced warning', E_USER_WARNING); + $logger->log(LogLevel::WARNING, 'Dummy log'); + ErrorHandler::unstackErrors(); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testBootstrappingLogger() + { + $bootLogger = new BufferingLogger(); + $handler = new ErrorHandler($bootLogger); + + $loggers = array( + E_DEPRECATED => array($bootLogger, LogLevel::INFO), + E_USER_DEPRECATED => array($bootLogger, LogLevel::INFO), + E_NOTICE => array($bootLogger, LogLevel::WARNING), + E_USER_NOTICE => array($bootLogger, LogLevel::WARNING), + E_STRICT => array($bootLogger, LogLevel::WARNING), + E_WARNING => array($bootLogger, LogLevel::WARNING), + E_USER_WARNING => array($bootLogger, LogLevel::WARNING), + E_COMPILE_WARNING => array($bootLogger, LogLevel::WARNING), + E_CORE_WARNING => array($bootLogger, LogLevel::WARNING), + E_USER_ERROR => array($bootLogger, LogLevel::CRITICAL), + E_RECOVERABLE_ERROR => array($bootLogger, LogLevel::CRITICAL), + E_COMPILE_ERROR => array($bootLogger, LogLevel::CRITICAL), + E_PARSE => array($bootLogger, LogLevel::CRITICAL), + E_ERROR => array($bootLogger, LogLevel::CRITICAL), + E_CORE_ERROR => array($bootLogger, LogLevel::CRITICAL), + ); + + $this->assertSame($loggers, $handler->setLoggers(array())); + + $handler->handleError(E_DEPRECATED, 'Foo message', __FILE__, 123, array()); + + $logs = $bootLogger->cleanLogs(); + + $this->assertCount(1, $logs); + $log = $logs[0]; + $this->assertSame('info', $log[0]); + $this->assertSame('Deprecated: Foo message', $log[1]); + $this->assertArrayHasKey('exception', $log[2]); + $exception = $log[2]['exception']; + $this->assertInstanceOf(\ErrorException::class, $exception); + $this->assertSame('Deprecated: Foo message', $exception->getMessage()); + $this->assertSame(__FILE__, $exception->getFile()); + $this->assertSame(123, $exception->getLine()); + $this->assertSame(E_DEPRECATED, $exception->getSeverity()); + + $bootLogger->log(LogLevel::WARNING, 'Foo message', array('exception' => $exception)); + + $mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $mockLogger->expects($this->once()) + ->method('log') + ->with(LogLevel::WARNING, 'Foo message', array('exception' => $exception)); + + $handler->setLoggers(array(E_DEPRECATED => array($mockLogger, LogLevel::WARNING))); + } + + public function testSettingLoggerWhenExceptionIsBuffered() + { + $bootLogger = new BufferingLogger(); + $handler = new ErrorHandler($bootLogger); + + $exception = new \Exception('Foo message'); + + $mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $mockLogger->expects($this->once()) + ->method('log') + ->with(LogLevel::CRITICAL, 'Uncaught Exception: Foo message', array('exception' => $exception)); + + $handler->setExceptionHandler(function () use ($handler, $mockLogger) { + $handler->setDefaultLogger($mockLogger); + }); + + $handler->handleException($exception); + } + + public function testHandleFatalError() + { + try { + $handler = ErrorHandler::register(); + + $error = array( + 'type' => E_PARSE, + 'message' => 'foo', + 'file' => 'bar', + 'line' => 123, + ); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $logArgCheck = function ($level, $message, $context) { + $this->assertEquals('Fatal Parse Error: foo', $message); + $this->assertArrayHasKey('exception', $context); + $this->assertInstanceOf(\Exception::class, $context['exception']); + }; + + $logger + ->expects($this->once()) + ->method('log') + ->will($this->returnCallback($logArgCheck)) + ; + + $handler->setDefaultLogger($logger, E_PARSE); + + $handler->handleFatalError($error); + + restore_error_handler(); + restore_exception_handler(); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + + throw $e; + } + } + + /** + * @requires PHP 7 + */ + public function testHandleErrorException() + { + $exception = new \Error("Class 'Foo' not found"); + + $handler = new ErrorHandler(); + $handler->setExceptionHandler(function () use (&$args) { + $args = func_get_args(); + }); + + $handler->handleException($exception); + + $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $args[0]); + $this->assertStringStartsWith("Attempted to load class \"Foo\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage()); + } + + public function testHandleFatalErrorOnHHVM() + { + try { + $handler = ErrorHandler::register(); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $logger + ->expects($this->once()) + ->method('log') + ->with( + $this->equalTo(LogLevel::CRITICAL), + $this->equalTo('Fatal Error: foo') + ) + ; + + $handler->setDefaultLogger($logger, E_ERROR); + + $error = array( + 'type' => E_ERROR + 0x1000000, // This error level is used by HHVM for fatal errors + 'message' => 'foo', + 'file' => 'bar', + 'line' => 123, + 'context' => array(123), + 'backtrace' => array(456), + ); + + call_user_func_array(array($handler, 'handleError'), $error); + $handler->handleFatalError($error); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } +} diff --git a/vendor/symfony/debug/Tests/Exception/FlattenExceptionTest.php b/vendor/symfony/debug/Tests/Exception/FlattenExceptionTest.php new file mode 100644 index 00000000..e7762bde --- /dev/null +++ b/vendor/symfony/debug/Tests/Exception/FlattenExceptionTest.php @@ -0,0 +1,301 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests\Exception; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException; +use Symfony\Component\HttpKernel\Exception\ConflictHttpException; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\GoneHttpException; +use Symfony\Component\HttpKernel\Exception\LengthRequiredHttpException; +use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException; +use Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException; +use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; +use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; +use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException; + +class FlattenExceptionTest extends TestCase +{ + public function testStatusCode() + { + $flattened = FlattenException::create(new \RuntimeException(), 403); + $this->assertEquals('403', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new \RuntimeException()); + $this->assertEquals('500', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new NotFoundHttpException()); + $this->assertEquals('404', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new UnauthorizedHttpException('Basic realm="My Realm"')); + $this->assertEquals('401', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new BadRequestHttpException()); + $this->assertEquals('400', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new NotAcceptableHttpException()); + $this->assertEquals('406', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new ConflictHttpException()); + $this->assertEquals('409', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new MethodNotAllowedHttpException(array('POST'))); + $this->assertEquals('405', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new AccessDeniedHttpException()); + $this->assertEquals('403', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new GoneHttpException()); + $this->assertEquals('410', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new LengthRequiredHttpException()); + $this->assertEquals('411', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new PreconditionFailedHttpException()); + $this->assertEquals('412', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new PreconditionRequiredHttpException()); + $this->assertEquals('428', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new ServiceUnavailableHttpException()); + $this->assertEquals('503', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new TooManyRequestsHttpException()); + $this->assertEquals('429', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new UnsupportedMediaTypeHttpException()); + $this->assertEquals('415', $flattened->getStatusCode()); + + if (class_exists(SuspiciousOperationException::class)) { + $flattened = FlattenException::create(new SuspiciousOperationException()); + $this->assertEquals('400', $flattened->getStatusCode()); + } + } + + public function testHeadersForHttpException() + { + $flattened = FlattenException::create(new MethodNotAllowedHttpException(array('POST'))); + $this->assertEquals(array('Allow' => 'POST'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new UnauthorizedHttpException('Basic realm="My Realm"')); + $this->assertEquals(array('WWW-Authenticate' => 'Basic realm="My Realm"'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new ServiceUnavailableHttpException('Fri, 31 Dec 1999 23:59:59 GMT')); + $this->assertEquals(array('Retry-After' => 'Fri, 31 Dec 1999 23:59:59 GMT'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new ServiceUnavailableHttpException(120)); + $this->assertEquals(array('Retry-After' => 120), $flattened->getHeaders()); + + $flattened = FlattenException::create(new TooManyRequestsHttpException('Fri, 31 Dec 1999 23:59:59 GMT')); + $this->assertEquals(array('Retry-After' => 'Fri, 31 Dec 1999 23:59:59 GMT'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new TooManyRequestsHttpException(120)); + $this->assertEquals(array('Retry-After' => 120), $flattened->getHeaders()); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testFlattenHttpException(\Exception $exception, $statusCode) + { + $flattened = FlattenException::create($exception); + $flattened2 = FlattenException::create($exception); + + $flattened->setPrevious($flattened2); + + $this->assertEquals($exception->getMessage(), $flattened->getMessage(), 'The message is copied from the original exception.'); + $this->assertEquals($exception->getCode(), $flattened->getCode(), 'The code is copied from the original exception.'); + $this->assertInstanceOf($flattened->getClass(), $exception, 'The class is set to the class of the original exception'); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testPrevious(\Exception $exception, $statusCode) + { + $flattened = FlattenException::create($exception); + $flattened2 = FlattenException::create($exception); + + $flattened->setPrevious($flattened2); + + $this->assertSame($flattened2, $flattened->getPrevious()); + + $this->assertSame(array($flattened2), $flattened->getAllPrevious()); + } + + /** + * @requires PHP 7.0 + */ + public function testPreviousError() + { + $exception = new \Exception('test', 123, new \ParseError('Oh noes!', 42)); + + $flattened = FlattenException::create($exception)->getPrevious(); + + $this->assertEquals($flattened->getMessage(), 'Parse error: Oh noes!', 'The message is copied from the original exception.'); + $this->assertEquals($flattened->getCode(), 42, 'The code is copied from the original exception.'); + $this->assertEquals($flattened->getClass(), 'Symfony\Component\Debug\Exception\FatalThrowableError', 'The class is set to the class of the original exception'); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testLine(\Exception $exception) + { + $flattened = FlattenException::create($exception); + $this->assertSame($exception->getLine(), $flattened->getLine()); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testFile(\Exception $exception) + { + $flattened = FlattenException::create($exception); + $this->assertSame($exception->getFile(), $flattened->getFile()); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testToArray(\Exception $exception, $statusCode) + { + $flattened = FlattenException::create($exception); + $flattened->setTrace(array(), 'foo.php', 123); + + $this->assertEquals(array( + array( + 'message' => 'test', + 'class' => 'Exception', + 'trace' => array(array( + 'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => 'foo.php', 'line' => 123, + 'args' => array(), + )), + ), + ), $flattened->toArray()); + } + + public function flattenDataProvider() + { + return array( + array(new \Exception('test', 123), 500), + ); + } + + public function testArguments() + { + $dh = opendir(__DIR__); + $fh = tmpfile(); + + $incomplete = unserialize('O:14:"BogusTestClass":0:{}'); + + $exception = $this->createException(array( + (object) array('foo' => 1), + new NotFoundHttpException(), + $incomplete, + $dh, + $fh, + function () {}, + array(1, 2), + array('foo' => 123), + null, + true, + false, + 0, + 0.0, + '0', + '', + INF, + NAN, + )); + + $flattened = FlattenException::create($exception); + $trace = $flattened->getTrace(); + $args = $trace[1]['args']; + $array = $args[0][1]; + + closedir($dh); + fclose($fh); + + $i = 0; + $this->assertSame(array('object', 'stdClass'), $array[$i++]); + $this->assertSame(array('object', 'Symfony\Component\HttpKernel\Exception\NotFoundHttpException'), $array[$i++]); + $this->assertSame(array('incomplete-object', 'BogusTestClass'), $array[$i++]); + $this->assertSame(array('resource', defined('HHVM_VERSION') ? 'Directory' : 'stream'), $array[$i++]); + $this->assertSame(array('resource', 'stream'), $array[$i++]); + + $args = $array[$i++]; + $this->assertSame($args[0], 'object'); + $this->assertTrue('Closure' === $args[1] || is_subclass_of($args[1], '\Closure'), 'Expect object class name to be Closure or a subclass of Closure.'); + + $this->assertSame(array('array', array(array('integer', 1), array('integer', 2))), $array[$i++]); + $this->assertSame(array('array', array('foo' => array('integer', 123))), $array[$i++]); + $this->assertSame(array('null', null), $array[$i++]); + $this->assertSame(array('boolean', true), $array[$i++]); + $this->assertSame(array('boolean', false), $array[$i++]); + $this->assertSame(array('integer', 0), $array[$i++]); + $this->assertSame(array('float', 0.0), $array[$i++]); + $this->assertSame(array('string', '0'), $array[$i++]); + $this->assertSame(array('string', ''), $array[$i++]); + $this->assertSame(array('float', INF), $array[$i++]); + + // assertEquals() does not like NAN values. + $this->assertEquals($array[$i][0], 'float'); + $this->assertTrue(is_nan($array[$i++][1])); + } + + public function testRecursionInArguments() + { + $a = array('foo', array(2, &$a)); + $exception = $this->createException($a); + + $flattened = FlattenException::create($exception); + $trace = $flattened->getTrace(); + $this->assertContains('*DEEP NESTED ARRAY*', serialize($trace)); + } + + public function testTooBigArray() + { + $a = array(); + for ($i = 0; $i < 20; ++$i) { + for ($j = 0; $j < 50; ++$j) { + for ($k = 0; $k < 10; ++$k) { + $a[$i][$j][$k] = 'value'; + } + } + } + $a[20] = 'value'; + $a[21] = 'value1'; + $exception = $this->createException($a); + + $flattened = FlattenException::create($exception); + $trace = $flattened->getTrace(); + + $this->assertSame($trace[1]['args'][0], array('array', array('array', '*SKIPPED over 10000 entries*'))); + + $serializeTrace = serialize($trace); + + $this->assertContains('*SKIPPED over 10000 entries*', $serializeTrace); + $this->assertNotContains('*value1*', $serializeTrace); + } + + private function createException($foo) + { + return new \Exception(); + } +} diff --git a/vendor/symfony/debug/Tests/ExceptionHandlerTest.php b/vendor/symfony/debug/Tests/ExceptionHandlerTest.php new file mode 100644 index 00000000..0285eff1 --- /dev/null +++ b/vendor/symfony/debug/Tests/ExceptionHandlerTest.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\ExceptionHandler; +use Symfony\Component\Debug\Exception\OutOfMemoryException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; + +require_once __DIR__.'/HeaderMock.php'; + +class ExceptionHandlerTest extends TestCase +{ + protected function setUp() + { + testHeader(); + } + + protected function tearDown() + { + testHeader(); + } + + public function testDebug() + { + $handler = new ExceptionHandler(false); + + ob_start(); + $handler->sendPhpResponse(new \RuntimeException('Foo')); + $response = ob_get_clean(); + + $this->assertContains('Whoops, looks like something went wrong.', $response); + $this->assertNotContains('
', $response); + + $handler = new ExceptionHandler(true); + + ob_start(); + $handler->sendPhpResponse(new \RuntimeException('Foo')); + $response = ob_get_clean(); + + $this->assertContains('Whoops, looks like something went wrong.', $response); + $this->assertContains('
', $response); + } + + public function testStatusCode() + { + $handler = new ExceptionHandler(false, 'iso8859-1'); + + ob_start(); + $handler->sendPhpResponse(new NotFoundHttpException('Foo')); + $response = ob_get_clean(); + + $this->assertContains('Sorry, the page you are looking for could not be found.', $response); + + $expectedHeaders = array( + array('HTTP/1.0 404', true, null), + array('Content-Type: text/html; charset=iso8859-1', true, null), + ); + + $this->assertSame($expectedHeaders, testHeader()); + } + + public function testHeaders() + { + $handler = new ExceptionHandler(false, 'iso8859-1'); + + ob_start(); + $handler->sendPhpResponse(new MethodNotAllowedHttpException(array('POST'))); + $response = ob_get_clean(); + + $expectedHeaders = array( + array('HTTP/1.0 405', true, null), + array('Allow: POST', false, null), + array('Content-Type: text/html; charset=iso8859-1', true, null), + ); + + $this->assertSame($expectedHeaders, testHeader()); + } + + public function testNestedExceptions() + { + $handler = new ExceptionHandler(true); + ob_start(); + $handler->sendPhpResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar'))); + $response = ob_get_clean(); + + $this->assertStringMatchesFormat('%A

Foo

%A

Bar

%A', $response); + } + + public function testHandle() + { + $exception = new \Exception('foo'); + + $handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(array('sendPhpResponse'))->getMock(); + $handler + ->expects($this->exactly(2)) + ->method('sendPhpResponse'); + + $handler->handle($exception); + + $handler->setHandler(function ($e) use ($exception) { + $this->assertSame($exception, $e); + }); + + $handler->handle($exception); + } + + public function testHandleOutOfMemoryException() + { + $exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__); + + $handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(array('sendPhpResponse'))->getMock(); + $handler + ->expects($this->once()) + ->method('sendPhpResponse'); + + $handler->setHandler(function ($e) { + $this->fail('OutOfMemoryException should bypass the handler'); + }); + + $handler->handle($exception); + } +} diff --git a/vendor/symfony/debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php b/vendor/symfony/debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php new file mode 100644 index 00000000..65c80fc1 --- /dev/null +++ b/vendor/symfony/debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests\FatalErrorHandler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler; +use Symfony\Component\Debug\DebugClassLoader; +use Composer\Autoload\ClassLoader as ComposerClassLoader; + +class ClassNotFoundFatalErrorHandlerTest extends TestCase +{ + public static function setUpBeforeClass() + { + foreach (spl_autoload_functions() as $function) { + if (!is_array($function)) { + continue; + } + + // get class loaders wrapped by DebugClassLoader + if ($function[0] instanceof DebugClassLoader) { + $function = $function[0]->getClassLoader(); + } + + if ($function[0] instanceof ComposerClassLoader) { + $function[0]->add('Symfony_Component_Debug_Tests_Fixtures', dirname(dirname(dirname(dirname(dirname(__DIR__)))))); + break; + } + } + } + + /** + * @dataProvider provideClassNotFoundData + */ + public function testHandleClassNotFound($error, $translatedMessage, $autoloader = null) + { + if ($autoloader) { + // Unregister all autoloaders to ensure the custom provided + // autoloader is the only one to be used during the test run. + $autoloaders = spl_autoload_functions(); + array_map('spl_autoload_unregister', $autoloaders); + spl_autoload_register($autoloader); + } + + $handler = new ClassNotFoundFatalErrorHandler(); + + $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line'])); + + if ($autoloader) { + spl_autoload_unregister($autoloader); + array_map('spl_autoload_register', $autoloaders); + } + + $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception); + $this->assertSame($translatedMessage, $exception->getMessage()); + $this->assertSame($error['type'], $exception->getSeverity()); + $this->assertSame($error['file'], $exception->getFile()); + $this->assertSame($error['line'], $exception->getLine()); + } + + public function provideClassNotFoundData() + { + $autoloader = new ComposerClassLoader(); + $autoloader->add('Symfony\Component\Debug\Exception\\', realpath(__DIR__.'/../../Exception')); + + $debugClassLoader = new DebugClassLoader(array($autoloader, 'loadClass')); + + return array( + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'WhizBangFactory\' not found', + ), + "Attempted to load class \"WhizBangFactory\" from the global namespace.\nDid you forget a \"use\" statement?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\WhizBangFactory\' not found', + ), + "Attempted to load class \"WhizBangFactory\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'PEARClass\' not found', + ), + "Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_Debug_Tests_Fixtures_PEARClass\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + array($autoloader, 'loadClass'), + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + array($debugClassLoader, 'loadClass'), + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?", + function ($className) { /* do nothing here */ }, + ), + ); + } + + public function testCannotRedeclareClass() + { + if (!file_exists(__DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP')) { + $this->markTestSkipped('Can only be run on case insensitive filesystems'); + } + + require_once __DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP'; + + $error = array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\RequiredTwice\' not found', + ); + + $handler = new ClassNotFoundFatalErrorHandler(); + $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line'])); + + $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception); + } +} diff --git a/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php b/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php new file mode 100644 index 00000000..1dc21200 --- /dev/null +++ b/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests\FatalErrorHandler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler; + +class UndefinedFunctionFatalErrorHandlerTest extends TestCase +{ + /** + * @dataProvider provideUndefinedFunctionData + */ + public function testUndefinedFunction($error, $translatedMessage) + { + $handler = new UndefinedFunctionFatalErrorHandler(); + $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line'])); + + $this->assertInstanceOf('Symfony\Component\Debug\Exception\UndefinedFunctionException', $exception); + // class names are case insensitive and PHP/HHVM do not return the same + $this->assertSame(strtolower($translatedMessage), strtolower($exception->getMessage())); + $this->assertSame($error['type'], $exception->getSeverity()); + $this->assertSame($error['file'], $exception->getFile()); + $this->assertSame($error['line'], $exception->getLine()); + } + + public function provideUndefinedFunctionData() + { + return array( + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined function test_namespaced_function()', + ), + "Attempted to call function \"test_namespaced_function\" from the global namespace.\nDid you mean to call \"\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined function Foo\\Bar\\Baz\\test_namespaced_function()', + ), + "Attempted to call function \"test_namespaced_function\" from namespace \"Foo\\Bar\\Baz\".\nDid you mean to call \"\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined function foo()', + ), + 'Attempted to call function "foo" from the global namespace.', + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined function Foo\\Bar\\Baz\\foo()', + ), + 'Attempted to call function "foo" from namespace "Foo\Bar\Baz".', + ), + ); + } +} + +function test_namespaced_function() +{ +} diff --git a/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php b/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php new file mode 100644 index 00000000..739e5b2b --- /dev/null +++ b/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests\FatalErrorHandler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler; + +class UndefinedMethodFatalErrorHandlerTest extends TestCase +{ + /** + * @dataProvider provideUndefinedMethodData + */ + public function testUndefinedMethod($error, $translatedMessage) + { + $handler = new UndefinedMethodFatalErrorHandler(); + $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line'])); + + $this->assertInstanceOf('Symfony\Component\Debug\Exception\UndefinedMethodException', $exception); + $this->assertSame($translatedMessage, $exception->getMessage()); + $this->assertSame($error['type'], $exception->getSeverity()); + $this->assertSame($error['file'], $exception->getFile()); + $this->assertSame($error['line'], $exception->getLine()); + } + + public function provideUndefinedMethodData() + { + return array( + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined method SplObjectStorage::what()', + ), + 'Attempted to call an undefined method named "what" of class "SplObjectStorage".', + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined method SplObjectStorage::walid()', + ), + "Attempted to call an undefined method named \"walid\" of class \"SplObjectStorage\".\nDid you mean to call \"valid\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined method SplObjectStorage::offsetFet()', + ), + "Attempted to call an undefined method named \"offsetFet\" of class \"SplObjectStorage\".\nDid you mean to call e.g. \"offsetGet\", \"offsetSet\" or \"offsetUnset\"?", + ), + array( + array( + 'type' => 1, + 'message' => 'Call to undefined method class@anonymous::test()', + 'file' => '/home/possum/work/symfony/test.php', + 'line' => 11, + ), + 'Attempted to call an undefined method named "test" of class "class@anonymous".', + ), + ); + } +} diff --git a/vendor/symfony/debug/Tests/Fixtures/ClassAlias.php b/vendor/symfony/debug/Tests/Fixtures/ClassAlias.php new file mode 100644 index 00000000..9d6dbaa7 --- /dev/null +++ b/vendor/symfony/debug/Tests/Fixtures/ClassAlias.php @@ -0,0 +1,3 @@ +exception = $e; + } + + public function __toString() + { + try { + throw $this->exception; + } catch (\Exception $e) { + // Using user_error() here is on purpose so we do not forget + // that this alias also should work alongside with trigger_error(). + return user_error($e, E_USER_ERROR); + } + } +} diff --git a/vendor/symfony/debug/Tests/Fixtures/casemismatch.php b/vendor/symfony/debug/Tests/Fixtures/casemismatch.php new file mode 100644 index 00000000..691d660f --- /dev/null +++ b/vendor/symfony/debug/Tests/Fixtures/casemismatch.php @@ -0,0 +1,7 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +function headers_sent() +{ + return false; +} + +function header($str, $replace = true, $status = null) +{ + Tests\testHeader($str, $replace, $status); +} + +namespace Symfony\Component\Debug\Tests; + +function testHeader() +{ + static $headers = array(); + + if (!$h = func_get_args()) { + $h = $headers; + $headers = array(); + + return $h; + } + + $headers[] = func_get_args(); +} diff --git a/vendor/symfony/debug/Tests/MockExceptionHandler.php b/vendor/symfony/debug/Tests/MockExceptionHandler.php new file mode 100644 index 00000000..2d6ce564 --- /dev/null +++ b/vendor/symfony/debug/Tests/MockExceptionHandler.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests; + +use Symfony\Component\Debug\ExceptionHandler; + +class MockExceptionHandler extends ExceptionHandler +{ + public $e; + + public function handle(\Exception $e) + { + $this->e = $e; + } +} diff --git a/vendor/symfony/debug/composer.json b/vendor/symfony/debug/composer.json new file mode 100644 index 00000000..38323dd4 --- /dev/null +++ b/vendor/symfony/debug/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/debug", + "type": "library", + "description": "Symfony Debug 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": "^5.5.9|>=7.0.8", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Debug\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/vendor/symfony/debug/phpunit.xml.dist b/vendor/symfony/debug/phpunit.xml.dist new file mode 100644 index 00000000..12e58612 --- /dev/null +++ b/vendor/symfony/debug/phpunit.xml.dist @@ -0,0 +1,33 @@ + + + + + + + + + + ./Tests/ + + + ./Resources/ext/tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/event-dispatcher/.gitignore b/vendor/symfony/event-dispatcher/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /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 00000000..736bd849 --- /dev/null +++ b/vendor/symfony/event-dispatcher/CHANGELOG.md @@ -0,0 +1,37 @@ +CHANGELOG +========= + +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/ContainerAwareEventDispatcher.php b/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php new file mode 100644 index 00000000..fc7b30f9 --- /dev/null +++ b/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php @@ -0,0 +1,211 @@ + + * + * 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\Component\DependencyInjection\ContainerInterface; + +/** + * Lazily loads listeners and subscribers from the dependency injection + * container. + * + * @author Fabien Potencier + * @author Bernhard Schussek + * @author Jordan Alliot + * + * @deprecated since 3.3, to be removed in 4.0. Use EventDispatcher with closure factories instead. + */ +class ContainerAwareEventDispatcher extends EventDispatcher +{ + /** + * The container from where services are loaded. + * + * @var ContainerInterface + */ + private $container; + + /** + * The service IDs of the event listeners and subscribers. + * + * @var array + */ + private $listenerIds = array(); + + /** + * The services registered as listeners. + * + * @var array + */ + private $listeners = array(); + + /** + * Constructor. + * + * @param ContainerInterface $container A ContainerInterface instance + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + + $class = get_class($this); + if ($this instanceof \PHPUnit_Framework_MockObject_MockObject || $this instanceof \Prophecy\Doubler\DoubleInterface) { + $class = get_parent_class($class); + } + if (__CLASS__ !== $class) { + @trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED); + } + } + + /** + * Adds a service as event listener. + * + * @param string $eventName Event for which the listener is added + * @param array $callback The service ID of the listener service & the method + * name that has to be called + * @param int $priority The higher this value, the earlier an event listener + * will be triggered in the chain. + * Defaults to 0. + * + * @throws \InvalidArgumentException + */ + public function addListenerService($eventName, $callback, $priority = 0) + { + @trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED); + + if (!is_array($callback) || 2 !== count($callback)) { + throw new \InvalidArgumentException('Expected an array("service", "method") argument'); + } + + $this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority); + } + + public function removeListener($eventName, $listener) + { + $this->lazyLoad($eventName); + + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as $i => list($serviceId, $method, $priority)) { + $key = $serviceId.'.'.$method; + if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) { + unset($this->listeners[$eventName][$key]); + if (empty($this->listeners[$eventName])) { + unset($this->listeners[$eventName]); + } + unset($this->listenerIds[$eventName][$i]); + if (empty($this->listenerIds[$eventName])) { + unset($this->listenerIds[$eventName]); + } + } + } + } + + parent::removeListener($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + if (null === $eventName) { + return $this->listenerIds || $this->listeners || parent::hasListeners(); + } + + if (isset($this->listenerIds[$eventName])) { + return true; + } + + return parent::hasListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + if (null === $eventName) { + foreach ($this->listenerIds as $serviceEventName => $args) { + $this->lazyLoad($serviceEventName); + } + } else { + $this->lazyLoad($eventName); + } + + return parent::getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + $this->lazyLoad($eventName); + + return parent::getListenerPriority($eventName, $listener); + } + + /** + * Adds a service as event subscriber. + * + * @param string $serviceId The service ID of the subscriber service + * @param string $class The service's class name (which must implement EventSubscriberInterface) + */ + public function addSubscriberService($serviceId, $class) + { + @trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED); + + foreach ($class::getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->listenerIds[$eventName][] = array($serviceId, $params, 0); + } elseif (is_string($params[0])) { + $this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + public function getContainer() + { + @trigger_error('The '.__METHOD__.'() method is deprecated since version 3.3 as its class will be removed in 4.0. Inject the container or the services you need in your listeners/subscribers instead.', E_USER_DEPRECATED); + + return $this->container; + } + + /** + * Lazily loads listeners for this event from the dependency injection + * container. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + */ + protected function lazyLoad($eventName) + { + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as list($serviceId, $method, $priority)) { + $listener = $this->container->get($serviceId); + + $key = $serviceId.'.'.$method; + if (!isset($this->listeners[$eventName][$key])) { + $this->addListener($eventName, array($listener, $method), $priority); + } elseif ($listener !== $this->listeners[$eventName][$key]) { + parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method)); + $this->addListener($eventName, array($listener, $method), $priority); + } + + $this->listeners[$eventName][$key] = $listener; + } + } + } +} diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php new file mode 100644 index 00000000..988cf112 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php @@ -0,0 +1,324 @@ + + * + * 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\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\Stopwatch\Stopwatch; +use Psr\Log\LoggerInterface; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier + */ +class TraceableEventDispatcher implements TraceableEventDispatcherInterface +{ + protected $logger; + protected $stopwatch; + + private $called; + private $dispatcher; + private $wrappedListeners; + + /** + * Constructor. + * + * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance + * @param Stopwatch $stopwatch A Stopwatch instance + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null) + { + $this->dispatcher = $dispatcher; + $this->stopwatch = $stopwatch; + $this->logger = $logger; + $this->called = array(); + $this->wrappedListeners = array(); + } + + /** + * {@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} + */ + public function dispatch($eventName, Event $event = null) + { + if (null === $event) { + $event = new Event(); + } + + if (null !== $this->logger && $event->isPropagationStopped()) { + $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); + } + + $this->preProcess($eventName); + $this->preDispatch($eventName, $event); + + $e = $this->stopwatch->start($eventName, 'section'); + + $this->dispatcher->dispatch($eventName, $event); + + if ($e->isStarted()) { + $e->stop(); + } + + $this->postDispatch($eventName, $event); + $this->postProcess($eventName); + + return $event; + } + + /** + * {@inheritdoc} + */ + public function getCalledListeners() + { + $called = array(); + foreach ($this->called as $eventName => $listeners) { + foreach ($listeners as $listener) { + $called[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName); + } + } + + return $called; + } + + /** + * {@inheritdoc} + */ + public function getNotCalledListeners() + { + try { + $allListeners = $this->getListeners(); + } catch (\Exception $e) { + if (null !== $this->logger) { + $this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e)); + } + + // unable to retrieve the uncalled listeners + return array(); + } + + $notCalled = array(); + foreach ($allListeners as $eventName => $listeners) { + foreach ($listeners as $listener) { + $called = false; + if (isset($this->called[$eventName])) { + foreach ($this->called[$eventName] as $l) { + if ($l->getWrappedListener() === $listener) { + $called = true; + + break; + } + } + } + + if (!$called) { + if (!$listener instanceof WrappedListener) { + $listener = new WrappedListener($listener, null, $this->stopwatch, $this); + } + $notCalled[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName); + } + } + } + + uasort($notCalled, array($this, 'sortListenersByPriority')); + + return $notCalled; + } + + /** + * 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 call_user_func_array(array($this->dispatcher, $method), $arguments); + } + + /** + * Called before dispatching the event. + * + * @param string $eventName The event name + * @param Event $event The event + */ + protected function preDispatch($eventName, Event $event) + { + } + + /** + * Called after dispatching the event. + * + * @param string $eventName The event name + * @param Event $event The event + */ + protected function postDispatch($eventName, Event $event) + { + } + + private function preProcess($eventName) + { + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + $priority = $this->getListenerPriority($eventName, $listener); + $wrappedListener = new WrappedListener($listener, null, $this->stopwatch, $this); + $this->wrappedListeners[$eventName][] = $wrappedListener; + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $wrappedListener, $priority); + } + } + + 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 = array('event' => $eventName, 'listener' => $listener->getPretty()); + } + + if ($listener->wasCalled()) { + if (null !== $this->logger) { + $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context); + } + + if (!isset($this->called[$eventName])) { + $this->called[$eventName] = new \SplObjectStorage(); + } + + $this->called[$eventName]->attach($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 sortListenersByPriority($a, $b) + { + 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 00000000..5483e815 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php @@ -0,0 +1,34 @@ + + * + * 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; + +/** + * @author Fabien Potencier + */ +interface TraceableEventDispatcherInterface extends EventDispatcherInterface +{ + /** + * Gets the called listeners. + * + * @return array An array of called listeners + */ + public function getCalledListeners(); + + /** + * Gets the not called listeners. + * + * @return array An array of not called listeners + */ + public function getNotCalledListeners(); +} diff --git a/vendor/symfony/event-dispatcher/Debug/WrappedListener.php b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php new file mode 100644 index 00000000..f7b0273e --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php @@ -0,0 +1,114 @@ + + * + * 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\Stopwatch\Stopwatch; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\VarDumper\Caster\ClassStub; + +/** + * @author Fabien Potencier + */ +class WrappedListener +{ + private $listener; + private $name; + private $called; + private $stoppedPropagation; + private $stopwatch; + private $dispatcher; + private $pretty; + private $stub; + private static $hasClassStub; + + public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) + { + $this->listener = $listener; + $this->name = $name; + $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) { + $this->pretty = $this->name = 'closure'; + } 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 array( + 'event' => $eventName, + '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) + { + $this->called = true; + + $e = $this->stopwatch->start($this->name, 'event_listener'); + + call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher); + + if ($e->isStarted()) { + $e->stop(); + } + + if ($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 00000000..50e466a3 --- /dev/null +++ b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php @@ -0,0 +1,135 @@ + + * + * 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\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +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 +{ + /** + * @var string + */ + protected $dispatcherService; + + /** + * @var string + */ + protected $listenerTag; + + /** + * @var string + */ + protected $subscriberTag; + + /** + * Constructor. + * + * @param string $dispatcherService Service name of the event dispatcher in processed container + * @param string $listenerTag Tag name used for listener + * @param string $subscriberTag Tag name used for subscribers + */ + public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber') + { + $this->dispatcherService = $dispatcherService; + $this->listenerTag = $listenerTag; + $this->subscriberTag = $subscriberTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { + return; + } + + $definition = $container->findDefinition($this->dispatcherService); + + foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) { + $def = $container->getDefinition($id); + + 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)); + } + + if (!isset($event['method'])) { + $event['method'] = 'on'.preg_replace_callback(array( + '/(?<=\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']); + } + + $definition->addMethodCall('addListener', array($event['event'], array(new ServiceClosureArgument(new Reference($id)), $event['method']), $priority)); + } + } + + $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 = $container->getParameterBag()->resolveValue($def->getClass()); + $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + + if (!is_subclass_of($class, $interface)) { + if (!class_exists($class, false)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + + throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); + } + $container->addObjectResource($class); + + ExtractingEventDispatcher::$subscriber = $class; + $extractingDispatcher->addSubscriber($extractingDispatcher); + foreach ($extractingDispatcher->listeners as $args) { + $args[1] = array(new ServiceClosureArgument(new Reference($id)), $args[1]); + $definition->addMethodCall('addListener', $args); + } + $extractingDispatcher->listeners = array(); + } + } +} + +/** + * @internal + */ +class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface +{ + public $listeners = array(); + + public static $subscriber; + + public function addListener($eventName, $listener, $priority = 0) + { + $this->listeners[] = array($eventName, $listener[1], $priority); + } + + public static function getSubscribedEvents() + { + $callback = array(self::$subscriber, 'getSubscribedEvents'); + + return $callback(); + } +} diff --git a/vendor/symfony/event-dispatcher/Event.php b/vendor/symfony/event-dispatcher/Event.php new file mode 100644 index 00000000..9c56b2f5 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Event.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * 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 + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + */ +class Event +{ + /** + * @var bool Whether no further event listeners should be triggered + */ + private $propagationStopped = false; + + /** + * Returns whether further event listeners should be triggered. + * + * @see Event::stopPropagation() + * + * @return bool Whether propagation was already stopped for this event + */ + public function isPropagationStopped() + { + 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() + { + $this->propagationStopped = true; + } +} diff --git a/vendor/symfony/event-dispatcher/EventDispatcher.php b/vendor/symfony/event-dispatcher/EventDispatcher.php new file mode 100644 index 00000000..4630b01c --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventDispatcher.php @@ -0,0 +1,236 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * 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 + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * @author Fabien Potencier + * @author Jordi Boggiano + * @author Jordan Alliot + * @author Nicolas Grekas + */ +class EventDispatcher implements EventDispatcherInterface +{ + private $listeners = array(); + private $sorted = array(); + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + if (null === $event) { + $event = new Event(); + } + + if ($listeners = $this->getListeners($eventName)) { + $this->doDispatch($listeners, $eventName, $event); + } + + return $event; + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + if (null !== $eventName) { + if (empty($this->listeners[$eventName])) { + return array(); + } + + 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; + } + + 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](); + $this->listeners[$eventName][$priority][$k] = $v; + } + if ($v === $listener) { + return $priority; + } + } + } + } + + /** + * {@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]); + } + + /** + * {@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]); + } else { + $listeners[$k] = $v; + } + } + + if ($listeners) { + $this->listeners[$eventName][$priority] = $listeners; + } else { + unset($this->listeners[$eventName][$priority]); + } + } + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->addListener($eventName, array($subscriber, $params)); + } elseif (is_string($params[0])) { + $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, array($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, array($subscriber, $listener[0])); + } + } else { + $this->removeListener($eventName, array($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 Event $event The event object to pass to the event handlers/listeners + */ + protected function doDispatch($listeners, $eventName, Event $event) + { + foreach ($listeners as $listener) { + if ($event->isPropagationStopped()) { + break; + } + call_user_func($listener, $event, $eventName, $this); + } + } + + /** + * Sorts the internal list of listeners for the given event by priority. + * + * @param string $eventName The name of the event + */ + private function sortListeners($eventName) + { + krsort($this->listeners[$eventName]); + $this->sorted[$eventName] = array(); + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + foreach ($listeners as $k => $listener) { + if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { + $listener[0] = $listener[0](); + $this->listeners[$eventName][$priority][$k] = $listener; + } + $this->sorted[$eventName][] = $listener; + } + } + } +} diff --git a/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php new file mode 100644 index 00000000..08ebf340 --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * 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 + */ +interface EventDispatcherInterface +{ + /** + * Dispatches an event to all registered listeners. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + * @param Event $event The event to pass to the event handlers/listeners + * If not supplied, an empty Event instance is created. + * + * @return Event + */ + public function dispatch($eventName, Event $event = null); + + /** + * 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 he is + * interested in and added as a listener for these events. + * + * @param EventSubscriberInterface $subscriber The subscriber + */ + 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); + + /** + * Removes an event subscriber. + * + * @param EventSubscriberInterface $subscriber The subscriber + */ + public function removeSubscriber(EventSubscriberInterface $subscriber); + + /** + * Gets the listeners of a specific event or all listeners sorted by descending priority. + * + * @param string $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 $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 00000000..8af77891 --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php @@ -0,0 +1,46 @@ + + * + * 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 himself what events he 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 + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + */ +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: + * + * * array('eventName' => 'methodName') + * * array('eventName' => array('methodName', $priority)) + * * array('eventName' => array(array('methodName1', $priority), array('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 00000000..e8e4cc05 --- /dev/null +++ b/vendor/symfony/event-dispatcher/GenericEvent.php @@ -0,0 +1,186 @@ + + * + * 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 + */ +class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate +{ + /** + * Event subject. + * + * @var mixed usually object or callable + */ + protected $subject; + + /** + * Array of arguments. + * + * @var array + */ + protected $arguments; + + /** + * Encapsulate an event with $subject and $args. + * + * @param mixed $subject The subject of the event, usually an object + * @param array $arguments Arguments to store in the event + */ + public function __construct($subject = null, array $arguments = array()) + { + $this->subject = $subject; + $this->arguments = $arguments; + } + + /** + * Getter for subject property. + * + * @return mixed $subject 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 = array()) + { + $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 00000000..7f2be8d3 --- /dev/null +++ b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php @@ -0,0 +1,101 @@ + + * + * 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 + */ +class ImmutableEventDispatcher implements EventDispatcherInterface +{ + /** + * The proxied dispatcher. + * + * @var EventDispatcherInterface + */ + private $dispatcher; + + /** + * Creates an unmodifiable proxy for an event dispatcher. + * + * @param EventDispatcherInterface $dispatcher The proxied event dispatcher + */ + public function __construct(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + return $this->dispatcher->dispatch($eventName, $event); + } + + /** + * {@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 00000000..17d16a13 --- /dev/null +++ b/vendor/symfony/event-dispatcher/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2017 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/README.md b/vendor/symfony/event-dispatcher/README.md new file mode 100644 index 00000000..185c3fec --- /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/AbstractEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php new file mode 100644 index 00000000..9443f216 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php @@ -0,0 +1,442 @@ + + * + * 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; + +abstract class AbstractEventDispatcherTest 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() + { + $this->dispatcher = $this->createEventDispatcher(); + $this->listener = new TestEventListener(); + } + + protected function tearDown() + { + $this->dispatcher = null; + $this->listener = null; + } + + abstract protected function createEventDispatcher(); + + public function testInitialState() + { + $this->assertEquals(array(), $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', array($this->listener, 'preFoo')); + $this->dispatcher->addListener('post.foo', array($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', array($listener1, 'preFoo'), -10); + $this->dispatcher->addListener('pre.foo', array($listener2, 'preFoo'), 10); + $this->dispatcher->addListener('pre.foo', array($listener3, 'preFoo')); + + $expected = array( + array($listener2, 'preFoo'), + array($listener3, 'preFoo'), + array($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 = array( + 'pre.foo' => array($listener3, $listener2, $listener1), + 'post.foo' => array($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', array($this->listener, 'preFoo')); + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo')); + $this->dispatcher->dispatch(self::preFoo); + $this->assertTrue($this->listener->preFooInvoked); + $this->assertFalse($this->listener->postFooInvoked); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch('noevent')); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(self::preFoo)); + $event = new Event(); + $return = $this->dispatcher->dispatch(self::preFoo, $event); + $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(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', array($this->listener, 'postFoo'), 10); + $this->dispatcher->addListener('post.foo', array($otherListener, 'preFoo')); + $this->dispatcher->dispatch(self::postFoo); + $this->assertTrue($this->listener->postFooInvoked); + $this->assertFalse($otherListener->postFooInvoked); + } + + public function testDispatchByPriority() + { + $invoked = array(); + $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(self::preFoo); + $this->assertEquals(array('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', array($listener, 'foo')); + $this->assertNull($listener->name); + $this->assertNull($listener->dispatcher); + $this->dispatcher->dispatch('test'); + $this->assertEquals('test', $listener->name); + $this->assertSame($this->dispatcher, $listener->dispatcher); + } + + /** + * @see https://bugs.php.net/bug.php?id=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(array(), $this->dispatcher->getListeners()); + } + + public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled() + { + $this->assertFalse($this->dispatcher->hasListeners('foo')); + $this->assertFalse($this->dispatcher->hasListeners()); + } + + public function testHasListenersIsLazy() + { + $called = 0; + $listener = array(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', array($factory, 'foo')); + $this->assertSame(0, $called); + $this->dispatcher->dispatch('foo', new Event()); + $this->dispatcher->dispatch('foo', new Event()); + $this->assertSame(1, $called); + } + + public function testRemoveFindsLazyListeners() + { + $test = new TestWithDispatcher(); + $factory = function () use ($test) { return $test; }; + + $this->dispatcher->addListener('foo', array($factory, 'foo')); + $this->assertTrue($this->dispatcher->hasListeners('foo')); + $this->dispatcher->removeListener('foo', array($test, 'foo')); + $this->assertFalse($this->dispatcher->hasListeners('foo')); + + $this->dispatcher->addListener('foo', array($test, 'foo')); + $this->assertTrue($this->dispatcher->hasListeners('foo')); + $this->dispatcher->removeListener('foo', array($factory, 'foo')); + $this->assertFalse($this->dispatcher->hasListeners('foo')); + } + + public function testPriorityFindsLazyListeners() + { + $test = new TestWithDispatcher(); + $factory = function () use ($test) { return $test; }; + + $this->dispatcher->addListener('foo', array($factory, 'foo'), 3); + $this->assertSame(3, $this->dispatcher->getListenerPriority('foo', array($test, 'foo'))); + $this->dispatcher->removeListener('foo', array($factory, 'foo')); + + $this->dispatcher->addListener('foo', array($test, 'foo'), 5); + $this->assertSame(5, $this->dispatcher->getListenerPriority('foo', array($factory, 'foo'))); + } + + public function testGetLazyListeners() + { + $test = new TestWithDispatcher(); + $factory = function () use ($test) { return $test; }; + + $this->dispatcher->addListener('foo', array($factory, 'foo'), 3); + $this->assertSame(array(array($test, 'foo')), $this->dispatcher->getListeners('foo')); + + $this->dispatcher->removeListener('foo', array($test, 'foo')); + $this->dispatcher->addListener('bar', array($factory, 'foo'), 3); + $this->assertSame(array('bar' => array(array($test, 'foo'))), $this->dispatcher->getListeners()); + } +} + +class CallableClass +{ + public function __invoke() + { + } +} + +class TestEventListener +{ + public $preFooInvoked = false; + public $postFooInvoked = false; + + /* Listener methods */ + + public function preFoo(Event $e) + { + $this->preFooInvoked = true; + } + + public function postFoo(Event $e) + { + $this->postFooInvoked = true; + + $e->stopPropagation(); + } +} + +class TestWithDispatcher +{ + public $name; + public $dispatcher; + + public function foo(Event $e, $name, $dispatcher) + { + $this->name = $name; + $this->dispatcher = $dispatcher; + } +} + +class TestEventSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('pre.foo' => 'preFoo', 'post.foo' => 'postFoo'); + } +} + +class TestEventSubscriberWithPriorities implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + 'pre.foo' => array('preFoo', 10), + 'post.foo' => array('postFoo'), + ); + } +} + +class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('pre.foo' => array( + array('preFoo1'), + array('preFoo2', 10), + )); + } +} diff --git a/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php new file mode 100644 index 00000000..18055614 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php @@ -0,0 +1,210 @@ + + * + * 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\DependencyInjection\Container; +use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * @group legacy + */ +class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest +{ + protected function createEventDispatcher() + { + $container = new Container(); + + return new ContainerAwareEventDispatcher($container); + } + + public function testAddAListenerService() + { + $event = new Event(); + + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->dispatch('onEvent', $event); + } + + public function testAddASubscriberService() + { + $event = new Event(); + + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\SubscriberService')->getMock(); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $service + ->expects($this->once()) + ->method('onEventWithPriority') + ->with($event) + ; + + $service + ->expects($this->once()) + ->method('onEventNested') + ->with($event) + ; + + $container = new Container(); + $container->set('service.subscriber', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addSubscriberService('service.subscriber', 'Symfony\Component\EventDispatcher\Tests\SubscriberService'); + + $dispatcher->dispatch('onEvent', $event); + $dispatcher->dispatch('onEventWithPriority', $event); + $dispatcher->dispatch('onEventNested', $event); + } + + public function testPreventDuplicateListenerService() + { + $event = new Event(); + + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 5); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 10); + + $dispatcher->dispatch('onEvent', $event); + } + + public function testHasListenersOnLazyLoad() + { + $event = new Event(); + + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $this->assertTrue($dispatcher->hasListeners()); + + if ($dispatcher->hasListeners('onEvent')) { + $dispatcher->dispatch('onEvent'); + } + } + + public function testGetListenersOnLazyLoad() + { + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $listeners = $dispatcher->getListeners(); + + $this->assertTrue(isset($listeners['onEvent'])); + + $this->assertCount(1, $dispatcher->getListeners('onEvent')); + } + + public function testRemoveAfterDispatch() + { + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->dispatch('onEvent', new Event()); + $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent')); + $this->assertFalse($dispatcher->hasListeners('onEvent')); + } + + public function testRemoveBeforeDispatch() + { + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent')); + $this->assertFalse($dispatcher->hasListeners('onEvent')); + } +} + +class Service +{ + public function onEvent(Event $e) + { + } +} + +class SubscriberService implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + 'onEvent' => 'onEvent', + 'onEventWithPriority' => array('onEventWithPriority', 10), + 'onEventNested' => array(array('onEventNested')), + ); + } + + public function onEvent(Event $e) + { + } + + public function onEventWithPriority(Event $e) + { + } + + public function onEventNested(Event $e) + { + } +} 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 00000000..a1cf6708 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php @@ -0,0 +1,242 @@ + + * + * 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\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\Stopwatch\Stopwatch; + +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('foo', new Event()); + $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('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(array($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['foo.closure']); + unset($listeners['foo.closure']['stub']); + $this->assertEquals(array(), $tdispatcher->getCalledListeners()); + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'pretty' => 'closure', 'priority' => 5)), $listeners); + + $tdispatcher->dispatch('foo'); + + $listeners = $tdispatcher->getCalledListeners(); + $this->assertArrayHasKey('stub', $listeners['foo.closure']); + unset($listeners['foo.closure']['stub']); + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'pretty' => 'closure', 'priority' => 5)), $listeners); + $this->assertEquals(array(), $tdispatcher->getNotCalledListeners()); + } + + 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('bar'); + }); + $dispatcher->addListener('bar', function (Event $event) {}); + $dispatcher->dispatch('foo'); + $this->assertSame($dispatcher, $tdispatcher); + $this->assertCount(2, $dispatcher->getCalledListeners()); + } + + 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}".', array('event' => 'foo', 'listener' => 'closure')); + $logger->expects($this->at(1))->method('debug')->with('Notified event "{event}" to listener "{listener}".', array('event' => 'foo', 'listener' => 'closure')); + + $tdispatcher->dispatch('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}".', array('event' => 'foo', 'listener' => 'closure')); + $logger->expects($this->at(1))->method('debug')->with('Listener "{listener}" stopped propagation of the event "{event}".', array('event' => 'foo', 'listener' => 'closure')); + $logger->expects($this->at(2))->method('debug')->with('Listener "{listener}" was not called for event "{event}".', array('event' => 'foo', 'listener' => 'closure')); + + $tdispatcher->dispatch('foo'); + } + + public function testDispatchCallListeners() + { + $called = array(); + + $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('foo'); + + $this->assertSame(array('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('foo'); + } + }); + $dispatcher->addListener('foo', function () use (&$dispatchedEvents) { + ++$dispatchedEvents; + }); + + $dispatcher->dispatch('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('bar', $e); + }); + $dispatcher->addListener('bar', function (Event $e) use (&$nestedCall) { + $nestedCall = true; + }); + + $this->assertFalse($nestedCall); + $dispatcher->dispatch('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('foo'); + + $this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed'); + } +} + +class EventSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('foo' => 'call'); + } +} 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 00000000..d46d8c59 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php @@ -0,0 +1,167 @@ + + * + * 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; + +class RegisterListenersPassTest extends TestCase +{ + /** + * Tests that event subscribers not implementing EventSubscriberInterface + * trigger an exception. + * + * @expectedException \InvalidArgumentException + */ + public function testEventSubscriberWithoutInterface() + { + // one service, not implementing any interface + $services = array( + 'my_event_subscriber' => array(0 => array()), + ); + + $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('stdClass')); + + $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition'))->getMock(); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.event_listener here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->onConsecutiveCalls(array(), $services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($builder); + } + + public function testValidEventSubscriber() + { + $services = array( + 'my_event_subscriber' => array(0 => array()), + ); + + $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService')); + + $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition', 'findDefinition'))->getMock(); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.event_listener here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->onConsecutiveCalls(array(), $services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $builder->expects($this->atLeastOnce()) + ->method('findDefinition') + ->will($this->returnValue($definition)); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($builder); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" tagged "kernel.event_listener" must not be abstract. + */ + public function testAbstractEventListener() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_listener', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" tagged "kernel.event_subscriber" must not be abstract. + */ + public function testAbstractEventSubscriber() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_subscriber', array()); + $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', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + + $definition = $container->getDefinition('event_dispatcher'); + $expectedCalls = array( + array( + 'addListener', + array( + 'event', + array(new ServiceClosureArgument(new Reference('foo')), 'onEvent'), + 0, + ), + ), + ); + $this->assertEquals($expectedCalls, $definition->getMethodCalls()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage You have requested a non-existent parameter "subscriber.class" + */ + public function testEventSubscriberUnresolvableClassName() + { + $container = new ContainerBuilder(); + $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } +} + +class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + 'event' => 'onEvent', + ); + } +} diff --git a/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php new file mode 100644 index 00000000..5faa5c8b --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php @@ -0,0 +1,22 @@ + + * + * 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 EventDispatcherTest extends AbstractEventDispatcherTest +{ + protected function createEventDispatcher() + { + return new EventDispatcher(); + } +} diff --git a/vendor/symfony/event-dispatcher/Tests/EventTest.php b/vendor/symfony/event-dispatcher/Tests/EventTest.php new file mode 100644 index 00000000..5be2ea09 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/EventTest.php @@ -0,0 +1,55 @@ + + * + * 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; + +/** + * Test class for Event. + */ +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() + { + $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() + { + $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 00000000..c84d3ac2 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php @@ -0,0 +1,140 @@ + + * + * 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() + { + parent::setUp(); + + $this->subject = new \stdClass(); + $this->event = new GenericEvent($this->subject, array('name' => 'Event')); + } + + /** + * Cleans up the environment after running a test. + */ + protected function tearDown() + { + $this->subject = null; + $this->event = null; + + parent::tearDown(); + } + + public function testConstruct() + { + $this->assertEquals($this->event, new GenericEvent($this->subject, array('name' => 'Event'))); + } + + /** + * Tests Event->getArgs(). + */ + public function testGetArguments() + { + // test getting all + $this->assertSame(array('name' => 'Event'), $this->event->getArguments()); + } + + public function testSetArguments() + { + $result = $this->event->setArguments(array('foo' => 'bar')); + $this->assertAttributeSame(array('foo' => 'bar'), 'arguments', $this->event); + $this->assertSame($this->event, $result); + } + + public function testSetArgument() + { + $result = $this->event->setArgument('foo2', 'bar2'); + $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event); + $this->assertEquals($this->event, $result); + } + + public function testGetArgument() + { + // test getting key + $this->assertEquals('Event', $this->event->getArgument('name')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetArgException() + { + $this->event->getArgument('nameNotExist'); + } + + public function testOffsetGet() + { + // test getting key + $this->assertEquals('Event', $this->event['name']); + + // test getting invalid arg + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException'); + $this->assertFalse($this->event['nameNotExist']); + } + + public function testOffsetSet() + { + $this->event['foo2'] = 'bar2'; + $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event); + } + + public function testOffsetUnset() + { + unset($this->event['name']); + $this->assertAttributeSame(array(), 'arguments', $this->event); + } + + public function testOffsetIsset() + { + $this->assertTrue(isset($this->event['name'])); + $this->assertFalse(isset($this->event['nameNotExist'])); + } + + 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 = array(); + foreach ($this->event as $key => $value) { + $data[$key] = $value; + } + $this->assertEquals(array('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 00000000..04f2861e --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php @@ -0,0 +1,106 @@ + + * + * 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\ImmutableEventDispatcher; + +/** + * @author Bernhard Schussek + */ +class ImmutableEventDispatcherTest extends TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $innerDispatcher; + + /** + * @var ImmutableEventDispatcher + */ + private $dispatcher; + + protected function setUp() + { + $this->innerDispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); + $this->dispatcher = new ImmutableEventDispatcher($this->innerDispatcher); + } + + public function testDispatchDelegates() + { + $event = new Event(); + + $this->innerDispatcher->expects($this->once()) + ->method('dispatch') + ->with('event', $event) + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->dispatch('event', $event)); + } + + public function testGetListenersDelegates() + { + $this->innerDispatcher->expects($this->once()) + ->method('getListeners') + ->with('event') + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->getListeners('event')); + } + + public function testHasListenersDelegates() + { + $this->innerDispatcher->expects($this->once()) + ->method('hasListeners') + ->with('event') + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->hasListeners('event')); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testAddListenerDisallowed() + { + $this->dispatcher->addListener('event', function () { return 'foo'; }); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testAddSubscriberDisallowed() + { + $subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock(); + + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRemoveListenerDisallowed() + { + $this->dispatcher->removeListener('event', function () { return 'foo'; }); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRemoveSubscriberDisallowed() + { + $subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock(); + + $this->dispatcher->removeSubscriber($subscriber); + } +} diff --git a/vendor/symfony/event-dispatcher/composer.json b/vendor/symfony/event-dispatcher/composer.json new file mode 100644 index 00000000..994e8ca6 --- /dev/null +++ b/vendor/symfony/event-dispatcher/composer.json @@ -0,0 +1,47 @@ +{ + "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": "^5.5.9|>=7.0.8" + }, + "require-dev": { + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "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": "3.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 00000000..b3ad1bdf --- /dev/null +++ b/vendor/symfony/event-dispatcher/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/http-foundation/.gitignore b/vendor/symfony/http-foundation/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/http-foundation/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/http-foundation/AcceptHeader.php b/vendor/symfony/http-foundation/AcceptHeader.php new file mode 100644 index 00000000..2aa91dc4 --- /dev/null +++ b/vendor/symfony/http-foundation/AcceptHeader.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents an Accept-* header. + * + * An accept header is compound with a list of items, + * sorted by descending quality. + * + * @author Jean-François Simon + */ +class AcceptHeader +{ + /** + * @var AcceptHeaderItem[] + */ + private $items = array(); + + /** + * @var bool + */ + private $sorted = true; + + /** + * Constructor. + * + * @param AcceptHeaderItem[] $items + */ + public function __construct(array $items) + { + foreach ($items as $item) { + $this->add($item); + } + } + + /** + * Builds an AcceptHeader instance from a string. + * + * @param string $headerValue + * + * @return self + */ + public static function fromString($headerValue) + { + $index = 0; + + return new self(array_map(function ($itemValue) use (&$index) { + $item = AcceptHeaderItem::fromString($itemValue); + $item->setIndex($index++); + + return $item; + }, preg_split('/\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\s*/', $headerValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE))); + } + + /** + * Returns header value's string representation. + * + * @return string + */ + public function __toString() + { + return implode(',', $this->items); + } + + /** + * Tests if header has given value. + * + * @param string $value + * + * @return bool + */ + public function has($value) + { + return isset($this->items[$value]); + } + + /** + * Returns given value's item, if exists. + * + * @param string $value + * + * @return AcceptHeaderItem|null + */ + public function get($value) + { + return isset($this->items[$value]) ? $this->items[$value] : null; + } + + /** + * Adds an item. + * + * @param AcceptHeaderItem $item + * + * @return $this + */ + public function add(AcceptHeaderItem $item) + { + $this->items[$item->getValue()] = $item; + $this->sorted = false; + + return $this; + } + + /** + * Returns all items. + * + * @return AcceptHeaderItem[] + */ + public function all() + { + $this->sort(); + + return $this->items; + } + + /** + * Filters items on their value using given regex. + * + * @param string $pattern + * + * @return self + */ + public function filter($pattern) + { + return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) { + return preg_match($pattern, $item->getValue()); + })); + } + + /** + * Returns first item. + * + * @return AcceptHeaderItem|null + */ + public function first() + { + $this->sort(); + + return !empty($this->items) ? reset($this->items) : null; + } + + /** + * Sorts items by descending quality. + */ + private function sort() + { + if (!$this->sorted) { + uasort($this->items, function ($a, $b) { + $qA = $a->getQuality(); + $qB = $b->getQuality(); + + if ($qA === $qB) { + return $a->getIndex() > $b->getIndex() ? 1 : -1; + } + + return $qA > $qB ? -1 : 1; + }); + + $this->sorted = true; + } + } +} diff --git a/vendor/symfony/http-foundation/AcceptHeaderItem.php b/vendor/symfony/http-foundation/AcceptHeaderItem.php new file mode 100644 index 00000000..fb54b493 --- /dev/null +++ b/vendor/symfony/http-foundation/AcceptHeaderItem.php @@ -0,0 +1,226 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents an Accept-* header item. + * + * @author Jean-François Simon + */ +class AcceptHeaderItem +{ + /** + * @var string + */ + private $value; + + /** + * @var float + */ + private $quality = 1.0; + + /** + * @var int + */ + private $index = 0; + + /** + * @var array + */ + private $attributes = array(); + + /** + * Constructor. + * + * @param string $value + * @param array $attributes + */ + public function __construct($value, array $attributes = array()) + { + $this->value = $value; + foreach ($attributes as $name => $value) { + $this->setAttribute($name, $value); + } + } + + /** + * Builds an AcceptHeaderInstance instance from a string. + * + * @param string $itemValue + * + * @return self + */ + public static function fromString($itemValue) + { + $bits = preg_split('/\s*(?:;*("[^"]+");*|;*(\'[^\']+\');*|;+)\s*/', $itemValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $value = array_shift($bits); + $attributes = array(); + + $lastNullAttribute = null; + foreach ($bits as $bit) { + if (($start = substr($bit, 0, 1)) === ($end = substr($bit, -1)) && ($start === '"' || $start === '\'')) { + $attributes[$lastNullAttribute] = substr($bit, 1, -1); + } elseif ('=' === $end) { + $lastNullAttribute = $bit = substr($bit, 0, -1); + $attributes[$bit] = null; + } else { + $parts = explode('=', $bit); + $attributes[$parts[0]] = isset($parts[1]) && strlen($parts[1]) > 0 ? $parts[1] : ''; + } + } + + return new self(($start = substr($value, 0, 1)) === ($end = substr($value, -1)) && ($start === '"' || $start === '\'') ? substr($value, 1, -1) : $value, $attributes); + } + + /** + * Returns header value's string representation. + * + * @return string + */ + public function __toString() + { + $string = $this->value.($this->quality < 1 ? ';q='.$this->quality : ''); + if (count($this->attributes) > 0) { + $string .= ';'.implode(';', array_map(function ($name, $value) { + return sprintf(preg_match('/[,;=]/', $value) ? '%s="%s"' : '%s=%s', $name, $value); + }, array_keys($this->attributes), $this->attributes)); + } + + return $string; + } + + /** + * Set the item value. + * + * @param string $value + * + * @return $this + */ + public function setValue($value) + { + $this->value = $value; + + return $this; + } + + /** + * Returns the item value. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Set the item quality. + * + * @param float $quality + * + * @return $this + */ + public function setQuality($quality) + { + $this->quality = $quality; + + return $this; + } + + /** + * Returns the item quality. + * + * @return float + */ + public function getQuality() + { + return $this->quality; + } + + /** + * Set the item index. + * + * @param int $index + * + * @return $this + */ + public function setIndex($index) + { + $this->index = $index; + + return $this; + } + + /** + * Returns the item index. + * + * @return int + */ + public function getIndex() + { + return $this->index; + } + + /** + * Tests if an attribute exists. + * + * @param string $name + * + * @return bool + */ + public function hasAttribute($name) + { + return isset($this->attributes[$name]); + } + + /** + * Returns an attribute by its name. + * + * @param string $name + * @param mixed $default + * + * @return mixed + */ + public function getAttribute($name, $default = null) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + } + + /** + * Returns all attributes. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Set an attribute. + * + * @param string $name + * @param string $value + * + * @return $this + */ + public function setAttribute($name, $value) + { + if ('q' === $name) { + $this->quality = (float) $value; + } else { + $this->attributes[$name] = (string) $value; + } + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/ApacheRequest.php b/vendor/symfony/http-foundation/ApacheRequest.php new file mode 100644 index 00000000..84803eba --- /dev/null +++ b/vendor/symfony/http-foundation/ApacheRequest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Request represents an HTTP request from an Apache server. + * + * @author Fabien Potencier + */ +class ApacheRequest extends Request +{ + /** + * {@inheritdoc} + */ + protected function prepareRequestUri() + { + return $this->server->get('REQUEST_URI'); + } + + /** + * {@inheritdoc} + */ + protected function prepareBaseUrl() + { + $baseUrl = $this->server->get('SCRIPT_NAME'); + + if (false === strpos($this->server->get('REQUEST_URI'), $baseUrl)) { + // assume mod_rewrite + return rtrim(dirname($baseUrl), '/\\'); + } + + return $baseUrl; + } +} diff --git a/vendor/symfony/http-foundation/BinaryFileResponse.php b/vendor/symfony/http-foundation/BinaryFileResponse.php new file mode 100644 index 00000000..dcf5dcfe --- /dev/null +++ b/vendor/symfony/http-foundation/BinaryFileResponse.php @@ -0,0 +1,361 @@ + + * + * 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\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\Exception\FileException; + +/** + * BinaryFileResponse represents an HTTP response delivering a file. + * + * @author Niklas Fiekas + * @author stealth35 + * @author Igor Wiedler + * @author Jordan Alliot + * @author Sergey Linnik + */ +class BinaryFileResponse extends Response +{ + protected static $trustXSendfileTypeHeader = false; + + /** + * @var File + */ + protected $file; + protected $offset; + protected $maxlen; + protected $deleteFileAfterSend = false; + + /** + * Constructor. + * + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $public Files are public by default + * @param null|string $contentDisposition The type of Content-Disposition to set automatically with the filename + * @param bool $autoEtag Whether the ETag header should be automatically set + * @param bool $autoLastModified Whether the Last-Modified header should be automatically set + */ + public function __construct($file, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true) + { + parent::__construct(null, $status, $headers); + + $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified); + + if ($public) { + $this->setPublic(); + } + } + + /** + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $public Files are public by default + * @param null|string $contentDisposition The type of Content-Disposition to set automatically with the filename + * @param bool $autoEtag Whether the ETag header should be automatically set + * @param bool $autoLastModified Whether the Last-Modified header should be automatically set + * + * @return static + */ + public static function create($file = null, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true) + { + return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified); + } + + /** + * Sets the file to stream. + * + * @param \SplFileInfo|string $file The file to stream + * @param string $contentDisposition + * @param bool $autoEtag + * @param bool $autoLastModified + * + * @return $this + * + * @throws FileException + */ + public function setFile($file, $contentDisposition = null, $autoEtag = false, $autoLastModified = true) + { + if (!$file instanceof File) { + if ($file instanceof \SplFileInfo) { + $file = new File($file->getPathname()); + } else { + $file = new File((string) $file); + } + } + + if (!$file->isReadable()) { + throw new FileException('File must be readable.'); + } + + $this->file = $file; + + if ($autoEtag) { + $this->setAutoEtag(); + } + + if ($autoLastModified) { + $this->setAutoLastModified(); + } + + if ($contentDisposition) { + $this->setContentDisposition($contentDisposition); + } + + return $this; + } + + /** + * Gets the file. + * + * @return File The file to stream + */ + public function getFile() + { + return $this->file; + } + + /** + * Automatically sets the Last-Modified header according the file modification date. + */ + public function setAutoLastModified() + { + $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime())); + + return $this; + } + + /** + * Automatically sets the ETag header according to the checksum of the file. + */ + public function setAutoEtag() + { + $this->setEtag(sha1_file($this->file->getPathname())); + + return $this; + } + + /** + * Sets the Content-Disposition header with the given filename. + * + * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT + * @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file + * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename + * + * @return $this + */ + public function setContentDisposition($disposition, $filename = '', $filenameFallback = '') + { + if ($filename === '') { + $filename = $this->file->getFilename(); + } + + if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) { + $encoding = mb_detect_encoding($filename, null, true) ?: '8bit'; + + for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) { + $char = mb_substr($filename, $i, 1, $encoding); + + if ('%' === $char || ord($char) < 32 || ord($char) > 126) { + $filenameFallback .= '_'; + } else { + $filenameFallback .= $char; + } + } + } + + $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback); + $this->headers->set('Content-Disposition', $dispositionHeader); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function prepare(Request $request) + { + if (!$this->headers->has('Content-Type')) { + $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream'); + } + + if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + $this->ensureIEOverSSLCompatibility($request); + + $this->offset = 0; + $this->maxlen = -1; + + if (false === $fileSize = $this->file->getSize()) { + return $this; + } + $this->headers->set('Content-Length', $fileSize); + + if (!$this->headers->has('Accept-Ranges')) { + // Only accept ranges on safe HTTP methods + $this->headers->set('Accept-Ranges', $request->isMethodSafe(false) ? 'bytes' : 'none'); + } + + if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) { + // Use X-Sendfile, do not send any content. + $type = $request->headers->get('X-Sendfile-Type'); + $path = $this->file->getRealPath(); + // Fall back to scheme://path for stream wrapped locations. + if (false === $path) { + $path = $this->file->getPathname(); + } + if (strtolower($type) === 'x-accel-redirect') { + // Do X-Accel-Mapping substitutions. + // @link http://wiki.nginx.org/X-accel#X-Accel-Redirect + foreach (explode(',', $request->headers->get('X-Accel-Mapping', '')) as $mapping) { + $mapping = explode('=', $mapping, 2); + + if (2 === count($mapping)) { + $pathPrefix = trim($mapping[0]); + $location = trim($mapping[1]); + + if (substr($path, 0, strlen($pathPrefix)) === $pathPrefix) { + $path = $location.substr($path, strlen($pathPrefix)); + break; + } + } + } + } + $this->headers->set($type, $path); + $this->maxlen = 0; + } elseif ($request->headers->has('Range')) { + // Process the range headers. + if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) { + $range = $request->headers->get('Range'); + + list($start, $end) = explode('-', substr($range, 6), 2) + array(0); + + $end = ('' === $end) ? $fileSize - 1 : (int) $end; + + if ('' === $start) { + $start = $fileSize - $end; + $end = $fileSize - 1; + } else { + $start = (int) $start; + } + + if ($start <= $end) { + if ($start < 0 || $end > $fileSize - 1) { + $this->setStatusCode(416); + $this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize)); + } elseif ($start !== 0 || $end !== $fileSize - 1) { + $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1; + $this->offset = $start; + + $this->setStatusCode(206); + $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize)); + $this->headers->set('Content-Length', $end - $start + 1); + } + } + } + } + + return $this; + } + + private function hasValidIfRangeHeader($header) + { + if ($this->getEtag() === $header) { + return true; + } + + if (null === $lastModified = $this->getLastModified()) { + return false; + } + + return $lastModified->format('D, d M Y H:i:s').' GMT' === $header; + } + + /** + * Sends the file. + * + * {@inheritdoc} + */ + public function sendContent() + { + if (!$this->isSuccessful()) { + return parent::sendContent(); + } + + if (0 === $this->maxlen) { + return $this; + } + + $out = fopen('php://output', 'wb'); + $file = fopen($this->file->getPathname(), 'rb'); + + stream_copy_to_stream($file, $out, $this->maxlen, $this->offset); + + fclose($out); + fclose($file); + + if ($this->deleteFileAfterSend) { + unlink($this->file->getPathname()); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @throws \LogicException when the content is not null + */ + public function setContent($content) + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.'); + } + } + + /** + * {@inheritdoc} + * + * @return false + */ + public function getContent() + { + return false; + } + + /** + * Trust X-Sendfile-Type header. + */ + public static function trustXSendfileTypeHeader() + { + self::$trustXSendfileTypeHeader = true; + } + + /** + * If this is set to true, the file will be unlinked after the request is send + * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used. + * + * @param bool $shouldDelete + * + * @return $this + */ + public function deleteFileAfterSend($shouldDelete) + { + $this->deleteFileAfterSend = $shouldDelete; + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/CHANGELOG.md b/vendor/symfony/http-foundation/CHANGELOG.md new file mode 100644 index 00000000..e1fdf77b --- /dev/null +++ b/vendor/symfony/http-foundation/CHANGELOG.md @@ -0,0 +1,149 @@ +CHANGELOG +========= + +3.3.0 +----- + + * the `Request::setTrustedProxies()` method takes a new `$trustedHeaderSet` argument, + see http://symfony.com/doc/current/components/http_foundation/trusting_proxies.html for more info, + * deprecated the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` methods, + * added `File\Stream`, to be passed to `BinaryFileResponse` when the size of the served file is unknown, + disabling `Range` and `Content-Length` handling, switching to chunked encoding instead + * added the `Cookie::fromString()` method that allows to create a cookie from a + raw header string + +3.1.0 +----- + + * Added support for creating `JsonResponse` with a string of JSON data + +3.0.0 +----- + + * The precedence of parameters returned from `Request::get()` changed from "GET, PATH, BODY" to "PATH, GET, BODY" + +2.8.0 +----- + + * Finding deep items in `ParameterBag::get()` is deprecated since version 2.8 and + will be removed in 3.0. + +2.6.0 +----- + + * PdoSessionHandler changes + - implemented different session locking strategies to prevent loss of data by concurrent access to the same session + - [BC BREAK] save session data in a binary column without base64_encode + - [BC BREAK] added lifetime column to the session table which allows to have different lifetimes for each session + - implemented lazy connections that are only opened when a session is used by either passing a dsn string + explicitly or falling back to session.save_path ini setting + - added a createTable method that initializes a correctly defined table depending on the database vendor + +2.5.0 +----- + + * added `JsonResponse::setEncodingOptions()` & `JsonResponse::getEncodingOptions()` for easier manipulation + of the options used while encoding data to JSON format. + +2.4.0 +----- + + * added RequestStack + * added Request::getEncodings() + * added accessors methods to session handlers + +2.3.0 +----- + + * added support for ranges of IPs in trusted proxies + * `UploadedFile::isValid` now returns false if the file was not uploaded via HTTP (in a non-test mode) + * Improved error-handling of `\Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler` + to ensure the supplied PDO handler throws Exceptions on error (as the class expects). Added related test cases + to verify that Exceptions are properly thrown when the PDO queries fail. + +2.2.0 +----- + + * fixed the Request::create() precedence (URI information always take precedence now) + * added Request::getTrustedProxies() + * deprecated Request::isProxyTrusted() + * [BC BREAK] JsonResponse does not turn a top level empty array to an object anymore, use an ArrayObject to enforce objects + * added a IpUtils class to check if an IP belongs to a CIDR + * added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method) + * disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to + enable it, and Request::getHttpMethodParameterOverride() to check if it is supported) + * Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3 + * Deprecated Flashbag::count() and \Countable interface, will be removed in 2.3 + +2.1.0 +----- + + * added Request::getSchemeAndHttpHost() and Request::getUserInfo() + * added a fluent interface to the Response class + * added Request::isProxyTrusted() + * added JsonResponse + * added a getTargetUrl method to RedirectResponse + * added support for streamed responses + * made Response::prepare() method the place to enforce HTTP specification + * [BC BREAK] moved management of the locale from the Session class to the Request class + * added a generic access to the PHP built-in filter mechanism: ParameterBag::filter() + * made FileBinaryMimeTypeGuesser command configurable + * added Request::getUser() and Request::getPassword() + * added support for the PATCH method in Request + * removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3 + * added ResponseHeaderBag::makeDisposition() (implements RFC 6266) + * made mimetype to extension conversion configurable + * [BC BREAK] Moved all session related classes and interfaces into own namespace, as + `Symfony\Component\HttpFoundation\Session` and renamed classes accordingly. + Session handlers are located in the subnamespace `Symfony\Component\HttpFoundation\Session\Handler`. + * SessionHandlers must implement `\SessionHandlerInterface` or extend from the + `Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class. + * Added internal storage driver proxy mechanism for forward compatibility with + PHP 5.4 `\SessionHandler` class. + * Added session handlers for custom Memcache, Memcached and Null session save handlers. + * [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`. + * [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and + `remove()`. Added `getBag()`, `registerBag()`. The `NativeSessionStorage` class + is a mediator for the session storage internals including the session handlers + which do the real work of participating in the internal PHP session workflow. + * [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit + and functional testing without starting real PHP sessions. Removed + `ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit + tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage` + for functional tests. These do not interact with global session ini + configuration values, session functions or `$_SESSION` superglobal. This means + they can be configured directly allowing multiple instances to work without + conflicting in the same PHP process. + * [BC BREAK] Removed the `close()` method from the `Session` class, as this is + now redundant. + * Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()` + `getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag()` instead + which returns a `FlashBagInterface`. + * `Session->clear()` now only clears session attributes as before it cleared + flash messages and attributes. `Session->getFlashBag()->all()` clears flashes now. + * Session data is now managed by `SessionBagInterface` to better encapsulate + session data. + * Refactored session attribute and flash messages system to their own + `SessionBagInterface` implementations. + * Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This + implementation is ESI compatible. + * Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire + behaviour of messages auto expiring after one page page load. Messages must + be retrieved by `get()` or `all()`. + * Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate + attributes storage behaviour from 2.0.x (default). + * Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for + namespace session attributes. + * Flash API can stores messages in an array so there may be multiple messages + per flash type. The old `Session` class API remains without BC break as it + will allow single messages as before. + * Added basic session meta-data to the session to record session create time, + last updated time, and the lifetime of the session cookie that was provided + to the client. + * Request::getClientIp() method doesn't take a parameter anymore but bases + itself on the trustProxy parameter. + * Added isMethod() to Request object. + * [BC BREAK] The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of + a `Request` now all return a raw value (vs a urldecoded value before). Any call + to one of these methods must be checked and wrapped in a `rawurldecode()` if + needed. diff --git a/vendor/symfony/http-foundation/Cookie.php b/vendor/symfony/http-foundation/Cookie.php new file mode 100644 index 00000000..a2139ff6 --- /dev/null +++ b/vendor/symfony/http-foundation/Cookie.php @@ -0,0 +1,291 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents a cookie. + * + * @author Johannes M. Schmitt + */ +class Cookie +{ + protected $name; + protected $value; + protected $domain; + protected $expire; + protected $path; + protected $secure; + protected $httpOnly; + private $raw; + private $sameSite; + + const SAMESITE_LAX = 'lax'; + const SAMESITE_STRICT = 'strict'; + + /** + * Creates cookie from raw header string. + * + * @param string $cookie + * @param bool $decode + * + * @return static + */ + public static function fromString($cookie, $decode = false) + { + $data = array( + 'expires' => 0, + 'path' => '/', + 'domain' => null, + 'secure' => false, + 'httponly' => false, + 'raw' => !$decode, + 'samesite' => null, + ); + foreach (explode(';', $cookie) as $part) { + if (false === strpos($part, '=')) { + $key = trim($part); + $value = true; + } else { + list($key, $value) = explode('=', trim($part), 2); + $key = trim($key); + $value = trim($value); + } + if (!isset($data['name'])) { + $data['name'] = $decode ? urldecode($key) : $key; + $data['value'] = true === $value ? null : ($decode ? urldecode($value) : $value); + continue; + } + switch ($key = strtolower($key)) { + case 'name': + case 'value': + break; + case 'max-age': + $data['expires'] = time() + (int) $value; + break; + default: + $data[$key] = $value; + break; + } + } + + return new static($data['name'], $data['value'], $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']); + } + + /** + * Constructor. + * + * @param string $name The name of the cookie + * @param string|null $value The value of the cookie + * @param int|string|\DateTimeInterface $expire The time the cookie expires + * @param string $path The path on the server in which the cookie will be available on + * @param string|null $domain The domain that the cookie is available to + * @param bool $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client + * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol + * @param bool $raw Whether the cookie value should be sent with no url encoding + * @param string|null $sameSite Whether the cookie will be available for cross-site requests + * + * @throws \InvalidArgumentException + */ + public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null) + { + // from PHP source code + if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); + } + + if (empty($name)) { + throw new \InvalidArgumentException('The cookie name cannot be empty.'); + } + + // convert expiration time to a Unix timestamp + if ($expire instanceof \DateTimeInterface) { + $expire = $expire->format('U'); + } elseif (!is_numeric($expire)) { + $expire = strtotime($expire); + + if (false === $expire) { + throw new \InvalidArgumentException('The cookie expiration time is not valid.'); + } + } + + $this->name = $name; + $this->value = $value; + $this->domain = $domain; + $this->expire = 0 < $expire ? (int) $expire : 0; + $this->path = empty($path) ? '/' : $path; + $this->secure = (bool) $secure; + $this->httpOnly = (bool) $httpOnly; + $this->raw = (bool) $raw; + + if (null !== $sameSite) { + $sameSite = strtolower($sameSite); + } + + if (!in_array($sameSite, array(self::SAMESITE_LAX, self::SAMESITE_STRICT, null), true)) { + throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.'); + } + + $this->sameSite = $sameSite; + } + + /** + * Returns the cookie as a string. + * + * @return string The cookie + */ + public function __toString() + { + $str = ($this->isRaw() ? $this->getName() : urlencode($this->getName())).'='; + + if ('' === (string) $this->getValue()) { + $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001'; + } else { + $str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue()); + + if (0 !== $this->getExpiresTime()) { + $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; max-age='.$this->getMaxAge(); + } + } + + if ($this->getPath()) { + $str .= '; path='.$this->getPath(); + } + + if ($this->getDomain()) { + $str .= '; domain='.$this->getDomain(); + } + + if (true === $this->isSecure()) { + $str .= '; secure'; + } + + if (true === $this->isHttpOnly()) { + $str .= '; httponly'; + } + + if (null !== $this->getSameSite()) { + $str .= '; samesite='.$this->getSameSite(); + } + + return $str; + } + + /** + * Gets the name of the cookie. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the cookie. + * + * @return string|null + */ + public function getValue() + { + return $this->value; + } + + /** + * Gets the domain that the cookie is available to. + * + * @return string|null + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Gets the time the cookie expires. + * + * @return int + */ + public function getExpiresTime() + { + return $this->expire; + } + + /** + * Gets the max-age attribute. + * + * @return int + */ + public function getMaxAge() + { + return 0 !== $this->expire ? $this->expire - time() : 0; + } + + /** + * Gets the path on the server in which the cookie will be available on. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client. + * + * @return bool + */ + public function isSecure() + { + return $this->secure; + } + + /** + * Checks whether the cookie will be made accessible only through the HTTP protocol. + * + * @return bool + */ + public function isHttpOnly() + { + return $this->httpOnly; + } + + /** + * Whether this cookie is about to be cleared. + * + * @return bool + */ + public function isCleared() + { + return $this->expire < time(); + } + + /** + * Checks if the cookie value should be sent with no url encoding. + * + * @return bool + */ + public function isRaw() + { + return $this->raw; + } + + /** + * Gets the SameSite attribute. + * + * @return string|null + */ + public function getSameSite() + { + return $this->sameSite; + } +} diff --git a/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php new file mode 100644 index 00000000..5fcf5b42 --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * The HTTP request contains headers with conflicting information. + * + * @author Magnus Nordlander + */ +class ConflictingHeadersException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php b/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php new file mode 100644 index 00000000..478d0dc7 --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Interface for Request exceptions. + * + * Exceptions implementing this interface should trigger an HTTP 400 response in the application code. + */ +interface RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php b/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php new file mode 100644 index 00000000..ae7a5f13 --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Raised when a user has performed an operation that should be considered + * suspicious from a security perspective. + */ +class SuspiciousOperationException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/ExpressionRequestMatcher.php b/vendor/symfony/http-foundation/ExpressionRequestMatcher.php new file mode 100644 index 00000000..e9c8441c --- /dev/null +++ b/vendor/symfony/http-foundation/ExpressionRequestMatcher.php @@ -0,0 +1,47 @@ + + * + * 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\ExpressionLanguage\ExpressionLanguage; + +/** + * ExpressionRequestMatcher uses an expression to match a Request. + * + * @author Fabien Potencier + */ +class ExpressionRequestMatcher extends RequestMatcher +{ + private $language; + private $expression; + + public function setExpression(ExpressionLanguage $language, $expression) + { + $this->language = $language; + $this->expression = $expression; + } + + public function matches(Request $request) + { + if (!$this->language) { + throw new \LogicException('Unable to match the request as the expression language is not available.'); + } + + return $this->language->evaluate($this->expression, array( + 'request' => $request, + 'method' => $request->getMethod(), + 'path' => rawurldecode($request->getPathInfo()), + 'host' => $request->getHost(), + 'ip' => $request->getClientIp(), + 'attributes' => $request->attributes->all(), + )) && parent::matches($request); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php b/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php new file mode 100644 index 00000000..41f7a462 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php @@ -0,0 +1,30 @@ + + * + * 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 the access on a file was denied. + * + * @author Bernhard Schussek + */ +class AccessDeniedException extends FileException +{ + /** + * Constructor. + * + * @param string $path The path to the accessed file + */ + public function __construct($path) + { + parent::__construct(sprintf('The file %s could not be accessed', $path)); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/FileException.php b/vendor/symfony/http-foundation/File/Exception/FileException.php new file mode 100644 index 00000000..fad5133e --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/FileException.php @@ -0,0 +1,21 @@ + + * + * 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 error occurred in the component File. + * + * @author Bernhard Schussek + */ +class FileException extends \RuntimeException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php b/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php new file mode 100644 index 00000000..ac90d403 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php @@ -0,0 +1,30 @@ + + * + * 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 a file was not found. + * + * @author Bernhard Schussek + */ +class FileNotFoundException extends FileException +{ + /** + * Constructor. + * + * @param string $path The path to the file that was not found + */ + public function __construct($path) + { + parent::__construct(sprintf('The file "%s" does not exist', $path)); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php b/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php new file mode 100644 index 00000000..0444b877 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php @@ -0,0 +1,20 @@ + + * + * 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; + +class UnexpectedTypeException extends FileException +{ + public function __construct($value, $expectedType) + { + parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, is_object($value) ? get_class($value) : gettype($value))); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/UploadException.php b/vendor/symfony/http-foundation/File/Exception/UploadException.php new file mode 100644 index 00000000..7074e765 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/UploadException.php @@ -0,0 +1,21 @@ + + * + * 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 error occurred during file upload. + * + * @author Bernhard Schussek + */ +class UploadException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/File.php b/vendor/symfony/http-foundation/File/File.php new file mode 100644 index 00000000..e2a67684 --- /dev/null +++ b/vendor/symfony/http-foundation/File/File.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; +use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser; + +/** + * A file in the file system. + * + * @author Bernhard Schussek + */ +class File extends \SplFileInfo +{ + /** + * Constructs a new file from the given path. + * + * @param string $path The path to the file + * @param bool $checkPath Whether to check the path or not + * + * @throws FileNotFoundException If the given path is not a file + */ + public function __construct($path, $checkPath = true) + { + if ($checkPath && !is_file($path)) { + throw new FileNotFoundException($path); + } + + parent::__construct($path); + } + + /** + * Returns the extension based on the mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getMimeType() + * to guess the file extension. + * + * @return string|null The guessed extension or null if it cannot be guessed + * + * @see ExtensionGuesser + * @see getMimeType() + */ + public function guessExtension() + { + $type = $this->getMimeType(); + $guesser = ExtensionGuesser::getInstance(); + + return $guesser->guess($type); + } + + /** + * Returns the mime type of the file. + * + * The mime type is guessed using a MimeTypeGuesser instance, which uses finfo(), + * mime_content_type() and the system binary "file" (in this order), depending on + * which of those are available. + * + * @return string|null The guessed mime type (e.g. "application/pdf") + * + * @see MimeTypeGuesser + */ + public function getMimeType() + { + $guesser = MimeTypeGuesser::getInstance(); + + return $guesser->guess($this->getPathname()); + } + + /** + * Moves the file to a new location. + * + * @param string $directory The destination folder + * @param string $name The new file name + * + * @return self A File object representing the new file + * + * @throws FileException if the target file could not be created + */ + public function move($directory, $name = null) + { + $target = $this->getTargetFile($directory, $name); + + if (!@rename($this->getPathname(), $target)) { + $error = error_get_last(); + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message']))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + protected function getTargetFile($directory, $name = null) + { + if (!is_dir($directory)) { + if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) { + throw new FileException(sprintf('Unable to create the "%s" directory', $directory)); + } + } elseif (!is_writable($directory)) { + throw new FileException(sprintf('Unable to write in the "%s" directory', $directory)); + } + + $target = rtrim($directory, '/\\').DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name)); + + return new self($target, false); + } + + /** + * Returns locale independent base name of the given path. + * + * @param string $name The new file name + * + * @return string containing + */ + protected function getName($name) + { + $originalName = str_replace('\\', '/', $name); + $pos = strrpos($originalName, '/'); + $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1); + + return $originalName; + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php new file mode 100644 index 00000000..921751f6 --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +/** + * A singleton mime type to file extension guesser. + * + * A default guesser is provided. + * You can register custom guessers by calling the register() + * method on the singleton instance: + * + * $guesser = ExtensionGuesser::getInstance(); + * $guesser->register(new MyCustomExtensionGuesser()); + * + * The last registered guesser is preferred over previously registered ones. + */ +class ExtensionGuesser implements ExtensionGuesserInterface +{ + /** + * The singleton instance. + * + * @var ExtensionGuesser + */ + private static $instance = null; + + /** + * All registered ExtensionGuesserInterface instances. + * + * @var array + */ + protected $guessers = array(); + + /** + * Returns the singleton instance. + * + * @return self + */ + public static function getInstance() + { + if (null === self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Registers all natively provided extension guessers. + */ + private function __construct() + { + $this->register(new MimeTypeExtensionGuesser()); + } + + /** + * Registers a new extension guesser. + * + * When guessing, this guesser is preferred over previously registered ones. + * + * @param ExtensionGuesserInterface $guesser + */ + public function register(ExtensionGuesserInterface $guesser) + { + array_unshift($this->guessers, $guesser); + } + + /** + * Tries to guess the extension. + * + * The mime type 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. + * + * @param string $mimeType The mime type + * + * @return string The guessed extension or NULL, if none could be guessed + */ + public function guess($mimeType) + { + foreach ($this->guessers as $guesser) { + if (null !== $extension = $guesser->guess($mimeType)) { + return $extension; + } + } + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php new file mode 100644 index 00000000..d19a0e53 --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +/** + * Guesses the file extension corresponding to a given mime type. + */ +interface ExtensionGuesserInterface +{ + /** + * Makes a best guess for a file extension, given a mime type. + * + * @param string $mimeType The mime type + * + * @return string The guessed extension or NULL, if none could be guessed + */ + public function guess($mimeType); +} diff --git a/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php b/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php new file mode 100644 index 00000000..f917a06d --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; + +/** + * Guesses the mime type with the binary "file" (only available on *nix). + * + * @author Bernhard Schussek + */ +class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface +{ + private $cmd; + + /** + * Constructor. + * + * 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($cmd = 'file -b --mime %s 2>/dev/null') + { + $this->cmd = $cmd; + } + + /** + * Returns whether this guesser is supported on the current OS. + * + * @return bool + */ + public static function isSupported() + { + return '\\' !== DIRECTORY_SEPARATOR && function_exists('passthru') && function_exists('escapeshellarg'); + } + + /** + * {@inheritdoc} + */ + public function guess($path) + { + if (!is_file($path)) { + throw new FileNotFoundException($path); + } + + if (!is_readable($path)) { + throw new AccessDeniedException($path); + } + + if (!self::isSupported()) { + return; + } + + ob_start(); + + // need to use --mime instead of -i. see #6641 + passthru(sprintf($this->cmd, escapeshellarg($path)), $return); + if ($return > 0) { + ob_end_clean(); + + return; + } + + $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; + } + + return $match[1]; + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php b/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php new file mode 100644 index 00000000..6fee9479 --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; + +/** + * Guesses the mime type using the PECL extension FileInfo. + * + * @author Bernhard Schussek + */ +class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface +{ + private $magicFile; + + /** + * Constructor. + * + * @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($magicFile = null) + { + $this->magicFile = $magicFile; + } + + /** + * Returns whether this guesser is supported on the current OS/PHP setup. + * + * @return bool + */ + public static function isSupported() + { + return function_exists('finfo_open'); + } + + /** + * {@inheritdoc} + */ + public function guess($path) + { + if (!is_file($path)) { + throw new FileNotFoundException($path); + } + + if (!is_readable($path)) { + throw new AccessDeniedException($path); + } + + if (!self::isSupported()) { + return; + } + + if (!$finfo = new \finfo(FILEINFO_MIME_TYPE, $this->magicFile)) { + return; + } + + return $finfo->file($path); + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php b/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php new file mode 100644 index 00000000..e327f834 --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php @@ -0,0 +1,809 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +/** + * Provides a best-guess mapping of mime type to file extension. + */ +class MimeTypeExtensionGuesser implements ExtensionGuesserInterface +{ + /** + * A map of mime types and their default extensions. + * + * This list has been placed under the public domain by the Apache HTTPD project. + * This list has been updated from upstream on 2013-04-23. + * + * @see http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types + * + * @var array + */ + protected $defaultExtensions = array( + 'application/andrew-inset' => 'ez', + '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/cu-seeme' => 'cu', + 'application/davmount+xml' => 'davmount', + 'application/docbook+xml' => 'dbk', + 'application/dssc+der' => 'dssc', + 'application/dssc+xml' => 'xdssc', + 'application/ecmascript' => 'ecma', + 'application/emma+xml' => 'emma', + 'application/epub+zip' => 'epub', + 'application/exi' => 'exi', + 'application/font-tdpfr' => 'pfr', + 'application/gml+xml' => 'gml', + 'application/gpx+xml' => 'gpx', + 'application/gxf' => 'gxf', + 'application/hyperstudio' => 'stk', + 'application/inkml+xml' => 'ink', + 'application/ipfix' => 'ipfix', + 'application/java-archive' => 'jar', + 'application/java-serialized-object' => 'ser', + 'application/java-vm' => 'class', + 'application/javascript' => 'js', + 'application/json' => 'json', + 'application/jsonml+json' => 'jsonml', + 'application/lost+xml' => 'lostxml', + 'application/mac-binhex40' => 'hqx', + 'application/mac-compactpro' => 'cpt', + 'application/mads+xml' => 'mads', + 'application/marc' => 'mrc', + 'application/marcxml+xml' => 'mrcx', + 'application/mathematica' => 'ma', + 'application/mathml+xml' => 'mathml', + 'application/mbox' => 'mbox', + 'application/mediaservercontrol+xml' => 'mscml', + 'application/metalink+xml' => 'metalink', + 'application/metalink4+xml' => 'meta4', + 'application/mets+xml' => 'mets', + 'application/mods+xml' => 'mods', + 'application/mp21' => 'm21', + 'application/mp4' => 'mp4s', + 'application/msword' => 'doc', + 'application/mxf' => 'mxf', + 'application/octet-stream' => 'bin', + 'application/oda' => 'oda', + 'application/oebps-package+xml' => 'opf', + 'application/ogg' => 'ogx', + 'application/omdoc+xml' => 'omdoc', + 'application/onenote' => 'onetoc', + 'application/oxps' => 'oxps', + 'application/patch-ops-error+xml' => 'xer', + 'application/pdf' => 'pdf', + 'application/pgp-encrypted' => 'pgp', + 'application/pgp-signature' => 'asc', + 'application/pics-rules' => 'prf', + 'application/pkcs10' => 'p10', + 'application/pkcs7-mime' => 'p7m', + 'application/pkcs7-signature' => 'p7s', + 'application/pkcs8' => 'p8', + 'application/pkix-attr-cert' => 'ac', + 'application/pkix-cert' => 'cer', + 'application/pkix-crl' => 'crl', + 'application/pkix-pkipath' => 'pkipath', + 'application/pkixcmp' => 'pki', + 'application/pls+xml' => 'pls', + 'application/postscript' => 'ai', + 'application/prs.cww' => 'cww', + 'application/pskc+xml' => 'pskcxml', + 'application/rdf+xml' => 'rdf', + '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/smil+xml' => 'smi', + 'application/sparql-query' => 'rq', + 'application/sparql-results+xml' => 'srx', + 'application/srgs' => 'gram', + 'application/srgs+xml' => 'grxml', + 'application/sru+xml' => 'sru', + 'application/ssdl+xml' => 'ssdl', + 'application/ssml+xml' => 'ssml', + 'application/tei+xml' => 'tei', + 'application/thraud+xml' => 'tfi', + 'application/timestamped-data' => 'tsd', + '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', + 'application/vnd.adobe.air-application-installer-package+zip' => 'air', + 'application/vnd.adobe.formscentral.fcdt' => 'fcdt', + 'application/vnd.adobe.fxp' => 'fxp', + '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.apple.installer+xml' => 'mpkg', + 'application/vnd.apple.mpegurl' => 'm3u8', + '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.chipnuts.karaoke-mmd' => 'mmd', + 'application/vnd.cinderella' => 'cdy', + 'application/vnd.claymore' => 'cla', + 'application/vnd.cloanto.rp9' => 'rp9', + 'application/vnd.clonk.c4group' => 'c4g', + 'application/vnd.cluetrust.cartomobile-config' => 'c11amc', + 'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz', + 'application/vnd.commonspace' => 'csp', + 'application/vnd.contact.cmsg' => 'cdbcmsg', + '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.dece.data' => 'uvf', + 'application/vnd.dece.ttml+xml' => 'uvt', + 'application/vnd.dece.unspecified' => 'uvx', + 'application/vnd.dece.zip' => 'uvz', + '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.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', + 'application/vnd.ezpix-album' => 'ez2', + 'application/vnd.ezpix-package' => 'ez3', + 'application/vnd.fdf' => 'fdf', + 'application/vnd.fdsn.mseed' => 'mseed', + 'application/vnd.fdsn.seed' => 'seed', + 'application/vnd.flographit' => 'gph', + 'application/vnd.fluxtime.clip' => 'ftc', + 'application/vnd.framemaker' => 'fm', + '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.geogebra.file' => 'ggb', + 'application/vnd.geogebra.tool' => 'ggt', + 'application/vnd.geometry-explorer' => 'gex', + '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', + '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.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', + 'application/vnd.ibm.rights-management' => 'irm', + 'application/vnd.ibm.secure-container' => 'sc', + 'application/vnd.iccprofile' => 'icc', + 'application/vnd.igloader' => 'igl', + 'application/vnd.immervision-ivp' => 'ivp', + 'application/vnd.immervision-ivu' => 'ivu', + 'application/vnd.insors.igm' => 'igm', + 'application/vnd.intercon.formnet' => 'xpw', + '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', + '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', + 'application/vnd.kde.kspread' => 'ksp', + 'application/vnd.kde.kword' => 'kwd', + 'application/vnd.kenameaapp' => 'htke', + 'application/vnd.kidspiration' => 'kia', + 'application/vnd.kinar' => 'kne', + 'application/vnd.koan' => 'skp', + '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', + '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-artgalry' => 'cil', + 'application/vnd.ms-cab-compressed' => 'cab', + 'application/vnd.ms-excel' => 'xls', + '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', + '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', + 'application/vnd.ms-word.document.macroenabled.12' => 'docm', + 'application/vnd.ms-word.template.macroenabled.12' => 'dotm', + 'application/vnd.ms-works' => 'wps', + 'application/vnd.ms-wpl' => 'wpl', + 'application/vnd.ms-xpsdocument' => 'xps', + 'application/vnd.mseq' => 'mseq', + 'application/vnd.musician' => 'mus', + 'application/vnd.muvee.style' => 'msty', + 'application/vnd.mynfc' => 'taglet', + 'application/vnd.neurolanguage.nlu' => 'nlu', + 'application/vnd.nitf' => 'ntf', + '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.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', + 'application/vnd.oasis.opendocument.graphics' => 'odg', + '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-template' => 'otp', + 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', + 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots', + 'application/vnd.oasis.opendocument.text' => 'odt', + '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', + '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', + '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', + 'application/vnd.rn-realmedia-vbr' => 'rmvb', + 'application/vnd.route66.link66+xml' => 'link66', + 'application/vnd.sailingtracker.track' => 'st', + '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', + 'application/vnd.smaf' => 'mmf', + 'application/vnd.smart.teacher' => 'teacher', + 'application/vnd.solent.sdkm+xml' => 'sdkm', + 'application/vnd.spotfire.dxp' => 'dxp', + 'application/vnd.spotfire.sfs' => 'sfs', + 'application/vnd.stardivision.calc' => 'sdc', + 'application/vnd.stardivision.draw' => 'sda', + 'application/vnd.stardivision.impress' => 'sdd', + 'application/vnd.stardivision.math' => 'smf', + 'application/vnd.stardivision.writer' => 'sdw', + 'application/vnd.stardivision.writer-global' => 'sgl', + 'application/vnd.stepmania.package' => 'smzip', + 'application/vnd.stepmania.stepchart' => 'sm', + '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', + 'application/vnd.svd' => 'svd', + 'application/vnd.symbian.install' => 'sis', + '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', + 'application/vnd.tmobile-livetv' => 'tmo', + 'application/vnd.trid.tpt' => 'tpt', + 'application/vnd.triscape.mxs' => 'mxs', + 'application/vnd.trueapp' => 'tra', + 'application/vnd.ufdl' => 'ufd', + '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', + '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', + 'application/vnd.wqd' => 'wqd', + 'application/vnd.wt.stf' => 'stf', + 'application/vnd.xara' => 'xar', + '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.zul' => 'zir', + 'application/vnd.zzazz.deck+xml' => 'zaz', + 'application/voicexml+xml' => 'vxml', + 'application/widget' => 'wgt', + 'application/winhlp' => 'hlp', + 'application/wsdl+xml' => 'wsdl', + 'application/wspolicy+xml' => 'wspolicy', + 'application/x-7z-compressed' => '7z', + 'application/x-abiword' => 'abw', + 'application/x-ace-compressed' => 'ace', + 'application/x-apple-diskimage' => 'dmg', + 'application/x-authorware-bin' => 'aab', + 'application/x-authorware-map' => 'aam', + 'application/x-authorware-seg' => 'aas', + 'application/x-bcpio' => 'bcpio', + 'application/x-bittorrent' => 'torrent', + 'application/x-blorb' => 'blb', + 'application/x-bzip' => 'bz', + 'application/x-bzip2' => 'bz2', + 'application/x-cbr' => 'cbr', + 'application/x-cdlink' => 'vcd', + 'application/x-cfs-compressed' => 'cfs', + 'application/x-chat' => 'chat', + 'application/x-chess-pgn' => 'pgn', + 'application/x-conference' => 'nsc', + 'application/x-cpio' => 'cpio', + 'application/x-csh' => 'csh', + 'application/x-debian-package' => 'deb', + 'application/x-dgc-compressed' => 'dgc', + 'application/x-director' => 'dir', + 'application/x-doom' => 'wad', + 'application/x-dtbncx+xml' => 'ncx', + 'application/x-dtbook+xml' => 'dtb', + 'application/x-dtbresource+xml' => 'res', + 'application/x-dvi' => 'dvi', + 'application/x-envoy' => 'evy', + 'application/x-eva' => 'eva', + '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', + 'application/x-font-snf' => 'snf', + 'application/x-font-ttf' => 'ttf', + 'application/x-font-type1' => 'pfa', + 'application/x-font-woff' => 'woff', + 'application/x-freearc' => 'arc', + 'application/x-futuresplash' => 'spl', + 'application/x-gca-compressed' => 'gca', + 'application/x-glulx' => 'ulx', + 'application/x-gnumeric' => 'gnumeric', + 'application/x-gramps-xml' => 'gramps', + 'application/x-gtar' => 'gtar', + 'application/x-hdf' => 'hdf', + 'application/x-install-instructions' => 'install', + 'application/x-iso9660-image' => 'iso', + 'application/x-java-jnlp-file' => 'jnlp', + 'application/x-latex' => 'latex', + 'application/x-lzh-compressed' => 'lzh', + 'application/x-mie' => 'mie', + 'application/x-mobipocket-ebook' => 'prc', + 'application/x-ms-application' => 'application', + 'application/x-ms-shortcut' => 'lnk', + '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', + 'application/x-msmediaview' => 'mvb', + 'application/x-msmetafile' => 'wmf', + 'application/x-msmoney' => 'mny', + 'application/x-mspublisher' => 'pub', + 'application/x-msschedule' => 'scd', + 'application/x-msterminal' => 'trm', + 'application/x-mswrite' => 'wri', + 'application/x-netcdf' => 'nc', + 'application/x-nzb' => 'nzb', + 'application/x-pkcs12' => 'p12', + 'application/x-pkcs7-certificates' => 'p7b', + 'application/x-pkcs7-certreqresp' => 'p7r', + 'application/x-rar-compressed' => 'rar', + 'application/x-rar' => 'rar', + 'application/x-research-info-systems' => 'ris', + 'application/x-sh' => 'sh', + 'application/x-shar' => 'shar', + 'application/x-shockwave-flash' => 'swf', + 'application/x-silverlight-app' => 'xap', + 'application/x-sql' => 'sql', + '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-tads' => 'gam', + 'application/x-tar' => 'tar', + 'application/x-tcl' => 'tcl', + 'application/x-tex' => 'tex', + 'application/x-tex-tfm' => 'tfm', + 'application/x-texinfo' => 'texinfo', + 'application/x-tgif' => 'obj', + 'application/x-ustar' => 'ustar', + 'application/x-wais-source' => 'src', + 'application/x-x509-ca-cert' => 'der', + 'application/x-xfig' => 'fig', + 'application/x-xliff+xml' => 'xlf', + 'application/x-xpinstall' => 'xpi', + 'application/x-xz' => 'xz', + 'application/x-zmachine' => 'z1', + 'application/xaml+xml' => 'xaml', + 'application/xcap-diff+xml' => 'xdf', + 'application/xenc+xml' => 'xenc', + 'application/xhtml+xml' => 'xhtml', + 'application/xml' => 'xml', + 'application/xml-dtd' => 'dtd', + 'application/xop+xml' => 'xop', + 'application/xproc+xml' => 'xpl', + 'application/xslt+xml' => 'xslt', + 'application/xspf+xml' => 'xspf', + 'application/xv+xml' => 'mxml', + 'application/yang' => 'yang', + 'application/yin+xml' => 'yin', + 'application/zip' => 'zip', + 'audio/adpcm' => 'adp', + 'audio/basic' => 'au', + 'audio/midi' => 'mid', + 'audio/mp4' => 'mp4a', + 'audio/mpeg' => 'mpga', + 'audio/ogg' => 'oga', + 'audio/s3m' => 's3m', + 'audio/silk' => 'sil', + 'audio/vnd.dece.audio' => 'uva', + '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.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/webm' => 'weba', + 'audio/x-aac' => 'aac', + 'audio/x-aiff' => 'aif', + 'audio/x-caf' => 'caf', + 'audio/x-flac' => 'flac', + 'audio/x-matroska' => 'mka', + 'audio/x-mpegurl' => 'm3u', + 'audio/x-ms-wax' => 'wax', + 'audio/x-ms-wma' => 'wma', + 'audio/x-pn-realaudio' => 'ram', + 'audio/x-pn-realaudio-plugin' => 'rmp', + 'audio/x-wav' => 'wav', + 'audio/xm' => 'xm', + 'chemical/x-cdx' => 'cdx', + 'chemical/x-cif' => 'cif', + 'chemical/x-cmdf' => 'cmdf', + 'chemical/x-cml' => 'cml', + 'chemical/x-csml' => 'csml', + 'chemical/x-xyz' => 'xyz', + 'image/bmp' => 'bmp', + 'image/x-ms-bmp' => 'bmp', + 'image/cgm' => 'cgm', + 'image/g3fax' => 'g3', + 'image/gif' => 'gif', + 'image/ief' => 'ief', + 'image/jpeg' => 'jpeg', + 'image/pjpeg' => 'jpeg', + 'image/ktx' => 'ktx', + 'image/png' => 'png', + 'image/prs.btif' => 'btif', + 'image/sgi' => 'sgi', + 'image/svg+xml' => 'svg', + 'image/tiff' => 'tiff', + 'image/vnd.adobe.photoshop' => 'psd', + 'image/vnd.dece.graphic' => 'uvi', + 'image/vnd.dvb.subtitle' => 'sub', + 'image/vnd.djvu' => 'djvu', + '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.ms-modi' => 'mdi', + 'image/vnd.ms-photo' => 'wdp', + 'image/vnd.net-fpx' => 'npx', + 'image/vnd.wap.wbmp' => 'wbmp', + 'image/vnd.xiff' => 'xif', + 'image/webp' => 'webp', + 'image/x-3ds' => '3ds', + 'image/x-cmu-raster' => 'ras', + 'image/x-cmx' => 'cmx', + 'image/x-freehand' => 'fh', + 'image/x-icon' => 'ico', + 'image/x-mrsid-image' => 'sid', + 'image/x-pcx' => 'pcx', + 'image/x-pict' => 'pic', + 'image/x-portable-anymap' => 'pnm', + 'image/x-portable-bitmap' => 'pbm', + 'image/x-portable-graymap' => 'pgm', + 'image/x-portable-pixmap' => 'ppm', + 'image/x-rgb' => 'rgb', + 'image/x-tga' => 'tga', + 'image/x-xbitmap' => 'xbm', + 'image/x-xpixmap' => 'xpm', + 'image/x-xwindowdump' => 'xwd', + 'message/rfc822' => 'eml', + 'model/iges' => 'igs', + 'model/mesh' => 'msh', + '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', + 'model/x3d+binary' => 'x3db', + 'model/x3d+vrml' => 'x3dv', + 'model/x3d+xml' => 'x3d', + 'text/cache-manifest' => 'appcache', + 'text/calendar' => 'ics', + 'text/css' => 'css', + 'text/csv' => 'csv', + 'text/html' => 'html', + 'text/n3' => 'n3', + 'text/plain' => 'txt', + 'text/prs.lines.tag' => 'dsc', + 'text/richtext' => 'rtx', + 'text/rtf' => 'rtf', + 'text/sgml' => 'sgml', + 'text/tab-separated-values' => 'tsv', + 'text/troff' => 't', + 'text/turtle' => 'ttl', + 'text/uri-list' => 'uri', + 'text/vcard' => 'vcard', + 'text/vnd.curl' => 'curl', + 'text/vnd.curl.dcurl' => 'dcurl', + 'text/vnd.curl.scurl' => 'scurl', + 'text/vnd.curl.mcurl' => 'mcurl', + 'text/vnd.dvb.subtitle' => 'sub', + 'text/vnd.fly' => 'fly', + 'text/vnd.fmi.flexstor' => 'flx', + 'text/vnd.graphviz' => 'gv', + 'text/vnd.in3d.3dml' => '3dml', + 'text/vnd.in3d.spot' => 'spot', + 'text/vnd.sun.j2me.app-descriptor' => 'jad', + 'text/vnd.wap.wml' => 'wml', + 'text/vnd.wap.wmlscript' => 'wmls', + 'text/vtt' => 'vtt', + 'text/x-asm' => 's', + 'text/x-c' => 'c', + 'text/x-fortran' => 'f', + 'text/x-pascal' => 'p', + 'text/x-java-source' => 'java', + 'text/x-opml' => 'opml', + 'text/x-nfo' => 'nfo', + 'text/x-setext' => 'etx', + 'text/x-sfv' => 'sfv', + 'text/x-uuencode' => 'uu', + 'text/x-vcalendar' => 'vcs', + 'text/x-vcard' => 'vcf', + 'video/3gpp' => '3gp', + 'video/3gpp2' => '3g2', + 'video/h261' => 'h261', + 'video/h263' => 'h263', + 'video/h264' => 'h264', + 'video/jpeg' => 'jpgv', + 'video/jpm' => 'jpm', + 'video/mj2' => 'mj2', + 'video/mp4' => 'mp4', + 'video/mpeg' => 'mpeg', + 'video/ogg' => 'ogv', + 'video/quicktime' => 'qt', + 'video/vnd.dece.hd' => 'uvh', + 'video/vnd.dece.mobile' => 'uvm', + 'video/vnd.dece.pd' => 'uvp', + 'video/vnd.dece.sd' => 'uvs', + 'video/vnd.dece.video' => 'uvv', + 'video/vnd.dvb.file' => 'dvb', + 'video/vnd.fvt' => 'fvt', + 'video/vnd.mpegurl' => 'mxu', + 'video/vnd.ms-playready.media.pyv' => 'pyv', + 'video/vnd.uvvu.mp4' => 'uvu', + 'video/vnd.vivo' => 'viv', + 'video/webm' => 'webm', + 'video/x-f4v' => 'f4v', + 'video/x-fli' => 'fli', + 'video/x-flv' => 'flv', + 'video/x-m4v' => 'm4v', + 'video/x-matroska' => 'mkv', + 'video/x-mng' => 'mng', + 'video/x-ms-asf' => 'asf', + 'video/x-ms-vob' => 'vob', + 'video/x-ms-wm' => 'wm', + 'video/x-ms-wmv' => 'wmv', + 'video/x-ms-wmx' => 'wmx', + 'video/x-ms-wvx' => 'wvx', + 'video/x-msvideo' => 'avi', + 'video/x-sgi-movie' => 'movie', + 'video/x-smv' => 'smv', + 'x-conference/x-cooltalk' => 'ice', + ); + + /** + * {@inheritdoc} + */ + public function guess($mimeType) + { + return isset($this->defaultExtensions[$mimeType]) ? $this->defaultExtensions[$mimeType] : null; + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php new file mode 100644 index 00000000..69c803b4 --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; + +/** + * A singleton mime type guesser. + * + * By default, all mime type guessers provided by the framework are installed + * (if available on the current OS/PHP setup). + * + * You can register custom guessers by calling the register() method on the + * singleton instance. Custom guessers are always called before any default ones. + * + * $guesser = MimeTypeGuesser::getInstance(); + * $guesser->register(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 = MimeTypeGuesser::getInstance(); + * $guesser->register(new FileinfoMimeTypeGuesser('/path/to/magic/file')); + * + * @author Bernhard Schussek + */ +class MimeTypeGuesser implements MimeTypeGuesserInterface +{ + /** + * The singleton instance. + * + * @var MimeTypeGuesser + */ + private static $instance = null; + + /** + * All registered MimeTypeGuesserInterface instances. + * + * @var array + */ + protected $guessers = array(); + + /** + * Returns the singleton instance. + * + * @return self + */ + public static function getInstance() + { + if (null === self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Resets the singleton instance. + */ + public static function reset() + { + self::$instance = null; + } + + /** + * Registers all natively provided mime type guessers. + */ + private function __construct() + { + if (FileBinaryMimeTypeGuesser::isSupported()) { + $this->register(new FileBinaryMimeTypeGuesser()); + } + + if (FileinfoMimeTypeGuesser::isSupported()) { + $this->register(new FileinfoMimeTypeGuesser()); + } + } + + /** + * Registers a new mime type guesser. + * + * When guessing, this guesser is preferred over previously registered ones. + * + * @param MimeTypeGuesserInterface $guesser + */ + public function register(MimeTypeGuesserInterface $guesser) + { + array_unshift($this->guessers, $guesser); + } + + /** + * Tries to guess the mime type of the given file. + * + * 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. + * + * @param string $path The path to the file + * + * @return string The mime type or NULL, if none could be guessed + * + * @throws \LogicException + * @throws FileNotFoundException + * @throws AccessDeniedException + */ + public function guess($path) + { + if (!is_file($path)) { + throw new FileNotFoundException($path); + } + + if (!is_readable($path)) { + throw new AccessDeniedException($path); + } + + if (!$this->guessers) { + $msg = 'Unable to guess the mime type as no guessers are available'; + if (!FileinfoMimeTypeGuesser::isSupported()) { + $msg .= ' (Did you enable the php_fileinfo extension?)'; + } + throw new \LogicException($msg); + } + + foreach ($this->guessers as $guesser) { + if (null !== $mimeType = $guesser->guess($path)) { + return $mimeType; + } + } + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php new file mode 100644 index 00000000..f8c3ad22 --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; + +/** + * Guesses the mime type of a file. + * + * @author Bernhard Schussek + */ +interface MimeTypeGuesserInterface +{ + /** + * Guesses the mime type of the file with the given path. + * + * @param string $path The path to the file + * + * @return string The mime type or NULL, if none could be guessed + * + * @throws FileNotFoundException If the file does not exist + * @throws AccessDeniedException If the file could not be read + */ + public function guess($path); +} diff --git a/vendor/symfony/http-foundation/File/Stream.php b/vendor/symfony/http-foundation/File/Stream.php new file mode 100644 index 00000000..69ae74c1 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Stream.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +/** + * A PHP stream of unknown size. + * + * @author Nicolas Grekas + */ +class Stream extends File +{ + /** + * {@inheritdoc} + */ + public function getSize() + { + return false; + } +} diff --git a/vendor/symfony/http-foundation/File/UploadedFile.php b/vendor/symfony/http-foundation/File/UploadedFile.php new file mode 100644 index 00000000..10837726 --- /dev/null +++ b/vendor/symfony/http-foundation/File/UploadedFile.php @@ -0,0 +1,293 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser; + +/** + * A file uploaded through a form. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + * @author Fabien Potencier + */ +class UploadedFile extends File +{ + /** + * Whether the test mode is activated. + * + * Local files are used in test mode hence the code should not enforce HTTP uploads. + * + * @var bool + */ + private $test = false; + + /** + * The original name of the uploaded file. + * + * @var string + */ + private $originalName; + + /** + * The mime type provided by the uploader. + * + * @var string + */ + private $mimeType; + + /** + * The file size provided by the uploader. + * + * @var int|null + */ + private $size; + + /** + * The UPLOAD_ERR_XXX constant provided by the uploader. + * + * @var int + */ + private $error; + + /** + * Accepts the information of the uploaded file as provided by the PHP global $_FILES. + * + * The file object is only created when the uploaded file is valid (i.e. when the + * isValid() method returns true). Otherwise the only methods that could be called + * on an UploadedFile instance are: + * + * * getClientOriginalName, + * * getClientMimeType, + * * isValid, + * * getError. + * + * Calling any other method on an non-valid instance will cause an unpredictable result. + * + * @param string $path The full temporary path to the file + * @param string $originalName The original file name + * @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream + * @param int|null $size The file size + * @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK + * @param bool $test Whether the test mode is active + * + * @throws FileException If file_uploads is disabled + * @throws FileNotFoundException If the file does not exist + */ + public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false) + { + $this->originalName = $this->getName($originalName); + $this->mimeType = $mimeType ?: 'application/octet-stream'; + $this->size = $size; + $this->error = $error ?: UPLOAD_ERR_OK; + $this->test = (bool) $test; + + parent::__construct($path, UPLOAD_ERR_OK === $this->error); + } + + /** + * Returns the original file name. + * + * It is extracted from the request from which the file has been uploaded. + * Then it should not be considered as a safe value. + * + * @return string|null The original name + */ + public function getClientOriginalName() + { + return $this->originalName; + } + + /** + * Returns the original file extension. + * + * It is extracted from the original file name that was uploaded. + * Then it should not be considered as a safe value. + * + * @return string The extension + */ + public function getClientOriginalExtension() + { + return pathinfo($this->originalName, PATHINFO_EXTENSION); + } + + /** + * Returns the file mime type. + * + * The client mime type is extracted from the request from which the file + * was uploaded, so it should not be considered as a safe value. + * + * For a trusted mime type, use getMimeType() instead (which guesses the mime + * type based on the file content). + * + * @return string|null The mime type + * + * @see getMimeType() + */ + public function getClientMimeType() + { + return $this->mimeType; + } + + /** + * Returns the extension based on the client mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getClientMimeType() + * to guess the file extension. As such, the extension returned + * by this method cannot be trusted. + * + * For a trusted extension, use guessExtension() instead (which guesses + * the extension based on the guessed mime type for the file). + * + * @return string|null The guessed extension or null if it cannot be guessed + * + * @see guessExtension() + * @see getClientMimeType() + */ + public function guessClientExtension() + { + $type = $this->getClientMimeType(); + $guesser = ExtensionGuesser::getInstance(); + + return $guesser->guess($type); + } + + /** + * Returns the file size. + * + * It is extracted from the request from which the file has been uploaded. + * Then it should not be considered as a safe value. + * + * @return int|null The file size + */ + public function getClientSize() + { + return $this->size; + } + + /** + * Returns the upload error. + * + * If the upload was successful, the constant UPLOAD_ERR_OK is returned. + * Otherwise one of the other UPLOAD_ERR_XXX constants is returned. + * + * @return int The upload error + */ + public function getError() + { + return $this->error; + } + + /** + * Returns whether the file was uploaded successfully. + * + * @return bool True if the file has been uploaded with HTTP and no error occurred + */ + public function isValid() + { + $isOk = $this->error === UPLOAD_ERR_OK; + + return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname()); + } + + /** + * Moves the file to a new location. + * + * @param string $directory The destination folder + * @param string $name The new file name + * + * @return File A File object representing the new file + * + * @throws FileException if, for any reason, the file could not have been moved + */ + public function move($directory, $name = null) + { + if ($this->isValid()) { + if ($this->test) { + return parent::move($directory, $name); + } + + $target = $this->getTargetFile($directory, $name); + + if (!@move_uploaded_file($this->getPathname(), $target)) { + $error = error_get_last(); + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message']))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + throw new FileException($this->getErrorMessage()); + } + + /** + * Returns the maximum size of an uploaded file as configured in php.ini. + * + * @return int The maximum size of an uploaded file in bytes + */ + public static function getMaxFilesize() + { + $iniMax = strtolower(ini_get('upload_max_filesize')); + + if ('' === $iniMax) { + return PHP_INT_MAX; + } + + $max = ltrim($iniMax, '+'); + if (0 === strpos($max, '0x')) { + $max = intval($max, 16); + } elseif (0 === strpos($max, '0')) { + $max = intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($iniMax, -1)) { + case 't': $max *= 1024; + case 'g': $max *= 1024; + case 'm': $max *= 1024; + case 'k': $max *= 1024; + } + + return $max; + } + + /** + * Returns an informative upload error message. + * + * @return string The error message regarding the specified error code + */ + public function getErrorMessage() + { + static $errors = array( + UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).', + UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.', + UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.', + UPLOAD_ERR_NO_FILE => 'No file was uploaded.', + UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.', + UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.', + UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.', + ); + + $errorCode = $this->error; + $maxFilesize = $errorCode === UPLOAD_ERR_INI_SIZE ? self::getMaxFilesize() / 1024 : 0; + $message = isset($errors[$errorCode]) ? $errors[$errorCode] : 'The file "%s" was not uploaded due to an unknown error.'; + + return sprintf($message, $this->getClientOriginalName(), $maxFilesize); + } +} diff --git a/vendor/symfony/http-foundation/FileBag.php b/vendor/symfony/http-foundation/FileBag.php new file mode 100644 index 00000000..e17a9057 --- /dev/null +++ b/vendor/symfony/http-foundation/FileBag.php @@ -0,0 +1,145 @@ + + * + * 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\HttpFoundation\File\UploadedFile; + +/** + * FileBag is a container for uploaded files. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + */ +class FileBag extends ParameterBag +{ + private static $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type'); + + /** + * Constructor. + * + * @param array $parameters An array of HTTP files + */ + public function __construct(array $parameters = array()) + { + $this->replace($parameters); + } + + /** + * {@inheritdoc} + */ + public function replace(array $files = array()) + { + $this->parameters = array(); + $this->add($files); + } + + /** + * {@inheritdoc} + */ + public function set($key, $value) + { + if (!is_array($value) && !$value instanceof UploadedFile) { + throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.'); + } + + parent::set($key, $this->convertFileInformation($value)); + } + + /** + * {@inheritdoc} + */ + public function add(array $files = array()) + { + foreach ($files as $key => $file) { + $this->set($key, $file); + } + } + + /** + * Converts uploaded files to UploadedFile instances. + * + * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information + * + * @return UploadedFile|UploadedFile[] A (multi-dimensional) array of UploadedFile instances + */ + protected function convertFileInformation($file) + { + if ($file instanceof UploadedFile) { + return $file; + } + + $file = $this->fixPhpFilesArray($file); + if (is_array($file)) { + $keys = array_keys($file); + sort($keys); + + if ($keys == self::$fileKeys) { + if (UPLOAD_ERR_NO_FILE == $file['error']) { + $file = null; + } else { + $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']); + } + } else { + $file = array_map(array($this, 'convertFileInformation'), $file); + } + } + + return $file; + } + + /** + * Fixes a malformed PHP $_FILES array. + * + * PHP has a bug that the format of the $_FILES array differs, depending on + * whether the uploaded file fields had normal field names or array-like + * field names ("normal" vs. "parent[child]"). + * + * This method fixes the array to look like the "normal" $_FILES array. + * + * It's safe to pass an already converted array, in which case this method + * just returns the original array unmodified. + * + * @param array $data + * + * @return array + */ + protected function fixPhpFilesArray($data) + { + if (!is_array($data)) { + return $data; + } + + $keys = array_keys($data); + sort($keys); + + if (self::$fileKeys != $keys || !isset($data['name']) || !is_array($data['name'])) { + return $data; + } + + $files = $data; + foreach (self::$fileKeys as $k) { + unset($files[$k]); + } + + foreach ($data['name'] as $key => $name) { + $files[$key] = $this->fixPhpFilesArray(array( + 'error' => $data['error'][$key], + 'name' => $name, + 'type' => $data['type'][$key], + 'tmp_name' => $data['tmp_name'][$key], + 'size' => $data['size'][$key], + )); + } + + return $files; + } +} diff --git a/vendor/symfony/http-foundation/HeaderBag.php b/vendor/symfony/http-foundation/HeaderBag.php new file mode 100644 index 00000000..3cc9e702 --- /dev/null +++ b/vendor/symfony/http-foundation/HeaderBag.php @@ -0,0 +1,325 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * HeaderBag is a container for HTTP headers. + * + * @author Fabien Potencier + */ +class HeaderBag implements \IteratorAggregate, \Countable +{ + protected $headers = array(); + protected $cacheControl = array(); + + /** + * Constructor. + * + * @param array $headers An array of HTTP headers + */ + public function __construct(array $headers = array()) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns the headers as a string. + * + * @return string The headers + */ + public function __toString() + { + if (!$headers = $this->all()) { + return ''; + } + + ksort($headers); + $max = max(array_map('strlen', array_keys($headers))) + 1; + $content = ''; + foreach ($headers as $name => $values) { + $name = implode('-', array_map('ucfirst', explode('-', $name))); + foreach ($values as $value) { + $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value); + } + } + + return $content; + } + + /** + * Returns the headers. + * + * @return array An array of headers + */ + public function all() + { + return $this->headers; + } + + /** + * Returns the parameter keys. + * + * @return array An array of parameter keys + */ + public function keys() + { + return array_keys($this->all()); + } + + /** + * Replaces the current HTTP headers by a new set. + * + * @param array $headers An array of HTTP headers + */ + public function replace(array $headers = array()) + { + $this->headers = array(); + $this->add($headers); + } + + /** + * Adds new headers the current HTTP headers set. + * + * @param array $headers An array of HTTP headers + */ + public function add(array $headers) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns a header value by name. + * + * @param string $key The header name + * @param mixed $default The default value + * @param bool $first Whether to return the first value or all header values + * + * @return string|array The first header value if $first is true, an array of values otherwise + */ + public function get($key, $default = null, $first = true) + { + $key = str_replace('_', '-', strtolower($key)); + $headers = $this->all(); + + if (!array_key_exists($key, $headers)) { + if (null === $default) { + return $first ? null : array(); + } + + return $first ? $default : array($default); + } + + if ($first) { + return count($headers[$key]) ? $headers[$key][0] : $default; + } + + return $headers[$key]; + } + + /** + * Sets a header by name. + * + * @param string $key The key + * @param string|array $values The value or an array of values + * @param bool $replace Whether to replace the actual value or not (true by default) + */ + public function set($key, $values, $replace = true) + { + $key = str_replace('_', '-', strtolower($key)); + + $values = array_values((array) $values); + + if (true === $replace || !isset($this->headers[$key])) { + $this->headers[$key] = $values; + } else { + $this->headers[$key] = array_merge($this->headers[$key], $values); + } + + if ('cache-control' === $key) { + $this->cacheControl = $this->parseCacheControl($values[0]); + } + } + + /** + * Returns true if the HTTP header is defined. + * + * @param string $key The HTTP header + * + * @return bool true if the parameter exists, false otherwise + */ + public function has($key) + { + return array_key_exists(str_replace('_', '-', strtolower($key)), $this->all()); + } + + /** + * Returns true if the given HTTP header contains the given value. + * + * @param string $key The HTTP header name + * @param string $value The HTTP value + * + * @return bool true if the value is contained in the header, false otherwise + */ + public function contains($key, $value) + { + return in_array($value, $this->get($key, null, false)); + } + + /** + * Removes a header. + * + * @param string $key The HTTP header name + */ + public function remove($key) + { + $key = str_replace('_', '-', strtolower($key)); + + unset($this->headers[$key]); + + if ('cache-control' === $key) { + $this->cacheControl = array(); + } + } + + /** + * Returns the HTTP header value converted to a date. + * + * @param string $key The parameter key + * @param \DateTime $default The default value + * + * @return null|\DateTime The parsed DateTime or the default value if the header does not exist + * + * @throws \RuntimeException When the HTTP header is not parseable + */ + public function getDate($key, \DateTime $default = null) + { + if (null === $value = $this->get($key)) { + return $default; + } + + if (false === $date = \DateTime::createFromFormat(DATE_RFC2822, $value)) { + throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value)); + } + + return $date; + } + + /** + * Adds a custom Cache-Control directive. + * + * @param string $key The Cache-Control directive name + * @param mixed $value The Cache-Control directive value + */ + public function addCacheControlDirective($key, $value = true) + { + $this->cacheControl[$key] = $value; + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns true if the Cache-Control directive is defined. + * + * @param string $key The Cache-Control directive + * + * @return bool true if the directive exists, false otherwise + */ + public function hasCacheControlDirective($key) + { + return array_key_exists($key, $this->cacheControl); + } + + /** + * Returns a Cache-Control directive value by name. + * + * @param string $key The directive name + * + * @return mixed|null The directive value if defined, null otherwise + */ + public function getCacheControlDirective($key) + { + return array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null; + } + + /** + * Removes a Cache-Control directive. + * + * @param string $key The Cache-Control directive + */ + public function removeCacheControlDirective($key) + { + unset($this->cacheControl[$key]); + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns an iterator for headers. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->headers); + } + + /** + * Returns the number of headers. + * + * @return int The number of headers + */ + public function count() + { + return count($this->headers); + } + + protected function getCacheControlHeader() + { + $parts = array(); + ksort($this->cacheControl); + foreach ($this->cacheControl as $key => $value) { + if (true === $value) { + $parts[] = $key; + } else { + if (preg_match('#[^a-zA-Z0-9._-]#', $value)) { + $value = '"'.$value.'"'; + } + + $parts[] = "$key=$value"; + } + } + + return implode(', ', $parts); + } + + /** + * Parses a Cache-Control HTTP header. + * + * @param string $header The value of the Cache-Control HTTP header + * + * @return array An array representing the attribute values + */ + protected function parseCacheControl($header) + { + $cacheControl = array(); + preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $cacheControl[strtolower($match[1])] = isset($match[3]) ? $match[3] : (isset($match[2]) ? $match[2] : true); + } + + return $cacheControl; + } +} diff --git a/vendor/symfony/http-foundation/IpUtils.php b/vendor/symfony/http-foundation/IpUtils.php new file mode 100644 index 00000000..eba603b1 --- /dev/null +++ b/vendor/symfony/http-foundation/IpUtils.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Http utility functions. + * + * @author Fabien Potencier + */ +class IpUtils +{ + private static $checkedIps = array(); + + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets. + * + * @param string $requestIp IP to check + * @param string|array $ips List of IPs or subnets (can be a string if only a single one) + * + * @return bool Whether the IP is valid + */ + public static function checkIp($requestIp, $ips) + { + if (!is_array($ips)) { + $ips = array($ips); + } + + $method = substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4'; + + foreach ($ips as $ip) { + if (self::$method($requestIp, $ip)) { + return true; + } + } + + return false; + } + + /** + * Compares two IPv4 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @param string $requestIp IPv4 address to check + * @param string $ip IPv4 address or subnet in CIDR notation + * + * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet + */ + public static function checkIp4($requestIp, $ip) + { + $cacheKey = $requestIp.'-'.$ip; + if (isset(self::$checkedIps[$cacheKey])) { + return self::$checkedIps[$cacheKey]; + } + + if (!filter_var($requestIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + return self::$checkedIps[$cacheKey] = false; + } + + if (false !== strpos($ip, '/')) { + list($address, $netmask) = explode('/', $ip, 2); + + if ($netmask === '0') { + return self::$checkedIps[$cacheKey] = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); + } + + if ($netmask < 0 || $netmask > 32) { + return self::$checkedIps[$cacheKey] = false; + } + } else { + $address = $ip; + $netmask = 32; + } + + return self::$checkedIps[$cacheKey] = 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask); + } + + /** + * Compares two IPv6 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @author David Soria Parra + * + * @see https://github.com/dsp/v6tools + * + * @param string $requestIp IPv6 address to check + * @param string $ip IPv6 address or subnet in CIDR notation + * + * @return bool Whether the IP is valid + * + * @throws \RuntimeException When IPV6 support is not enabled + */ + public static function checkIp6($requestIp, $ip) + { + $cacheKey = $requestIp.'-'.$ip; + if (isset(self::$checkedIps[$cacheKey])) { + return self::$checkedIps[$cacheKey]; + } + + if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1'))) { + throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); + } + + if (false !== strpos($ip, '/')) { + list($address, $netmask) = explode('/', $ip, 2); + + if ($netmask < 1 || $netmask > 128) { + return self::$checkedIps[$cacheKey] = false; + } + } else { + $address = $ip; + $netmask = 128; + } + + $bytesAddr = unpack('n*', @inet_pton($address)); + $bytesTest = unpack('n*', @inet_pton($requestIp)); + + if (!$bytesAddr || !$bytesTest) { + return self::$checkedIps[$cacheKey] = false; + } + + for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) { + $left = $netmask - 16 * ($i - 1); + $left = ($left <= 16) ? $left : 16; + $mask = ~(0xffff >> $left) & 0xffff; + if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { + return self::$checkedIps[$cacheKey] = false; + } + } + + return self::$checkedIps[$cacheKey] = true; + } +} diff --git a/vendor/symfony/http-foundation/JsonResponse.php b/vendor/symfony/http-foundation/JsonResponse.php new file mode 100644 index 00000000..547a8e48 --- /dev/null +++ b/vendor/symfony/http-foundation/JsonResponse.php @@ -0,0 +1,220 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Response represents an HTTP response in JSON format. + * + * Note that this class does not force the returned JSON content to be an + * object. It is however recommended that you do return an object as it + * protects yourself against XSSI and JSON-JavaScript Hijacking. + * + * @see https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside + * + * @author Igor Wiedler + */ +class JsonResponse extends Response +{ + protected $data; + protected $callback; + + // Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML. + // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT + const DEFAULT_ENCODING_OPTIONS = 15; + + protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS; + + /** + * @param mixed $data The response data + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $json If the data is already a JSON string + */ + public function __construct($data = null, $status = 200, $headers = array(), $json = false) + { + parent::__construct('', $status, $headers); + + if (null === $data) { + $data = new \ArrayObject(); + } + + $json ? $this->setJson($data) : $this->setData($data); + } + + /** + * Factory method for chainability. + * + * Example: + * + * return JsonResponse::create($data, 200) + * ->setSharedMaxAge(300); + * + * @param mixed $data The json response data + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + */ + public static function create($data = null, $status = 200, $headers = array()) + { + return new static($data, $status, $headers); + } + + /** + * Make easier the creation of JsonResponse from raw json. + */ + public static function fromJsonString($data = null, $status = 200, $headers = array()) + { + return new static($data, $status, $headers, true); + } + + /** + * Sets the JSONP callback. + * + * @param string|null $callback The JSONP callback or null to use none + * + * @return $this + * + * @throws \InvalidArgumentException When the callback name is not valid + */ + public function setCallback($callback = null) + { + if (null !== $callback) { + // partially taken from http://www.geekality.net/2011/08/03/valid-javascript-identifier/ + // partially taken from https://github.com/willdurand/JsonpCallbackValidator + // JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details. + // (c) William Durand + $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u'; + $reserved = array( + 'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while', + 'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export', + 'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false', + ); + $parts = explode('.', $callback); + foreach ($parts as $part) { + if (!preg_match($pattern, $part) || in_array($part, $reserved, true)) { + throw new \InvalidArgumentException('The callback name is not valid.'); + } + } + } + + $this->callback = $callback; + + return $this->update(); + } + + /** + * Sets a raw string containing a JSON document to be sent. + * + * @param string $json + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setJson($json) + { + $this->data = $json; + + return $this->update(); + } + + /** + * Sets the data to be sent as JSON. + * + * @param mixed $data + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setData($data = array()) + { + if (defined('HHVM_VERSION')) { + // HHVM does not trigger any warnings and let exceptions + // thrown from a JsonSerializable object pass through. + // If only PHP did the same... + $data = json_encode($data, $this->encodingOptions); + } else { + if (!interface_exists('JsonSerializable', false)) { + set_error_handler(function () { return false; }); + try { + $data = @json_encode($data, $this->encodingOptions); + } finally { + restore_error_handler(); + } + } else { + try { + $data = json_encode($data, $this->encodingOptions); + } catch (\Exception $e) { + if ('Exception' === get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) { + throw $e->getPrevious() ?: $e; + } + throw $e; + } + } + } + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $this->setJson($data); + } + + /** + * Returns options used while encoding data to JSON. + * + * @return int + */ + public function getEncodingOptions() + { + return $this->encodingOptions; + } + + /** + * Sets options used while encoding data to JSON. + * + * @param int $encodingOptions + * + * @return $this + */ + public function setEncodingOptions($encodingOptions) + { + $this->encodingOptions = (int) $encodingOptions; + + return $this->setData(json_decode($this->data)); + } + + /** + * Updates the content and headers according to the JSON data and callback. + * + * @return $this + */ + protected function update() + { + if (null !== $this->callback) { + // Not using application/javascript for compatibility reasons with older browsers. + $this->headers->set('Content-Type', 'text/javascript'); + + return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data)); + } + + // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback) + // in order to not overwrite a custom definition. + if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) { + $this->headers->set('Content-Type', 'application/json'); + } + + return $this->setContent($this->data); + } +} diff --git a/vendor/symfony/http-foundation/LICENSE b/vendor/symfony/http-foundation/LICENSE new file mode 100644 index 00000000..17d16a13 --- /dev/null +++ b/vendor/symfony/http-foundation/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2017 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/http-foundation/ParameterBag.php b/vendor/symfony/http-foundation/ParameterBag.php new file mode 100644 index 00000000..c0b36479 --- /dev/null +++ b/vendor/symfony/http-foundation/ParameterBag.php @@ -0,0 +1,238 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ParameterBag is a container for key/value pairs. + * + * @author Fabien Potencier + */ +class ParameterBag implements \IteratorAggregate, \Countable +{ + /** + * Parameter storage. + * + * @var array + */ + protected $parameters; + + /** + * Constructor. + * + * @param array $parameters An array of parameters + */ + public function __construct(array $parameters = array()) + { + $this->parameters = $parameters; + } + + /** + * Returns the parameters. + * + * @return array An array of parameters + */ + public function all() + { + return $this->parameters; + } + + /** + * Returns the parameter keys. + * + * @return array An array of parameter keys + */ + public function keys() + { + return array_keys($this->parameters); + } + + /** + * Replaces the current parameters by a new set. + * + * @param array $parameters An array of parameters + */ + public function replace(array $parameters = array()) + { + $this->parameters = $parameters; + } + + /** + * Adds parameters. + * + * @param array $parameters An array of parameters + */ + public function add(array $parameters = array()) + { + $this->parameters = array_replace($this->parameters, $parameters); + } + + /** + * Returns a parameter by name. + * + * @param string $key The key + * @param mixed $default The default value if the parameter key does not exist + * + * @return mixed + */ + public function get($key, $default = null) + { + return array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default; + } + + /** + * Sets a parameter by name. + * + * @param string $key The key + * @param mixed $value The value + */ + public function set($key, $value) + { + $this->parameters[$key] = $value; + } + + /** + * Returns true if the parameter is defined. + * + * @param string $key The key + * + * @return bool true if the parameter exists, false otherwise + */ + public function has($key) + { + return array_key_exists($key, $this->parameters); + } + + /** + * Removes a parameter. + * + * @param string $key The key + */ + public function remove($key) + { + unset($this->parameters[$key]); + } + + /** + * Returns the alphabetic characters of the parameter value. + * + * @param string $key The parameter key + * @param string $default The default value if the parameter key does not exist + * + * @return string The filtered value + */ + public function getAlpha($key, $default = '') + { + return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default)); + } + + /** + * Returns the alphabetic characters and digits of the parameter value. + * + * @param string $key The parameter key + * @param string $default The default value if the parameter key does not exist + * + * @return string The filtered value + */ + public function getAlnum($key, $default = '') + { + return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default)); + } + + /** + * Returns the digits of the parameter value. + * + * @param string $key The parameter key + * @param string $default The default value if the parameter key does not exist + * + * @return string The filtered value + */ + public function getDigits($key, $default = '') + { + // we need to remove - and + because they're allowed in the filter + return str_replace(array('-', '+'), '', $this->filter($key, $default, FILTER_SANITIZE_NUMBER_INT)); + } + + /** + * Returns the parameter value converted to integer. + * + * @param string $key The parameter key + * @param int $default The default value if the parameter key does not exist + * + * @return int The filtered value + */ + public function getInt($key, $default = 0) + { + return (int) $this->get($key, $default); + } + + /** + * Returns the parameter value converted to boolean. + * + * @param string $key The parameter key + * @param mixed $default The default value if the parameter key does not exist + * + * @return bool The filtered value + */ + public function getBoolean($key, $default = false) + { + return $this->filter($key, $default, FILTER_VALIDATE_BOOLEAN); + } + + /** + * Filter key. + * + * @param string $key Key + * @param mixed $default Default = null + * @param int $filter FILTER_* constant + * @param mixed $options Filter options + * + * @see http://php.net/manual/en/function.filter-var.php + * + * @return mixed + */ + public function filter($key, $default = null, $filter = FILTER_DEFAULT, $options = array()) + { + $value = $this->get($key, $default); + + // Always turn $options into an array - this allows filter_var option shortcuts. + if (!is_array($options) && $options) { + $options = array('flags' => $options); + } + + // Add a convenience check for arrays. + if (is_array($value) && !isset($options['flags'])) { + $options['flags'] = FILTER_REQUIRE_ARRAY; + } + + return filter_var($value, $filter, $options); + } + + /** + * Returns an iterator for parameters. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->parameters); + } + + /** + * Returns the number of parameters. + * + * @return int The number of parameters + */ + public function count() + { + return count($this->parameters); + } +} diff --git a/vendor/symfony/http-foundation/README.md b/vendor/symfony/http-foundation/README.md new file mode 100644 index 00000000..8907f0b9 --- /dev/null +++ b/vendor/symfony/http-foundation/README.md @@ -0,0 +1,14 @@ +HttpFoundation Component +======================== + +The HttpFoundation component defines an object-oriented layer for the HTTP +specification. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/http_foundation/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/http-foundation/RedirectResponse.php b/vendor/symfony/http-foundation/RedirectResponse.php new file mode 100644 index 00000000..cb1c58e8 --- /dev/null +++ b/vendor/symfony/http-foundation/RedirectResponse.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RedirectResponse represents an HTTP response doing a redirect. + * + * @author Fabien Potencier + */ +class RedirectResponse extends Response +{ + protected $targetUrl; + + /** + * Creates a redirect response so that it conforms to the rules defined for a redirect status code. + * + * @param string $url The URL to redirect to. The URL should be a full URL, with schema etc., + * but practically every browser redirects on paths only as well + * @param int $status The status code (302 by default) + * @param array $headers The headers (Location is always set to the given URL) + * + * @throws \InvalidArgumentException + * + * @see http://tools.ietf.org/html/rfc2616#section-10.3 + */ + public function __construct($url, $status = 302, $headers = array()) + { + parent::__construct('', $status, $headers); + + $this->setTargetUrl($url); + + if (!$this->isRedirect()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status)); + } + + if (301 == $status && !array_key_exists('cache-control', $headers)) { + $this->headers->remove('cache-control'); + } + } + + /** + * {@inheritdoc} + */ + public static function create($url = '', $status = 302, $headers = array()) + { + return new static($url, $status, $headers); + } + + /** + * Returns the target URL. + * + * @return string target URL + */ + public function getTargetUrl() + { + return $this->targetUrl; + } + + /** + * Sets the redirect target of this response. + * + * @param string $url The URL to redirect to + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setTargetUrl($url) + { + if (empty($url)) { + throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); + } + + $this->targetUrl = $url; + + $this->setContent( + sprintf(' + + + + + + Redirecting to %1$s + + + Redirecting to %1$s. + +', htmlspecialchars($url, ENT_QUOTES, 'UTF-8'))); + + $this->headers->set('Location', $url); + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/Request.php b/vendor/symfony/http-foundation/Request.php new file mode 100644 index 00000000..6fd0707b --- /dev/null +++ b/vendor/symfony/http-foundation/Request.php @@ -0,0 +1,2114 @@ + + * + * 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\HttpFoundation\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Request represents an HTTP request. + * + * The methods dealing with URL accept / return a raw path (% encoded): + * * getBasePath + * * getBaseUrl + * * getPathInfo + * * getRequestUri + * * getUri + * * getUriForPath + * + * @author Fabien Potencier + */ +class Request +{ + const HEADER_FORWARDED = 0b00001; // When using RFC 7239 + const HEADER_X_FORWARDED_FOR = 0b00010; + const HEADER_X_FORWARDED_HOST = 0b00100; + const HEADER_X_FORWARDED_PROTO = 0b01000; + const HEADER_X_FORWARDED_PORT = 0b10000; + const HEADER_X_FORWARDED_ALL = 0b11110; // All "X-Forwarded-*" headers + const HEADER_X_FORWARDED_AWS_ELB = 0b11010; // AWS ELB doesn't send X-Forwarded-Host + + /** @deprecated since version 3.3, to be removed in 4.0 */ + const HEADER_CLIENT_IP = self::HEADER_X_FORWARDED_FOR; + /** @deprecated since version 3.3, to be removed in 4.0 */ + const HEADER_CLIENT_HOST = self::HEADER_X_FORWARDED_HOST; + /** @deprecated since version 3.3, to be removed in 4.0 */ + const HEADER_CLIENT_PROTO = self::HEADER_X_FORWARDED_PROTO; + /** @deprecated since version 3.3, to be removed in 4.0 */ + const HEADER_CLIENT_PORT = self::HEADER_X_FORWARDED_PORT; + + const METHOD_HEAD = 'HEAD'; + const METHOD_GET = 'GET'; + const METHOD_POST = 'POST'; + const METHOD_PUT = 'PUT'; + const METHOD_PATCH = 'PATCH'; + const METHOD_DELETE = 'DELETE'; + const METHOD_PURGE = 'PURGE'; + const METHOD_OPTIONS = 'OPTIONS'; + const METHOD_TRACE = 'TRACE'; + const METHOD_CONNECT = 'CONNECT'; + + /** + * @var string[] + */ + protected static $trustedProxies = array(); + + /** + * @var string[] + */ + protected static $trustedHostPatterns = array(); + + /** + * @var string[] + */ + protected static $trustedHosts = array(); + + /** + * Names for headers that can be trusted when + * using trusted proxies. + * + * The FORWARDED header is the standard as of rfc7239. + * + * The other headers are non-standard, but widely used + * by popular reverse proxies (like Apache mod_proxy or Amazon EC2). + * + * @deprecated since version 3.3, to be removed in 4.0 + */ + protected static $trustedHeaders = array( + self::HEADER_FORWARDED => 'FORWARDED', + self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', + self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', + self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', + self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', + ); + + protected static $httpMethodParameterOverride = false; + + /** + * Custom parameters. + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $attributes; + + /** + * Request body parameters ($_POST). + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $request; + + /** + * Query string parameters ($_GET). + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $query; + + /** + * Server and execution environment parameters ($_SERVER). + * + * @var \Symfony\Component\HttpFoundation\ServerBag + */ + public $server; + + /** + * Uploaded files ($_FILES). + * + * @var \Symfony\Component\HttpFoundation\FileBag + */ + public $files; + + /** + * Cookies ($_COOKIE). + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $cookies; + + /** + * Headers (taken from the $_SERVER). + * + * @var \Symfony\Component\HttpFoundation\HeaderBag + */ + public $headers; + + /** + * @var string + */ + protected $content; + + /** + * @var array + */ + protected $languages; + + /** + * @var array + */ + protected $charsets; + + /** + * @var array + */ + protected $encodings; + + /** + * @var array + */ + protected $acceptableContentTypes; + + /** + * @var string + */ + protected $pathInfo; + + /** + * @var string + */ + protected $requestUri; + + /** + * @var string + */ + protected $baseUrl; + + /** + * @var string + */ + protected $basePath; + + /** + * @var string + */ + protected $method; + + /** + * @var string + */ + protected $format; + + /** + * @var \Symfony\Component\HttpFoundation\Session\SessionInterface + */ + protected $session; + + /** + * @var string + */ + protected $locale; + + /** + * @var string + */ + protected $defaultLocale = 'en'; + + /** + * @var array + */ + protected static $formats; + + protected static $requestFactory; + + private $isHostValid = true; + private $isClientIpsValid = true; + private $isForwardedValid = true; + + private static $trustedHeaderSet = -1; + + /** @deprecated since version 3.3, to be removed in 4.0 */ + private static $trustedHeaderNames = array( + self::HEADER_FORWARDED => 'FORWARDED', + self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', + self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', + self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', + self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', + ); + + private static $forwardedParams = array( + self::HEADER_X_FORWARDED_FOR => 'for', + self::HEADER_X_FORWARDED_HOST => 'host', + self::HEADER_X_FORWARDED_PROTO => 'proto', + self::HEADER_X_FORWARDED_PORT => 'host', + ); + + /** + * Constructor. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource $content The raw body data + */ + public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Sets the parameters for this request. + * + * This method also re-initializes all properties. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource $content The raw body data + */ + public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + $this->request = new ParameterBag($request); + $this->query = new ParameterBag($query); + $this->attributes = new ParameterBag($attributes); + $this->cookies = new ParameterBag($cookies); + $this->files = new FileBag($files); + $this->server = new ServerBag($server); + $this->headers = new HeaderBag($this->server->getHeaders()); + + $this->content = $content; + $this->languages = null; + $this->charsets = null; + $this->encodings = null; + $this->acceptableContentTypes = null; + $this->pathInfo = null; + $this->requestUri = null; + $this->baseUrl = null; + $this->basePath = null; + $this->method = null; + $this->format = null; + } + + /** + * Creates a new request with values from PHP's super globals. + * + * @return static + */ + public static function createFromGlobals() + { + // With the php's bug #66606, the php's built-in web server + // stores the Content-Type and Content-Length header values in + // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields. + $server = $_SERVER; + if ('cli-server' === PHP_SAPI) { + if (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) { + $server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH']; + } + if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) { + $server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE']; + } + } + + $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server); + + if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded') + && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH')) + ) { + parse_str($request->getContent(), $data); + $request->request = new ParameterBag($data); + } + + return $request; + } + + /** + * Creates a Request based on a given URI and configuration. + * + * The information contained in the URI always take precedence + * over the other information (server and parameters). + * + * @param string $uri The URI + * @param string $method The HTTP method + * @param array $parameters The query (GET) or request (POST) parameters + * @param array $cookies The request cookies ($_COOKIE) + * @param array $files The request files ($_FILES) + * @param array $server The server parameters ($_SERVER) + * @param string $content The raw body data + * + * @return static + */ + public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null) + { + $server = array_replace(array( + 'SERVER_NAME' => 'localhost', + 'SERVER_PORT' => 80, + 'HTTP_HOST' => 'localhost', + 'HTTP_USER_AGENT' => 'Symfony/3.X', + 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'REMOTE_ADDR' => '127.0.0.1', + 'SCRIPT_NAME' => '', + 'SCRIPT_FILENAME' => '', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_TIME' => time(), + ), $server); + + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + + $components = parse_url($uri); + if (isset($components['host'])) { + $server['SERVER_NAME'] = $components['host']; + $server['HTTP_HOST'] = $components['host']; + } + + if (isset($components['scheme'])) { + if ('https' === $components['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + + if (isset($components['port'])) { + $server['SERVER_PORT'] = $components['port']; + $server['HTTP_HOST'] = $server['HTTP_HOST'].':'.$components['port']; + } + + if (isset($components['user'])) { + $server['PHP_AUTH_USER'] = $components['user']; + } + + if (isset($components['pass'])) { + $server['PHP_AUTH_PW'] = $components['pass']; + } + + if (!isset($components['path'])) { + $components['path'] = '/'; + } + + switch (strtoupper($method)) { + case 'POST': + case 'PUT': + case 'DELETE': + if (!isset($server['CONTENT_TYPE'])) { + $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + } + // no break + case 'PATCH': + $request = $parameters; + $query = array(); + break; + default: + $request = array(); + $query = $parameters; + break; + } + + $queryString = ''; + if (isset($components['query'])) { + parse_str(html_entity_decode($components['query']), $qs); + + if ($query) { + $query = array_replace($qs, $query); + $queryString = http_build_query($query, '', '&'); + } else { + $query = $qs; + $queryString = $components['query']; + } + } elseif ($query) { + $queryString = http_build_query($query, '', '&'); + } + + $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : ''); + $server['QUERY_STRING'] = $queryString; + + return self::createRequestFromFactory($query, $request, array(), $cookies, $files, $server, $content); + } + + /** + * Sets a callable able to create a Request instance. + * + * This is mainly useful when you need to override the Request class + * to keep BC with an existing system. It should not be used for any + * other purpose. + * + * @param callable|null $callable A PHP callable + */ + public static function setFactory($callable) + { + self::$requestFactory = $callable; + } + + /** + * Clones a request and overrides some of its parameters. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * + * @return static + */ + public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) + { + $dup = clone $this; + if ($query !== null) { + $dup->query = new ParameterBag($query); + } + if ($request !== null) { + $dup->request = new ParameterBag($request); + } + if ($attributes !== null) { + $dup->attributes = new ParameterBag($attributes); + } + if ($cookies !== null) { + $dup->cookies = new ParameterBag($cookies); + } + if ($files !== null) { + $dup->files = new FileBag($files); + } + if ($server !== null) { + $dup->server = new ServerBag($server); + $dup->headers = new HeaderBag($dup->server->getHeaders()); + } + $dup->languages = null; + $dup->charsets = null; + $dup->encodings = null; + $dup->acceptableContentTypes = null; + $dup->pathInfo = null; + $dup->requestUri = null; + $dup->baseUrl = null; + $dup->basePath = null; + $dup->method = null; + $dup->format = null; + + if (!$dup->get('_format') && $this->get('_format')) { + $dup->attributes->set('_format', $this->get('_format')); + } + + if (!$dup->getRequestFormat(null)) { + $dup->setRequestFormat($this->getRequestFormat(null)); + } + + return $dup; + } + + /** + * Clones the current request. + * + * Note that the session is not cloned as duplicated requests + * are most of the time sub-requests of the main one. + */ + public function __clone() + { + $this->query = clone $this->query; + $this->request = clone $this->request; + $this->attributes = clone $this->attributes; + $this->cookies = clone $this->cookies; + $this->files = clone $this->files; + $this->server = clone $this->server; + $this->headers = clone $this->headers; + } + + /** + * Returns the request as a string. + * + * @return string The request + */ + public function __toString() + { + try { + $content = $this->getContent(); + } catch (\LogicException $e) { + return trigger_error($e, E_USER_ERROR); + } + + return + sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n". + $this->headers."\r\n". + $content; + } + + /** + * Overrides the PHP global variables according to this request instance. + * + * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE. + * $_FILES is never overridden, see rfc1867 + */ + public function overrideGlobals() + { + $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), null, '&'))); + + $_GET = $this->query->all(); + $_POST = $this->request->all(); + $_SERVER = $this->server->all(); + $_COOKIE = $this->cookies->all(); + + foreach ($this->headers->all() as $key => $value) { + $key = strtoupper(str_replace('-', '_', $key)); + if (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) { + $_SERVER[$key] = implode(', ', $value); + } else { + $_SERVER['HTTP_'.$key] = implode(', ', $value); + } + } + + $request = array('g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE); + + $requestOrder = ini_get('request_order') ?: ini_get('variables_order'); + $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp'; + + $_REQUEST = array(); + foreach (str_split($requestOrder) as $order) { + $_REQUEST = array_merge($_REQUEST, $request[$order]); + } + } + + /** + * Sets a list of trusted proxies. + * + * You should only list the reverse proxies that you manage directly. + * + * @param array $proxies A list of trusted proxies + * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies + * + * @throws \InvalidArgumentException When $trustedHeaderSet is invalid + */ + public static function setTrustedProxies(array $proxies/*, int $trustedHeaderSet*/) + { + self::$trustedProxies = $proxies; + + if (2 > func_num_args()) { + @trigger_error(sprintf('The %s() method expects a bit field of Request::HEADER_* as second argument since version 3.3. Defining it will be required in 4.0. ', __METHOD__), E_USER_DEPRECATED); + + return; + } + $trustedHeaderSet = (int) func_get_arg(1); + + foreach (self::$trustedHeaderNames as $header => $name) { + self::$trustedHeaders[$header] = $header & $trustedHeaderSet ? $name : null; + } + self::$trustedHeaderSet = $trustedHeaderSet; + } + + /** + * Gets the list of trusted proxies. + * + * @return array An array of trusted proxies + */ + public static function getTrustedProxies() + { + return self::$trustedProxies; + } + + /** + * Gets the set of trusted headers from trusted proxies. + * + * @return int A bit field of Request::HEADER_* that defines which headers are trusted from your proxies + */ + public static function getTrustedHeaderSet() + { + return self::$trustedHeaderSet; + } + + /** + * Sets a list of trusted host patterns. + * + * You should only list the hosts you manage using regexs. + * + * @param array $hostPatterns A list of trusted host patterns + */ + public static function setTrustedHosts(array $hostPatterns) + { + self::$trustedHostPatterns = array_map(function ($hostPattern) { + return sprintf('#%s#i', $hostPattern); + }, $hostPatterns); + // we need to reset trusted hosts on trusted host patterns change + self::$trustedHosts = array(); + } + + /** + * Gets the list of trusted host patterns. + * + * @return array An array of trusted host patterns + */ + public static function getTrustedHosts() + { + return self::$trustedHostPatterns; + } + + /** + * Sets the name for trusted headers. + * + * The following header keys are supported: + * + * * Request::HEADER_CLIENT_IP: defaults to X-Forwarded-For (see getClientIp()) + * * Request::HEADER_CLIENT_HOST: defaults to X-Forwarded-Host (see getHost()) + * * Request::HEADER_CLIENT_PORT: defaults to X-Forwarded-Port (see getPort()) + * * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure()) + * * Request::HEADER_FORWARDED: defaults to Forwarded (see RFC 7239) + * + * Setting an empty value allows to disable the trusted header for the given key. + * + * @param string $key The header key + * @param string $value The header name + * + * @throws \InvalidArgumentException + * + * @deprecated since version 3.3, to be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead. + */ + public static function setTrustedHeaderName($key, $value) + { + @trigger_error(sprintf('The "%s()" method is deprecated since version 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.', __METHOD__), E_USER_DEPRECATED); + + if ('forwarded' === $key) { + $key = self::HEADER_FORWARDED; + } elseif ('client_ip' === $key) { + $key = self::HEADER_CLIENT_IP; + } elseif ('client_host' === $key) { + $key = self::HEADER_CLIENT_HOST; + } elseif ('client_proto' === $key) { + $key = self::HEADER_CLIENT_PROTO; + } elseif ('client_port' === $key) { + $key = self::HEADER_CLIENT_PORT; + } elseif (!array_key_exists($key, self::$trustedHeaders)) { + throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key)); + } + + self::$trustedHeaders[$key] = $value; + + if (null !== $value) { + self::$trustedHeaderNames[$key] = $value; + self::$trustedHeaderSet |= $key; + } else { + self::$trustedHeaderSet &= ~$key; + } + } + + /** + * Gets the trusted proxy header name. + * + * @param string $key The header key + * + * @return string The header name + * + * @throws \InvalidArgumentException + * + * @deprecated since version 3.3, to be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead. + */ + public static function getTrustedHeaderName($key) + { + if (2 > func_num_args() || func_get_arg(1)) { + @trigger_error(sprintf('The "%s()" method is deprecated since version 3.3 and will be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead.', __METHOD__), E_USER_DEPRECATED); + } + + if (!array_key_exists($key, self::$trustedHeaders)) { + throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key)); + } + + return self::$trustedHeaders[$key]; + } + + /** + * Normalizes a query string. + * + * It builds a normalized query string, where keys/value pairs are alphabetized, + * have consistent escaping and unneeded delimiters are removed. + * + * @param string $qs Query string + * + * @return string A normalized query string for the Request + */ + public static function normalizeQueryString($qs) + { + if ('' == $qs) { + return ''; + } + + $parts = array(); + $order = array(); + + foreach (explode('&', $qs) as $param) { + if ('' === $param || '=' === $param[0]) { + // Ignore useless delimiters, e.g. "x=y&". + // Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway. + // PHP also does not include them when building _GET. + continue; + } + + $keyValuePair = explode('=', $param, 2); + + // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded). + // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. This is why we use urldecode and then normalize to + // RFC 3986 with rawurlencode. + $parts[] = isset($keyValuePair[1]) ? + rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) : + rawurlencode(urldecode($keyValuePair[0])); + $order[] = urldecode($keyValuePair[0]); + } + + array_multisort($order, SORT_ASC, $parts); + + return implode('&', $parts); + } + + /** + * Enables support for the _method request parameter to determine the intended HTTP method. + * + * Be warned that enabling this feature might lead to CSRF issues in your code. + * Check that you are using CSRF tokens when required. + * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered + * and used to send a "PUT" or "DELETE" request via the _method request parameter. + * If these methods are not protected against CSRF, this presents a possible vulnerability. + * + * The HTTP method can only be overridden when the real HTTP method is POST. + */ + public static function enableHttpMethodParameterOverride() + { + self::$httpMethodParameterOverride = true; + } + + /** + * Checks whether support for the _method request parameter is enabled. + * + * @return bool True when the _method request parameter is enabled, false otherwise + */ + public static function getHttpMethodParameterOverride() + { + return self::$httpMethodParameterOverride; + } + + /** + * Gets a "parameter" value from any bag. + * + * This method is mainly useful for libraries that want to provide some flexibility. If you don't need the + * flexibility in controllers, it is better to explicitly get request parameters from the appropriate + * public property instead (attributes, query, request). + * + * Order of precedence: PATH (routing placeholders or custom attributes), GET, BODY + * + * @param string $key the key + * @param mixed $default the default value if the parameter key does not exist + * + * @return mixed + */ + public function get($key, $default = null) + { + if ($this !== $result = $this->attributes->get($key, $this)) { + return $result; + } + + if ($this !== $result = $this->query->get($key, $this)) { + return $result; + } + + if ($this !== $result = $this->request->get($key, $this)) { + return $result; + } + + return $default; + } + + /** + * Gets the Session. + * + * @return SessionInterface|null The session + */ + public function getSession() + { + return $this->session; + } + + /** + * Whether the request contains a Session which was started in one of the + * previous requests. + * + * @return bool + */ + public function hasPreviousSession() + { + // the check for $this->session avoids malicious users trying to fake a session cookie with proper name + return $this->hasSession() && $this->cookies->has($this->session->getName()); + } + + /** + * Whether the request contains a Session object. + * + * This method does not give any information about the state of the session object, + * like whether the session is started or not. It is just a way to check if this Request + * is associated with a Session instance. + * + * @return bool true when the Request contains a Session object, false otherwise + */ + public function hasSession() + { + return null !== $this->session; + } + + /** + * Sets the Session. + * + * @param SessionInterface $session The Session + */ + public function setSession(SessionInterface $session) + { + $this->session = $session; + } + + /** + * Returns the client IP addresses. + * + * In the returned array the most trusted IP address is first, and the + * least trusted one last. The "real" client IP address is the last one, + * but this is also the least trusted one. Trusted proxies are stripped. + * + * Use this method carefully; you should use getClientIp() instead. + * + * @return array The client IP addresses + * + * @see getClientIp() + */ + public function getClientIps() + { + $ip = $this->server->get('REMOTE_ADDR'); + + if (!$this->isFromTrustedProxy()) { + return array($ip); + } + + return $this->getTrustedValues(self::HEADER_CLIENT_IP, $ip) ?: array($ip); + } + + /** + * Returns the client IP address. + * + * This method can read the client IP address from the "X-Forwarded-For" header + * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For" + * header value is a comma+space separated list of IP addresses, the left-most + * being the original client, and each successive proxy that passed the request + * adding the IP address where it received the request from. + * + * If your reverse proxy uses a different header name than "X-Forwarded-For", + * ("Client-Ip" for instance), configure it via the $trustedHeaderSet + * argument of the Request::setTrustedProxies() method instead. + * + * @return string|null The client IP address + * + * @see getClientIps() + * @see http://en.wikipedia.org/wiki/X-Forwarded-For + */ + public function getClientIp() + { + $ipAddresses = $this->getClientIps(); + + return $ipAddresses[0]; + } + + /** + * Returns current script name. + * + * @return string + */ + public function getScriptName() + { + return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', '')); + } + + /** + * Returns the path being requested relative to the executed script. + * + * The path info always starts with a /. + * + * Suppose this request is instantiated from /mysite on localhost: + * + * * http://localhost/mysite returns an empty string + * * http://localhost/mysite/about returns '/about' + * * http://localhost/mysite/enco%20ded returns '/enco%20ded' + * * http://localhost/mysite/about?var=1 returns '/about' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getPathInfo() + { + if (null === $this->pathInfo) { + $this->pathInfo = $this->preparePathInfo(); + } + + return $this->pathInfo; + } + + /** + * Returns the root path from which this request is executed. + * + * Suppose that an index.php file instantiates this request object: + * + * * http://localhost/index.php returns an empty string + * * http://localhost/index.php/page returns an empty string + * * http://localhost/web/index.php returns '/web' + * * http://localhost/we%20b/index.php returns '/we%20b' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getBasePath() + { + if (null === $this->basePath) { + $this->basePath = $this->prepareBasePath(); + } + + return $this->basePath; + } + + /** + * Returns the root URL from which this request is executed. + * + * The base URL never ends with a /. + * + * This is similar to getBasePath(), except that it also includes the + * script filename (e.g. index.php) if one exists. + * + * @return string The raw URL (i.e. not urldecoded) + */ + public function getBaseUrl() + { + if (null === $this->baseUrl) { + $this->baseUrl = $this->prepareBaseUrl(); + } + + return $this->baseUrl; + } + + /** + * Gets the request's scheme. + * + * @return string + */ + public function getScheme() + { + return $this->isSecure() ? 'https' : 'http'; + } + + /** + * Returns the port on which the request is made. + * + * This method can read the client port from the "X-Forwarded-Port" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Port" header must contain the client port. + * + * If your reverse proxy uses a different header name than "X-Forwarded-Port", + * configure it via via the $trustedHeaderSet argument of the + * Request::setTrustedProxies() method instead. + * + * @return int|string can be a string if fetched from the server bag + */ + public function getPort() + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_PORT)) { + $host = $host[0]; + } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + return $this->server->get('SERVER_PORT'); + } + + if ($host[0] === '[') { + $pos = strpos($host, ':', strrpos($host, ']')); + } else { + $pos = strrpos($host, ':'); + } + + if (false !== $pos) { + return (int) substr($host, $pos + 1); + } + + return 'https' === $this->getScheme() ? 443 : 80; + } + + /** + * Returns the user. + * + * @return string|null + */ + public function getUser() + { + return $this->headers->get('PHP_AUTH_USER'); + } + + /** + * Returns the password. + * + * @return string|null + */ + public function getPassword() + { + return $this->headers->get('PHP_AUTH_PW'); + } + + /** + * Gets the user info. + * + * @return string A user name and, optionally, scheme-specific information about how to gain authorization to access the server + */ + public function getUserInfo() + { + $userinfo = $this->getUser(); + + $pass = $this->getPassword(); + if ('' != $pass) { + $userinfo .= ":$pass"; + } + + return $userinfo; + } + + /** + * Returns the HTTP host being requested. + * + * The port name will be appended to the host if it's non-standard. + * + * @return string + */ + public function getHttpHost() + { + $scheme = $this->getScheme(); + $port = $this->getPort(); + + if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) { + return $this->getHost(); + } + + return $this->getHost().':'.$port; + } + + /** + * Returns the requested URI (path and query string). + * + * @return string The raw URI (i.e. not URI decoded) + */ + public function getRequestUri() + { + if (null === $this->requestUri) { + $this->requestUri = $this->prepareRequestUri(); + } + + return $this->requestUri; + } + + /** + * Gets the scheme and HTTP host. + * + * If the URL was called with basic authentication, the user + * and the password are not added to the generated string. + * + * @return string The scheme and HTTP host + */ + public function getSchemeAndHttpHost() + { + return $this->getScheme().'://'.$this->getHttpHost(); + } + + /** + * Generates a normalized URI (URL) for the Request. + * + * @return string A normalized URI (URL) for the Request + * + * @see getQueryString() + */ + public function getUri() + { + if (null !== $qs = $this->getQueryString()) { + $qs = '?'.$qs; + } + + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs; + } + + /** + * Generates a normalized URI for the given path. + * + * @param string $path A path to use instead of the current one + * + * @return string The normalized URI for the path + */ + public function getUriForPath($path) + { + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path; + } + + /** + * Returns the path as relative reference from the current Request path. + * + * Only the URIs path component (no schema, host etc.) is relevant and must be given. + * Both paths must be absolute and not contain relative parts. + * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. + * Furthermore, they can be used to reduce the link size in documents. + * + * Example target paths, given a base path of "/a/b/c/d": + * - "/a/b/c/d" -> "" + * - "/a/b/c/" -> "./" + * - "/a/b/" -> "../" + * - "/a/b/c/other" -> "other" + * - "/a/x/y" -> "../../x/y" + * + * @param string $path The target path + * + * @return string The relative target path + */ + public function getRelativeUriForPath($path) + { + // be sure that we are dealing with an absolute path + if (!isset($path[0]) || '/' !== $path[0]) { + return $path; + } + + if ($path === $basePath = $this->getPathInfo()) { + return ''; + } + + $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); + $targetDirs = explode('/', isset($path[0]) && '/' === $path[0] ? substr($path, 1) : $path); + array_pop($sourceDirs); + $targetFile = array_pop($targetDirs); + + foreach ($sourceDirs as $i => $dir) { + if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { + unset($sourceDirs[$i], $targetDirs[$i]); + } else { + break; + } + } + + $targetDirs[] = $targetFile; + $path = str_repeat('../', count($sourceDirs)).implode('/', $targetDirs); + + // A reference to the same base directory or an empty subdirectory must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name + // (see http://tools.ietf.org/html/rfc3986#section-4.2). + return !isset($path[0]) || '/' === $path[0] + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } + + /** + * Generates the normalized query string for the Request. + * + * It builds a normalized query string, where keys/value pairs are alphabetized + * and have consistent escaping. + * + * @return string|null A normalized query string for the Request + */ + public function getQueryString() + { + $qs = static::normalizeQueryString($this->server->get('QUERY_STRING')); + + return '' === $qs ? null : $qs; + } + + /** + * Checks whether the request is secure or not. + * + * This method can read the client protocol from the "X-Forwarded-Proto" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http". + * + * If your reverse proxy uses a different header name than "X-Forwarded-Proto" + * ("SSL_HTTPS" for instance), configure it via the $trustedHeaderSet + * argument of the Request::setTrustedProxies() method instead. + * + * @return bool + */ + public function isSecure() + { + if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_CLIENT_PROTO)) { + return in_array(strtolower($proto[0]), array('https', 'on', 'ssl', '1'), true); + } + + $https = $this->server->get('HTTPS'); + + return !empty($https) && 'off' !== strtolower($https); + } + + /** + * Returns the host name. + * + * This method can read the client host name from the "X-Forwarded-Host" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Host" header must contain the client host name. + * + * If your reverse proxy uses a different header name than "X-Forwarded-Host", + * configure it via the $trustedHeaderSet argument of the + * Request::setTrustedProxies() method instead. + * + * @return string + * + * @throws SuspiciousOperationException when the host name is invalid or not trusted + */ + public function getHost() + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + if (!$host = $this->server->get('SERVER_NAME')) { + $host = $this->server->get('SERVER_ADDR', ''); + } + } + + // trim and remove port number from host + // host is lowercase as per RFC 952/2181 + $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); + + // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user) + // check that it does not contain forbidden characters (see RFC 952 and RFC 2181) + // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names + if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) { + if (!$this->isHostValid) { + return ''; + } + $this->isHostValid = false; + + throw new SuspiciousOperationException(sprintf('Invalid Host "%s".', $host)); + } + + if (count(self::$trustedHostPatterns) > 0) { + // to avoid host header injection attacks, you should provide a list of trusted host patterns + + if (in_array($host, self::$trustedHosts)) { + return $host; + } + + foreach (self::$trustedHostPatterns as $pattern) { + if (preg_match($pattern, $host)) { + self::$trustedHosts[] = $host; + + return $host; + } + } + + if (!$this->isHostValid) { + return ''; + } + $this->isHostValid = false; + + throw new SuspiciousOperationException(sprintf('Untrusted Host "%s".', $host)); + } + + return $host; + } + + /** + * Sets the request method. + * + * @param string $method + */ + public function setMethod($method) + { + $this->method = null; + $this->server->set('REQUEST_METHOD', $method); + } + + /** + * Gets the request "intended" method. + * + * If the X-HTTP-Method-Override header is set, and if the method is a POST, + * then it is used to determine the "real" intended HTTP method. + * + * The _method request parameter can also be used to determine the HTTP method, + * but only if enableHttpMethodParameterOverride() has been called. + * + * The method is always an uppercased string. + * + * @return string The request method + * + * @see getRealMethod() + */ + public function getMethod() + { + if (null === $this->method) { + $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + + if ('POST' === $this->method) { + if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) { + $this->method = strtoupper($method); + } elseif (self::$httpMethodParameterOverride) { + $this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST'))); + } + } + } + + return $this->method; + } + + /** + * Gets the "real" request method. + * + * @return string The request method + * + * @see getMethod() + */ + public function getRealMethod() + { + return strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + } + + /** + * Gets the mime type associated with the format. + * + * @param string $format The format + * + * @return string The associated mime type (null if not found) + */ + public function getMimeType($format) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return isset(static::$formats[$format]) ? static::$formats[$format][0] : null; + } + + /** + * Gets the mime types associated with the format. + * + * @param string $format The format + * + * @return array The associated mime types + */ + public static function getMimeTypes($format) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return isset(static::$formats[$format]) ? static::$formats[$format] : array(); + } + + /** + * Gets the format associated with the mime type. + * + * @param string $mimeType The associated mime type + * + * @return string|null The format (null if not found) + */ + public function getFormat($mimeType) + { + $canonicalMimeType = null; + if (false !== $pos = strpos($mimeType, ';')) { + $canonicalMimeType = substr($mimeType, 0, $pos); + } + + if (null === static::$formats) { + static::initializeFormats(); + } + + foreach (static::$formats as $format => $mimeTypes) { + if (in_array($mimeType, (array) $mimeTypes)) { + return $format; + } + if (null !== $canonicalMimeType && in_array($canonicalMimeType, (array) $mimeTypes)) { + return $format; + } + } + } + + /** + * Associates a format with mime types. + * + * @param string $format The format + * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) + */ + public function setFormat($format, $mimeTypes) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + static::$formats[$format] = is_array($mimeTypes) ? $mimeTypes : array($mimeTypes); + } + + /** + * Gets the request format. + * + * Here is the process to determine the format: + * + * * format defined by the user (with setRequestFormat()) + * * _format request attribute + * * $default + * + * @param string $default The default format + * + * @return string The request format + */ + public function getRequestFormat($default = 'html') + { + if (null === $this->format) { + $this->format = $this->attributes->get('_format'); + } + + return null === $this->format ? $default : $this->format; + } + + /** + * Sets the request format. + * + * @param string $format The request format + */ + public function setRequestFormat($format) + { + $this->format = $format; + } + + /** + * Gets the format associated with the request. + * + * @return string|null The format (null if no content type is present) + */ + public function getContentType() + { + return $this->getFormat($this->headers->get('CONTENT_TYPE')); + } + + /** + * Sets the default locale. + * + * @param string $locale + */ + public function setDefaultLocale($locale) + { + $this->defaultLocale = $locale; + + if (null === $this->locale) { + $this->setPhpDefaultLocale($locale); + } + } + + /** + * Get the default locale. + * + * @return string + */ + public function getDefaultLocale() + { + return $this->defaultLocale; + } + + /** + * Sets the locale. + * + * @param string $locale + */ + public function setLocale($locale) + { + $this->setPhpDefaultLocale($this->locale = $locale); + } + + /** + * Get the locale. + * + * @return string + */ + public function getLocale() + { + return null === $this->locale ? $this->defaultLocale : $this->locale; + } + + /** + * Checks if the request method is of specified type. + * + * @param string $method Uppercase request method (GET, POST etc) + * + * @return bool + */ + public function isMethod($method) + { + return $this->getMethod() === strtoupper($method); + } + + /** + * Checks whether or not the method is safe. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.1 + * + * @param bool $andCacheable Adds the additional condition that the method should be cacheable. True by default. + * + * @return bool + */ + public function isMethodSafe(/* $andCacheable = true */) + { + if (!func_num_args() || func_get_arg(0)) { + // This deprecation should be turned into a BadMethodCallException in 4.0 (without adding the argument in the signature) + // then setting $andCacheable to false should be deprecated in 4.1 + @trigger_error('Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is deprecated since version 3.2 and will throw an exception in 4.0. Disable checking only for cacheable methods by calling the method with `false` as first argument or use the Request::isMethodCacheable() instead.', E_USER_DEPRECATED); + + return in_array($this->getMethod(), array('GET', 'HEAD')); + } + + return in_array($this->getMethod(), array('GET', 'HEAD', 'OPTIONS', 'TRACE')); + } + + /** + * Checks whether or not the method is idempotent. + * + * @return bool + */ + public function isMethodIdempotent() + { + return in_array($this->getMethod(), array('HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE')); + } + + /** + * Checks whether the method is cacheable or not. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.3 + * + * @return bool + */ + public function isMethodCacheable() + { + return in_array($this->getMethod(), array('GET', 'HEAD')); + } + + /** + * Returns the request body content. + * + * @param bool $asResource If true, a resource will be returned + * + * @return string|resource The request body content or a resource to read the body stream + * + * @throws \LogicException + */ + public function getContent($asResource = false) + { + $currentContentIsResource = is_resource($this->content); + if (\PHP_VERSION_ID < 50600 && false === $this->content) { + throw new \LogicException('getContent() can only be called once when using the resource return type and PHP below 5.6.'); + } + + if (true === $asResource) { + if ($currentContentIsResource) { + rewind($this->content); + + return $this->content; + } + + // Content passed in parameter (test) + if (is_string($this->content)) { + $resource = fopen('php://temp', 'r+'); + fwrite($resource, $this->content); + rewind($resource); + + return $resource; + } + + $this->content = false; + + return fopen('php://input', 'rb'); + } + + if ($currentContentIsResource) { + rewind($this->content); + + return stream_get_contents($this->content); + } + + if (null === $this->content || false === $this->content) { + $this->content = file_get_contents('php://input'); + } + + return $this->content; + } + + /** + * Gets the Etags. + * + * @return array The entity tags + */ + public function getETags() + { + return preg_split('/\s*,\s*/', $this->headers->get('if_none_match'), null, PREG_SPLIT_NO_EMPTY); + } + + /** + * @return bool + */ + public function isNoCache() + { + return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); + } + + /** + * Returns the preferred language. + * + * @param array $locales An array of ordered available locales + * + * @return string|null The preferred locale + */ + public function getPreferredLanguage(array $locales = null) + { + $preferredLanguages = $this->getLanguages(); + + if (empty($locales)) { + return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null; + } + + if (!$preferredLanguages) { + return $locales[0]; + } + + $extendedPreferredLanguages = array(); + foreach ($preferredLanguages as $language) { + $extendedPreferredLanguages[] = $language; + if (false !== $position = strpos($language, '_')) { + $superLanguage = substr($language, 0, $position); + if (!in_array($superLanguage, $preferredLanguages)) { + $extendedPreferredLanguages[] = $superLanguage; + } + } + } + + $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales)); + + return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0]; + } + + /** + * Gets a list of languages acceptable by the client browser. + * + * @return array Languages ordered in the user browser preferences + */ + public function getLanguages() + { + if (null !== $this->languages) { + return $this->languages; + } + + $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all(); + $this->languages = array(); + foreach ($languages as $lang => $acceptHeaderItem) { + if (false !== strpos($lang, '-')) { + $codes = explode('-', $lang); + if ('i' === $codes[0]) { + // Language not listed in ISO 639 that are not variants + // of any listed language, which can be registered with the + // i-prefix, such as i-cherokee + if (count($codes) > 1) { + $lang = $codes[1]; + } + } else { + for ($i = 0, $max = count($codes); $i < $max; ++$i) { + if ($i === 0) { + $lang = strtolower($codes[0]); + } else { + $lang .= '_'.strtoupper($codes[$i]); + } + } + } + } + + $this->languages[] = $lang; + } + + return $this->languages; + } + + /** + * Gets a list of charsets acceptable by the client browser. + * + * @return array List of charsets in preferable order + */ + public function getCharsets() + { + if (null !== $this->charsets) { + return $this->charsets; + } + + return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all()); + } + + /** + * Gets a list of encodings acceptable by the client browser. + * + * @return array List of encodings in preferable order + */ + public function getEncodings() + { + if (null !== $this->encodings) { + return $this->encodings; + } + + return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all()); + } + + /** + * Gets a list of content types acceptable by the client browser. + * + * @return array List of content types in preferable order + */ + public function getAcceptableContentTypes() + { + if (null !== $this->acceptableContentTypes) { + return $this->acceptableContentTypes; + } + + return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all()); + } + + /** + * Returns true if the request is a XMLHttpRequest. + * + * It works if your JavaScript library sets an X-Requested-With HTTP header. + * It is known to work with common JavaScript frameworks: + * + * @see http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript + * + * @return bool true if the request is an XMLHttpRequest, false otherwise + */ + public function isXmlHttpRequest() + { + return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); + } + + /* + * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24) + * + * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + + protected function prepareRequestUri() + { + $requestUri = ''; + + if ($this->headers->has('X_ORIGINAL_URL')) { + // IIS with Microsoft Rewrite Module + $requestUri = $this->headers->get('X_ORIGINAL_URL'); + $this->headers->remove('X_ORIGINAL_URL'); + $this->server->remove('HTTP_X_ORIGINAL_URL'); + $this->server->remove('UNENCODED_URL'); + $this->server->remove('IIS_WasUrlRewritten'); + } elseif ($this->headers->has('X_REWRITE_URL')) { + // IIS with ISAPI_Rewrite + $requestUri = $this->headers->get('X_REWRITE_URL'); + $this->headers->remove('X_REWRITE_URL'); + } elseif ($this->server->get('IIS_WasUrlRewritten') == '1' && $this->server->get('UNENCODED_URL') != '') { + // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem) + $requestUri = $this->server->get('UNENCODED_URL'); + $this->server->remove('UNENCODED_URL'); + $this->server->remove('IIS_WasUrlRewritten'); + } elseif ($this->server->has('REQUEST_URI')) { + $requestUri = $this->server->get('REQUEST_URI'); + // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, only use URL path + $schemeAndHttpHost = $this->getSchemeAndHttpHost(); + if (strpos($requestUri, $schemeAndHttpHost) === 0) { + $requestUri = substr($requestUri, strlen($schemeAndHttpHost)); + } + } elseif ($this->server->has('ORIG_PATH_INFO')) { + // IIS 5.0, PHP as CGI + $requestUri = $this->server->get('ORIG_PATH_INFO'); + if ('' != $this->server->get('QUERY_STRING')) { + $requestUri .= '?'.$this->server->get('QUERY_STRING'); + } + $this->server->remove('ORIG_PATH_INFO'); + } + + // normalize the request URI to ease creating sub-requests from this request + $this->server->set('REQUEST_URI', $requestUri); + + return $requestUri; + } + + /** + * Prepares the base URL. + * + * @return string + */ + protected function prepareBaseUrl() + { + $filename = basename($this->server->get('SCRIPT_FILENAME')); + + if (basename($this->server->get('SCRIPT_NAME')) === $filename) { + $baseUrl = $this->server->get('SCRIPT_NAME'); + } elseif (basename($this->server->get('PHP_SELF')) === $filename) { + $baseUrl = $this->server->get('PHP_SELF'); + } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) { + $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility + } else { + // Backtrack up the script_filename to find the portion matching + // php_self + $path = $this->server->get('PHP_SELF', ''); + $file = $this->server->get('SCRIPT_FILENAME', ''); + $segs = explode('/', trim($file, '/')); + $segs = array_reverse($segs); + $index = 0; + $last = count($segs); + $baseUrl = ''; + do { + $seg = $segs[$index]; + $baseUrl = '/'.$seg.$baseUrl; + ++$index; + } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos); + } + + // Does the baseUrl have anything in common with the request_uri? + $requestUri = $this->getRequestUri(); + + if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) { + // full $baseUrl matches + return $prefix; + } + + if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(dirname($baseUrl), '/'.DIRECTORY_SEPARATOR).'/')) { + // directory portion of $baseUrl matches + return rtrim($prefix, '/'.DIRECTORY_SEPARATOR); + } + + $truncatedRequestUri = $requestUri; + if (false !== $pos = strpos($requestUri, '?')) { + $truncatedRequestUri = substr($requestUri, 0, $pos); + } + + $basename = basename($baseUrl); + if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) { + // no match whatsoever; set it blank + return ''; + } + + // If using mod_rewrite or ISAPI_Rewrite strip the script filename + // out of baseUrl. $pos !== 0 makes sure it is not matching a value + // from PATH_INFO or QUERY_STRING + if (strlen($requestUri) >= strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && $pos !== 0) { + $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl)); + } + + return rtrim($baseUrl, '/'.DIRECTORY_SEPARATOR); + } + + /** + * Prepares the base path. + * + * @return string base path + */ + protected function prepareBasePath() + { + $filename = basename($this->server->get('SCRIPT_FILENAME')); + $baseUrl = $this->getBaseUrl(); + if (empty($baseUrl)) { + return ''; + } + + if (basename($baseUrl) === $filename) { + $basePath = dirname($baseUrl); + } else { + $basePath = $baseUrl; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + $basePath = str_replace('\\', '/', $basePath); + } + + return rtrim($basePath, '/'); + } + + /** + * Prepares the path info. + * + * @return string path info + */ + protected function preparePathInfo() + { + $baseUrl = $this->getBaseUrl(); + + if (null === ($requestUri = $this->getRequestUri())) { + return '/'; + } + + // Remove the query string from REQUEST_URI + if ($pos = strpos($requestUri, '?')) { + $requestUri = substr($requestUri, 0, $pos); + } + + $pathInfo = substr($requestUri, strlen($baseUrl)); + if (null !== $baseUrl && (false === $pathInfo || '' === $pathInfo)) { + // If substr() returns false then PATH_INFO is set to an empty string + return '/'; + } elseif (null === $baseUrl) { + return $requestUri; + } + + return (string) $pathInfo; + } + + /** + * Initializes HTTP request formats. + */ + protected static function initializeFormats() + { + static::$formats = array( + 'html' => array('text/html', 'application/xhtml+xml'), + 'txt' => array('text/plain'), + 'js' => array('application/javascript', 'application/x-javascript', 'text/javascript'), + 'css' => array('text/css'), + 'json' => array('application/json', 'application/x-json'), + 'xml' => array('text/xml', 'application/xml', 'application/x-xml'), + 'rdf' => array('application/rdf+xml'), + 'atom' => array('application/atom+xml'), + 'rss' => array('application/rss+xml'), + 'form' => array('application/x-www-form-urlencoded'), + ); + } + + /** + * Sets the default PHP locale. + * + * @param string $locale + */ + private function setPhpDefaultLocale($locale) + { + // if either the class Locale doesn't exist, or an exception is thrown when + // setting the default locale, the intl module is not installed, and + // the call can be ignored: + try { + if (class_exists('Locale', false)) { + \Locale::setDefault($locale); + } + } catch (\Exception $e) { + } + } + + /* + * Returns the prefix as encoded in the string when the string starts with + * the given prefix, false otherwise. + * + * @param string $string The urlencoded string + * @param string $prefix The prefix not encoded + * + * @return string|false The prefix as it is encoded in $string, or false + */ + private function getUrlencodedPrefix($string, $prefix) + { + if (0 !== strpos(rawurldecode($string), $prefix)) { + return false; + } + + $len = strlen($prefix); + + if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) { + return $match[0]; + } + + return false; + } + + private static function createRequestFromFactory(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + if (self::$requestFactory) { + $request = call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content); + + if (!$request instanceof self) { + throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.'); + } + + return $request; + } + + return new static($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Indicates whether this request originated from a trusted proxy. + * + * This can be useful to determine whether or not to trust the + * contents of a proxy-specific header. + * + * @return bool true if the request came from a trusted proxy, false otherwise + */ + public function isFromTrustedProxy() + { + return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies); + } + + private function getTrustedValues($type, $ip = null) + { + $clientValues = array(); + $forwardedValues = array(); + + if (self::$trustedHeaders[$type] && $this->headers->has(self::$trustedHeaders[$type])) { + foreach (explode(',', $this->headers->get(self::$trustedHeaders[$type])) as $v) { + $clientValues[] = (self::HEADER_CLIENT_PORT === $type ? '0.0.0.0:' : '').trim($v); + } + } + + if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) { + $forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]); + $forwardedValues = preg_match_all(sprintf('{(?:%s)=(?:"?\[?)([a-zA-Z0-9\.:_\-/]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array(); + } + + if (null !== $ip) { + $clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip); + $forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip); + } + + if ($forwardedValues === $clientValues || !$clientValues) { + return $forwardedValues; + } + + if (!$forwardedValues) { + return $clientValues; + } + + if (!$this->isForwardedValid) { + return null !== $ip ? array('0.0.0.0', $ip) : array(); + } + $this->isForwardedValid = false; + + throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::$trustedHeaders[self::HEADER_FORWARDED], self::$trustedHeaders[$type])); + } + + private function normalizeAndFilterClientIps(array $clientIps, $ip) + { + if (!$clientIps) { + return array(); + } + $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from + $firstTrustedIp = null; + + foreach ($clientIps as $key => $clientIp) { + // Remove port (unfortunately, it does happen) + if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) { + $clientIps[$key] = $clientIp = $match[1]; + } + + if (!filter_var($clientIp, FILTER_VALIDATE_IP)) { + unset($clientIps[$key]); + + continue; + } + + if (IpUtils::checkIp($clientIp, self::$trustedProxies)) { + unset($clientIps[$key]); + + // Fallback to this when the client IP falls into the range of trusted proxies + if (null === $firstTrustedIp) { + $firstTrustedIp = $clientIp; + } + } + } + + // Now the IP chain contains only untrusted proxies and the client IP + return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp); + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher.php new file mode 100644 index 00000000..aa4f67b5 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcher.php @@ -0,0 +1,178 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RequestMatcher compares a pre-defined set of checks against a Request instance. + * + * @author Fabien Potencier + */ +class RequestMatcher implements RequestMatcherInterface +{ + /** + * @var string|null + */ + private $path; + + /** + * @var string|null + */ + private $host; + + /** + * @var string[] + */ + private $methods = array(); + + /** + * @var string[] + */ + private $ips = array(); + + /** + * @var array + */ + private $attributes = array(); + + /** + * @var string[] + */ + private $schemes = array(); + + /** + * @param string|null $path + * @param string|null $host + * @param string|string[]|null $methods + * @param string|string[]|null $ips + * @param array $attributes + * @param string|string[]|null $schemes + */ + public function __construct($path = null, $host = null, $methods = null, $ips = null, array $attributes = array(), $schemes = null) + { + $this->matchPath($path); + $this->matchHost($host); + $this->matchMethod($methods); + $this->matchIps($ips); + $this->matchScheme($schemes); + + foreach ($attributes as $k => $v) { + $this->matchAttribute($k, $v); + } + } + + /** + * Adds a check for the HTTP scheme. + * + * @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes + */ + public function matchScheme($scheme) + { + $this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : array(); + } + + /** + * Adds a check for the URL host name. + * + * @param string|null $regexp A Regexp + */ + public function matchHost($regexp) + { + $this->host = $regexp; + } + + /** + * Adds a check for the URL path info. + * + * @param string|null $regexp A Regexp + */ + public function matchPath($regexp) + { + $this->path = $regexp; + } + + /** + * Adds a check for the client IP. + * + * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + */ + public function matchIp($ip) + { + $this->matchIps($ip); + } + + /** + * Adds a check for the client IP. + * + * @param string|string[]|null $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + */ + public function matchIps($ips) + { + $this->ips = null !== $ips ? (array) $ips : array(); + } + + /** + * Adds a check for the HTTP method. + * + * @param string|string[]|null $method An HTTP method or an array of HTTP methods + */ + public function matchMethod($method) + { + $this->methods = null !== $method ? array_map('strtoupper', (array) $method) : array(); + } + + /** + * Adds a check for request attribute. + * + * @param string $key The request attribute name + * @param string $regexp A Regexp + */ + public function matchAttribute($key, $regexp) + { + $this->attributes[$key] = $regexp; + } + + /** + * {@inheritdoc} + */ + public function matches(Request $request) + { + if ($this->schemes && !in_array($request->getScheme(), $this->schemes, true)) { + return false; + } + + if ($this->methods && !in_array($request->getMethod(), $this->methods, true)) { + return false; + } + + foreach ($this->attributes as $key => $pattern) { + if (!preg_match('{'.$pattern.'}', $request->attributes->get($key))) { + return false; + } + } + + if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getPathInfo()))) { + return false; + } + + if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getHost())) { + return false; + } + + if (IpUtils::checkIp($request->getClientIp(), $this->ips)) { + return true; + } + + // Note to future implementors: add additional checks above the + // foreach above or else your check might not be run! + return count($this->ips) === 0; + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcherInterface.php b/vendor/symfony/http-foundation/RequestMatcherInterface.php new file mode 100644 index 00000000..066e7e8b --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcherInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RequestMatcherInterface is an interface for strategies to match a Request. + * + * @author Fabien Potencier + */ +interface RequestMatcherInterface +{ + /** + * Decides whether the rule(s) implemented by the strategy matches the supplied request. + * + * @param Request $request The request to check for a match + * + * @return bool true if the request matches, false otherwise + */ + public function matches(Request $request); +} diff --git a/vendor/symfony/http-foundation/RequestStack.php b/vendor/symfony/http-foundation/RequestStack.php new file mode 100644 index 00000000..3d9cfd0c --- /dev/null +++ b/vendor/symfony/http-foundation/RequestStack.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Request stack that controls the lifecycle of requests. + * + * @author Benjamin Eberlei + */ +class RequestStack +{ + /** + * @var Request[] + */ + private $requests = array(); + + /** + * Pushes a Request on the stack. + * + * This method should generally not be called directly as the stack + * management should be taken care of by the application itself. + */ + public function push(Request $request) + { + $this->requests[] = $request; + } + + /** + * Pops the current request from the stack. + * + * This operation lets the current request go out of scope. + * + * This method should generally not be called directly as the stack + * management should be taken care of by the application itself. + * + * @return Request|null + */ + public function pop() + { + if (!$this->requests) { + return; + } + + return array_pop($this->requests); + } + + /** + * @return Request|null + */ + public function getCurrentRequest() + { + return end($this->requests) ?: null; + } + + /** + * Gets the master Request. + * + * Be warned that making your code aware of the master request + * might make it un-compatible with other features of your framework + * like ESI support. + * + * @return Request|null + */ + public function getMasterRequest() + { + if (!$this->requests) { + return; + } + + return $this->requests[0]; + } + + /** + * Returns the parent request of the current. + * + * Be warned that making your code aware of the parent request + * might make it un-compatible with other features of your framework + * like ESI support. + * + * If current Request is the master request, it returns null. + * + * @return Request|null + */ + public function getParentRequest() + { + $pos = count($this->requests) - 2; + + if (!isset($this->requests[$pos])) { + return; + } + + return $this->requests[$pos]; + } +} diff --git a/vendor/symfony/http-foundation/Response.php b/vendor/symfony/http-foundation/Response.php new file mode 100644 index 00000000..4af1e0ba --- /dev/null +++ b/vendor/symfony/http-foundation/Response.php @@ -0,0 +1,1288 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Response represents an HTTP response. + * + * @author Fabien Potencier + */ +class Response +{ + const HTTP_CONTINUE = 100; + const HTTP_SWITCHING_PROTOCOLS = 101; + const HTTP_PROCESSING = 102; // RFC2518 + const HTTP_OK = 200; + const HTTP_CREATED = 201; + const HTTP_ACCEPTED = 202; + const HTTP_NON_AUTHORITATIVE_INFORMATION = 203; + const HTTP_NO_CONTENT = 204; + const HTTP_RESET_CONTENT = 205; + const HTTP_PARTIAL_CONTENT = 206; + const HTTP_MULTI_STATUS = 207; // RFC4918 + const HTTP_ALREADY_REPORTED = 208; // RFC5842 + const HTTP_IM_USED = 226; // RFC3229 + const HTTP_MULTIPLE_CHOICES = 300; + const HTTP_MOVED_PERMANENTLY = 301; + const HTTP_FOUND = 302; + const HTTP_SEE_OTHER = 303; + const HTTP_NOT_MODIFIED = 304; + const HTTP_USE_PROXY = 305; + const HTTP_RESERVED = 306; + const HTTP_TEMPORARY_REDIRECT = 307; + const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238 + const HTTP_BAD_REQUEST = 400; + const HTTP_UNAUTHORIZED = 401; + const HTTP_PAYMENT_REQUIRED = 402; + const HTTP_FORBIDDEN = 403; + const HTTP_NOT_FOUND = 404; + const HTTP_METHOD_NOT_ALLOWED = 405; + const HTTP_NOT_ACCEPTABLE = 406; + const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; + const HTTP_REQUEST_TIMEOUT = 408; + const HTTP_CONFLICT = 409; + const HTTP_GONE = 410; + const HTTP_LENGTH_REQUIRED = 411; + const HTTP_PRECONDITION_FAILED = 412; + const HTTP_REQUEST_ENTITY_TOO_LARGE = 413; + const HTTP_REQUEST_URI_TOO_LONG = 414; + const HTTP_UNSUPPORTED_MEDIA_TYPE = 415; + const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; + const HTTP_EXPECTATION_FAILED = 417; + const HTTP_I_AM_A_TEAPOT = 418; // RFC2324 + const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540 + const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918 + const HTTP_LOCKED = 423; // RFC4918 + const HTTP_FAILED_DEPENDENCY = 424; // RFC4918 + const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817 + const HTTP_UPGRADE_REQUIRED = 426; // RFC2817 + const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585 + const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585 + const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585 + const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; + const HTTP_INTERNAL_SERVER_ERROR = 500; + const HTTP_NOT_IMPLEMENTED = 501; + const HTTP_BAD_GATEWAY = 502; + const HTTP_SERVICE_UNAVAILABLE = 503; + const HTTP_GATEWAY_TIMEOUT = 504; + const HTTP_VERSION_NOT_SUPPORTED = 505; + const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295 + const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918 + const HTTP_LOOP_DETECTED = 508; // RFC5842 + const HTTP_NOT_EXTENDED = 510; // RFC2774 + const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585 + + /** + * @var \Symfony\Component\HttpFoundation\ResponseHeaderBag + */ + public $headers; + + /** + * @var string + */ + protected $content; + + /** + * @var string + */ + protected $version; + + /** + * @var int + */ + protected $statusCode; + + /** + * @var string + */ + protected $statusText; + + /** + * @var string + */ + protected $charset; + + /** + * Status codes translation table. + * + * The list of codes is complete according to the + * {@link http://www.iana.org/assignments/http-status-codes/ Hypertext Transfer Protocol (HTTP) Status Code Registry} + * (last updated 2016-03-01). + * + * Unless otherwise noted, the status code is defined in RFC2616. + * + * @var array + */ + public static $statusTexts = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', // RFC2518 + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', // RFC4918 + 208 => 'Already Reported', // RFC5842 + 226 => 'IM Used', // RFC3229 + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', // RFC7238 + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Payload Too Large', + 414 => 'URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', // RFC2324 + 421 => 'Misdirected Request', // RFC7540 + 422 => 'Unprocessable Entity', // RFC4918 + 423 => 'Locked', // RFC4918 + 424 => 'Failed Dependency', // RFC4918 + 425 => 'Reserved for WebDAV advanced collections expired proposal', // RFC2817 + 426 => 'Upgrade Required', // RFC2817 + 428 => 'Precondition Required', // RFC6585 + 429 => 'Too Many Requests', // RFC6585 + 431 => 'Request Header Fields Too Large', // RFC6585 + 451 => 'Unavailable For Legal Reasons', // RFC7725 + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', // RFC2295 + 507 => 'Insufficient Storage', // RFC4918 + 508 => 'Loop Detected', // RFC5842 + 510 => 'Not Extended', // RFC2774 + 511 => 'Network Authentication Required', // RFC6585 + ); + + /** + * Constructor. + * + * @param mixed $content The response content, see setContent() + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + */ + public function __construct($content = '', $status = 200, $headers = array()) + { + $this->headers = new ResponseHeaderBag($headers); + $this->setContent($content); + $this->setStatusCode($status); + $this->setProtocolVersion('1.0'); + + /* RFC2616 - 14.18 says all Responses need to have a Date */ + if (!$this->headers->has('Date')) { + $this->setDate(\DateTime::createFromFormat('U', time())); + } + } + + /** + * Factory method for chainability. + * + * Example: + * + * return Response::create($body, 200) + * ->setSharedMaxAge(300); + * + * @param mixed $content The response content, see setContent() + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + */ + public static function create($content = '', $status = 200, $headers = array()) + { + return new static($content, $status, $headers); + } + + /** + * Returns the Response as an HTTP string. + * + * The string representation of the Response is the same as the + * one that will be sent to the client only if the prepare() method + * has been called before. + * + * @return string The Response as an HTTP string + * + * @see prepare() + */ + public function __toString() + { + return + sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n". + $this->headers."\r\n". + $this->getContent(); + } + + /** + * Clones the current Response instance. + */ + public function __clone() + { + $this->headers = clone $this->headers; + } + + /** + * Prepares the Response before it is sent to the client. + * + * This method tweaks the Response to ensure that it is + * compliant with RFC 2616. Most of the changes are based on + * the Request that is "associated" with this Response. + * + * @param Request $request A Request instance + * + * @return $this + */ + public function prepare(Request $request) + { + $headers = $this->headers; + + if ($this->isInformational() || $this->isEmpty()) { + $this->setContent(null); + $headers->remove('Content-Type'); + $headers->remove('Content-Length'); + } else { + // Content-type based on the Request + if (!$headers->has('Content-Type')) { + $format = $request->getRequestFormat(); + if (null !== $format && $mimeType = $request->getMimeType($format)) { + $headers->set('Content-Type', $mimeType); + } + } + + // Fix Content-Type + $charset = $this->charset ?: 'UTF-8'; + if (!$headers->has('Content-Type')) { + $headers->set('Content-Type', 'text/html; charset='.$charset); + } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) { + // add the charset + $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset); + } + + // Fix Content-Length + if ($headers->has('Transfer-Encoding')) { + $headers->remove('Content-Length'); + } + + if ($request->isMethod('HEAD')) { + // cf. RFC2616 14.13 + $length = $headers->get('Content-Length'); + $this->setContent(null); + if ($length) { + $headers->set('Content-Length', $length); + } + } + } + + // Fix protocol + if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + // Check if we need to send extra expire info headers + if ('1.0' == $this->getProtocolVersion() && false !== strpos($this->headers->get('Cache-Control'), 'no-cache')) { + $this->headers->set('pragma', 'no-cache'); + $this->headers->set('expires', -1); + } + + $this->ensureIEOverSSLCompatibility($request); + + return $this; + } + + /** + * Sends HTTP headers. + * + * @return $this + */ + public function sendHeaders() + { + // headers have already been sent by the developer + if (headers_sent()) { + return $this; + } + + /* RFC2616 - 14.18 says all Responses need to have a Date */ + if (!$this->headers->has('Date')) { + $this->setDate(\DateTime::createFromFormat('U', time())); + } + + // headers + foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) { + foreach ($values as $value) { + header($name.': '.$value, false, $this->statusCode); + } + } + + // status + header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); + + // cookies + foreach ($this->headers->getCookies() as $cookie) { + if ($cookie->isRaw()) { + setrawcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); + } else { + setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); + } + } + + return $this; + } + + /** + * Sends content for the current web response. + * + * @return $this + */ + public function sendContent() + { + echo $this->content; + + return $this; + } + + /** + * Sends HTTP headers and content. + * + * @return $this + */ + public function send() + { + $this->sendHeaders(); + $this->sendContent(); + + if (function_exists('fastcgi_finish_request')) { + fastcgi_finish_request(); + } elseif ('cli' !== PHP_SAPI) { + static::closeOutputBuffers(0, true); + } + + return $this; + } + + /** + * Sets the response content. + * + * Valid types are strings, numbers, null, and objects that implement a __toString() method. + * + * @param mixed $content Content that can be cast to string + * + * @return $this + * + * @throws \UnexpectedValueException + */ + public function setContent($content) + { + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable(array($content, '__toString'))) { + throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', gettype($content))); + } + + $this->content = (string) $content; + + return $this; + } + + /** + * Gets the current response content. + * + * @return string Content + */ + public function getContent() + { + return $this->content; + } + + /** + * Sets the HTTP protocol version (1.0 or 1.1). + * + * @param string $version The HTTP protocol version + * + * @return $this + * + * @final since version 3.2 + */ + public function setProtocolVersion($version) + { + $this->version = $version; + + return $this; + } + + /** + * Gets the HTTP protocol version. + * + * @return string The HTTP protocol version + * + * @final since version 3.2 + */ + public function getProtocolVersion() + { + return $this->version; + } + + /** + * Sets the response status code. + * + * @param int $code HTTP status code + * @param mixed $text HTTP status text + * + * If the status text is null it will be automatically populated for the known + * status codes and left empty otherwise. + * + * @return $this + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + * + * @final since version 3.2 + */ + public function setStatusCode($code, $text = null) + { + $this->statusCode = $code = (int) $code; + if ($this->isInvalid()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); + } + + if (null === $text) { + $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status'; + + return $this; + } + + if (false === $text) { + $this->statusText = ''; + + return $this; + } + + $this->statusText = $text; + + return $this; + } + + /** + * Retrieves the status code for the current web response. + * + * @return int Status code + * + * @final since version 3.2 + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Sets the response charset. + * + * @param string $charset Character set + * + * @return $this + * + * @final since version 3.2 + */ + public function setCharset($charset) + { + $this->charset = $charset; + + return $this; + } + + /** + * Retrieves the response charset. + * + * @return string Character set + * + * @final since version 3.2 + */ + public function getCharset() + { + return $this->charset; + } + + /** + * Returns true if the response is worth caching under any circumstance. + * + * Responses marked "private" with an explicit Cache-Control directive are + * considered uncacheable. + * + * Responses with neither a freshness lifetime (Expires, max-age) nor cache + * validator (Last-Modified, ETag) are considered uncacheable. + * + * @return bool true if the response is worth caching, false otherwise + * + * @final since version 3.3 + */ + public function isCacheable() + { + if (!in_array($this->statusCode, array(200, 203, 300, 301, 302, 404, 410))) { + return false; + } + + if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) { + return false; + } + + return $this->isValidateable() || $this->isFresh(); + } + + /** + * Returns true if the response is "fresh". + * + * Fresh responses may be served from cache without any interaction with the + * origin. A response is considered fresh when it includes a Cache-Control/max-age + * indicator or Expires header and the calculated age is less than the freshness lifetime. + * + * @return bool true if the response is fresh, false otherwise + * + * @final since version 3.3 + */ + public function isFresh() + { + return $this->getTtl() > 0; + } + + /** + * Returns true if the response includes headers that can be used to validate + * the response with the origin server using a conditional GET request. + * + * @return bool true if the response is validateable, false otherwise + * + * @final since version 3.3 + */ + public function isValidateable() + { + return $this->headers->has('Last-Modified') || $this->headers->has('ETag'); + } + + /** + * Marks the response as "private". + * + * It makes the response ineligible for serving other clients. + * + * @return $this + * + * @final since version 3.2 + */ + public function setPrivate() + { + $this->headers->removeCacheControlDirective('public'); + $this->headers->addCacheControlDirective('private'); + + return $this; + } + + /** + * Marks the response as "public". + * + * It makes the response eligible for serving other clients. + * + * @return $this + * + * @final since version 3.2 + */ + public function setPublic() + { + $this->headers->addCacheControlDirective('public'); + $this->headers->removeCacheControlDirective('private'); + + return $this; + } + + /** + * Returns true if the response must be revalidated by caches. + * + * This method indicates that the response must not be served stale by a + * cache in any circumstance without first revalidating with the origin. + * When present, the TTL of the response should not be overridden to be + * greater than the value provided by the origin. + * + * @return bool true if the response must be revalidated by a cache, false otherwise + * + * @final since version 3.3 + */ + public function mustRevalidate() + { + return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate'); + } + + /** + * Returns the Date header as a DateTime instance. + * + * @return \DateTime A \DateTime instance + * + * @throws \RuntimeException When the header is not parseable + * + * @final since version 3.2 + */ + public function getDate() + { + /* + RFC2616 - 14.18 says all Responses need to have a Date. + Make sure we provide one even if it the header + has been removed in the meantime. + */ + if (!$this->headers->has('Date')) { + $this->setDate(\DateTime::createFromFormat('U', time())); + } + + return $this->headers->getDate('Date'); + } + + /** + * Sets the Date header. + * + * @param \DateTime $date A \DateTime instance + * + * @return $this + * + * @final since version 3.2 + */ + public function setDate(\DateTime $date) + { + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the age of the response. + * + * @return int The age of the response in seconds + * + * @final since version 3.2 + */ + public function getAge() + { + if (null !== $age = $this->headers->get('Age')) { + return (int) $age; + } + + return max(time() - $this->getDate()->format('U'), 0); + } + + /** + * Marks the response stale by setting the Age header to be equal to the maximum age of the response. + * + * @return $this + */ + public function expire() + { + if ($this->isFresh()) { + $this->headers->set('Age', $this->getMaxAge()); + } + + return $this; + } + + /** + * Returns the value of the Expires header as a DateTime instance. + * + * @return \DateTime|null A DateTime instance or null if the header does not exist + * + * @final since version 3.2 + */ + public function getExpires() + { + try { + return $this->headers->getDate('Expires'); + } catch (\RuntimeException $e) { + // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past + return \DateTime::createFromFormat(DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000'); + } + } + + /** + * Sets the Expires HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @param \DateTime|null $date A \DateTime instance or null to remove the header + * + * @return $this + * + * @final since version 3.2 + */ + public function setExpires(\DateTime $date = null) + { + if (null === $date) { + $this->headers->remove('Expires'); + } else { + $date = clone $date; + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT'); + } + + return $this; + } + + /** + * Returns the number of seconds after the time specified in the response's Date + * header when the response should no longer be considered fresh. + * + * First, it checks for a s-maxage directive, then a max-age directive, and then it falls + * back on an expires header. It returns null when no maximum age can be established. + * + * @return int|null Number of seconds + * + * @final since version 3.2 + */ + public function getMaxAge() + { + if ($this->headers->hasCacheControlDirective('s-maxage')) { + return (int) $this->headers->getCacheControlDirective('s-maxage'); + } + + if ($this->headers->hasCacheControlDirective('max-age')) { + return (int) $this->headers->getCacheControlDirective('max-age'); + } + + if (null !== $this->getExpires()) { + return $this->getExpires()->format('U') - $this->getDate()->format('U'); + } + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh. + * + * This methods sets the Cache-Control max-age directive. + * + * @param int $value Number of seconds + * + * @return $this + * + * @final since version 3.2 + */ + public function setMaxAge($value) + { + $this->headers->addCacheControlDirective('max-age', $value); + + return $this; + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh by shared caches. + * + * This methods sets the Cache-Control s-maxage directive. + * + * @param int $value Number of seconds + * + * @return $this + * + * @final since version 3.2 + */ + public function setSharedMaxAge($value) + { + $this->setPublic(); + $this->headers->addCacheControlDirective('s-maxage', $value); + + return $this; + } + + /** + * Returns the response's time-to-live in seconds. + * + * It returns null when no freshness information is present in the response. + * + * When the responses TTL is <= 0, the response may not be served from cache without first + * revalidating with the origin. + * + * @return int|null The TTL in seconds + * + * @final since version 3.2 + */ + public function getTtl() + { + if (null !== $maxAge = $this->getMaxAge()) { + return $maxAge - $this->getAge(); + } + } + + /** + * Sets the response's time-to-live for shared caches. + * + * This method adjusts the Cache-Control/s-maxage directive. + * + * @param int $seconds Number of seconds + * + * @return $this + * + * @final since version 3.2 + */ + public function setTtl($seconds) + { + $this->setSharedMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Sets the response's time-to-live for private/client caches. + * + * This method adjusts the Cache-Control/max-age directive. + * + * @param int $seconds Number of seconds + * + * @return $this + * + * @final since version 3.2 + */ + public function setClientTtl($seconds) + { + $this->setMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Returns the Last-Modified HTTP header as a DateTime instance. + * + * @return \DateTime|null A DateTime instance or null if the header does not exist + * + * @throws \RuntimeException When the HTTP header is not parseable + * + * @final since version 3.2 + */ + public function getLastModified() + { + return $this->headers->getDate('Last-Modified'); + } + + /** + * Sets the Last-Modified HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @param \DateTime|null $date A \DateTime instance or null to remove the header + * + * @return $this + * + * @final since version 3.2 + */ + public function setLastModified(\DateTime $date = null) + { + if (null === $date) { + $this->headers->remove('Last-Modified'); + } else { + $date = clone $date; + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT'); + } + + return $this; + } + + /** + * Returns the literal value of the ETag HTTP header. + * + * @return string|null The ETag HTTP header or null if it does not exist + * + * @final since version 3.2 + */ + public function getEtag() + { + return $this->headers->get('ETag'); + } + + /** + * Sets the ETag value. + * + * @param string|null $etag The ETag unique identifier or null to remove the header + * @param bool $weak Whether you want a weak ETag or not + * + * @return $this + * + * @final since version 3.2 + */ + public function setEtag($etag = null, $weak = false) + { + if (null === $etag) { + $this->headers->remove('Etag'); + } else { + if (0 !== strpos($etag, '"')) { + $etag = '"'.$etag.'"'; + } + + $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag); + } + + return $this; + } + + /** + * Sets the response's cache headers (validation and/or expiration). + * + * Available options are: etag, last_modified, max_age, s_maxage, private, and public. + * + * @param array $options An array of cache options + * + * @return $this + * + * @throws \InvalidArgumentException + * + * @final since version 3.3 + */ + public function setCache(array $options) + { + if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public'))) { + throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', array_values($diff)))); + } + + if (isset($options['etag'])) { + $this->setEtag($options['etag']); + } + + if (isset($options['last_modified'])) { + $this->setLastModified($options['last_modified']); + } + + if (isset($options['max_age'])) { + $this->setMaxAge($options['max_age']); + } + + if (isset($options['s_maxage'])) { + $this->setSharedMaxAge($options['s_maxage']); + } + + if (isset($options['public'])) { + if ($options['public']) { + $this->setPublic(); + } else { + $this->setPrivate(); + } + } + + if (isset($options['private'])) { + if ($options['private']) { + $this->setPrivate(); + } else { + $this->setPublic(); + } + } + + return $this; + } + + /** + * Modifies the response so that it conforms to the rules defined for a 304 status code. + * + * This sets the status, removes the body, and discards any headers + * that MUST NOT be included in 304 responses. + * + * @return $this + * + * @see http://tools.ietf.org/html/rfc2616#section-10.3.5 + * + * @final since version 3.3 + */ + public function setNotModified() + { + $this->setStatusCode(304); + $this->setContent(null); + + // remove headers that MUST NOT be included with 304 Not Modified responses + foreach (array('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified') as $header) { + $this->headers->remove($header); + } + + return $this; + } + + /** + * Returns true if the response includes a Vary header. + * + * @return bool true if the response includes a Vary header, false otherwise + * + * @final since version 3.2 + */ + public function hasVary() + { + return null !== $this->headers->get('Vary'); + } + + /** + * Returns an array of header names given in the Vary header. + * + * @return array An array of Vary names + * + * @final since version 3.2 + */ + public function getVary() + { + if (!$vary = $this->headers->get('Vary', null, false)) { + return array(); + } + + $ret = array(); + foreach ($vary as $item) { + $ret = array_merge($ret, preg_split('/[\s,]+/', $item)); + } + + return $ret; + } + + /** + * Sets the Vary header. + * + * @param string|array $headers + * @param bool $replace Whether to replace the actual value or not (true by default) + * + * @return $this + * + * @final since version 3.2 + */ + public function setVary($headers, $replace = true) + { + $this->headers->set('Vary', $headers, $replace); + + return $this; + } + + /** + * Determines if the Response validators (ETag, Last-Modified) match + * a conditional value specified in the Request. + * + * If the Response is not modified, it sets the status code to 304 and + * removes the actual content by calling the setNotModified() method. + * + * @param Request $request A Request instance + * + * @return bool true if the Response validators match the Request, false otherwise + * + * @final since version 3.3 + */ + public function isNotModified(Request $request) + { + if (!$request->isMethodCacheable()) { + return false; + } + + $notModified = false; + $lastModified = $this->headers->get('Last-Modified'); + $modifiedSince = $request->headers->get('If-Modified-Since'); + + if ($etags = $request->getETags()) { + $notModified = in_array($this->getEtag(), $etags) || in_array('*', $etags); + } + + if ($modifiedSince && $lastModified) { + $notModified = strtotime($modifiedSince) >= strtotime($lastModified) && (!$etags || $notModified); + } + + if ($notModified) { + $this->setNotModified(); + } + + return $notModified; + } + + /** + * Is response invalid? + * + * @return bool + * + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + * + * @final since version 3.2 + */ + public function isInvalid() + { + return $this->statusCode < 100 || $this->statusCode >= 600; + } + + /** + * Is response informative? + * + * @return bool + * + * @final since version 3.3 + */ + public function isInformational() + { + return $this->statusCode >= 100 && $this->statusCode < 200; + } + + /** + * Is response successful? + * + * @return bool + * + * @final since version 3.2 + */ + public function isSuccessful() + { + return $this->statusCode >= 200 && $this->statusCode < 300; + } + + /** + * Is the response a redirect? + * + * @return bool + * + * @final since version 3.2 + */ + public function isRedirection() + { + return $this->statusCode >= 300 && $this->statusCode < 400; + } + + /** + * Is there a client error? + * + * @return bool + * + * @final since version 3.2 + */ + public function isClientError() + { + return $this->statusCode >= 400 && $this->statusCode < 500; + } + + /** + * Was there a server side error? + * + * @return bool + * + * @final since version 3.3 + */ + public function isServerError() + { + return $this->statusCode >= 500 && $this->statusCode < 600; + } + + /** + * Is the response OK? + * + * @return bool + * + * @final since version 3.2 + */ + public function isOk() + { + return 200 === $this->statusCode; + } + + /** + * Is the response forbidden? + * + * @return bool + * + * @final since version 3.2 + */ + public function isForbidden() + { + return 403 === $this->statusCode; + } + + /** + * Is the response a not found error? + * + * @return bool + * + * @final since version 3.2 + */ + public function isNotFound() + { + return 404 === $this->statusCode; + } + + /** + * Is the response a redirect of some form? + * + * @param string $location + * + * @return bool + * + * @final since version 3.2 + */ + public function isRedirect($location = null) + { + return in_array($this->statusCode, array(201, 301, 302, 303, 307, 308)) && (null === $location ?: $location == $this->headers->get('Location')); + } + + /** + * Is the response empty? + * + * @return bool + * + * @final since version 3.2 + */ + public function isEmpty() + { + return in_array($this->statusCode, array(204, 304)); + } + + /** + * Cleans or flushes output buffers up to target level. + * + * Resulting level can be greater than target level if a non-removable buffer has been encountered. + * + * @param int $targetLevel The target output buffering level + * @param bool $flush Whether to flush or clean the buffers + * + * @final since version 3.3 + */ + public static function closeOutputBuffers($targetLevel, $flush) + { + $status = ob_get_status(true); + $level = count($status); + // PHP_OUTPUT_HANDLER_* are not defined on HHVM 3.3 + $flags = defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE) : -1; + + while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || $flags === ($s['flags'] & $flags) : $s['del'])) { + if ($flush) { + ob_end_flush(); + } else { + ob_end_clean(); + } + } + } + + /** + * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9. + * + * @see http://support.microsoft.com/kb/323308 + * + * @final since version 3.3 + */ + protected function ensureIEOverSSLCompatibility(Request $request) + { + if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) == 1 && true === $request->isSecure()) { + if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) { + $this->headers->remove('Cache-Control'); + } + } + } +} diff --git a/vendor/symfony/http-foundation/ResponseHeaderBag.php b/vendor/symfony/http-foundation/ResponseHeaderBag.php new file mode 100644 index 00000000..df2931be --- /dev/null +++ b/vendor/symfony/http-foundation/ResponseHeaderBag.php @@ -0,0 +1,341 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ResponseHeaderBag is a container for Response HTTP headers. + * + * @author Fabien Potencier + */ +class ResponseHeaderBag extends HeaderBag +{ + const COOKIES_FLAT = 'flat'; + const COOKIES_ARRAY = 'array'; + + const DISPOSITION_ATTACHMENT = 'attachment'; + const DISPOSITION_INLINE = 'inline'; + + /** + * @var array + */ + protected $computedCacheControl = array(); + + /** + * @var array + */ + protected $cookies = array(); + + /** + * @var array + */ + protected $headerNames = array(); + + /** + * Constructor. + * + * @param array $headers An array of HTTP headers + */ + public function __construct(array $headers = array()) + { + parent::__construct($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + } + + /** + * Returns the headers, with original capitalizations. + * + * @return array An array of headers + */ + public function allPreserveCase() + { + $headers = array(); + foreach ($this->all() as $name => $value) { + $headers[isset($this->headerNames[$name]) ? $this->headerNames[$name] : $name] = $value; + } + + return $headers; + } + + public function allPreserveCaseWithoutCookies() + { + $headers = $this->allPreserveCase(); + if (isset($this->headerNames['set-cookie'])) { + unset($headers[$this->headerNames['set-cookie']]); + } + + return $headers; + } + + /** + * {@inheritdoc} + */ + public function replace(array $headers = array()) + { + $this->headerNames = array(); + + parent::replace($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + } + + /** + * {@inheritdoc} + */ + public function all() + { + $headers = parent::all(); + foreach ($this->getCookies() as $cookie) { + $headers['set-cookie'][] = (string) $cookie; + } + + return $headers; + } + + /** + * {@inheritdoc} + */ + public function set($key, $values, $replace = true) + { + $uniqueKey = str_replace('_', '-', strtolower($key)); + + if ('set-cookie' === $uniqueKey) { + if ($replace) { + $this->cookies = array(); + } + foreach ((array) $values as $cookie) { + $this->setCookie(Cookie::fromString($cookie)); + } + $this->headerNames[$uniqueKey] = $key; + + return; + } + + $this->headerNames[$uniqueKey] = $key; + + parent::set($key, $values, $replace); + + // ensure the cache-control header has sensible defaults + if (in_array($uniqueKey, array('cache-control', 'etag', 'last-modified', 'expires'))) { + $computed = $this->computeCacheControlValue(); + $this->headers['cache-control'] = array($computed); + $this->headerNames['cache-control'] = 'Cache-Control'; + $this->computedCacheControl = $this->parseCacheControl($computed); + } + } + + /** + * {@inheritdoc} + */ + public function remove($key) + { + $uniqueKey = str_replace('_', '-', strtolower($key)); + unset($this->headerNames[$uniqueKey]); + + if ('set-cookie' === $uniqueKey) { + $this->cookies = array(); + + return; + } + + parent::remove($key); + + if ('cache-control' === $uniqueKey) { + $this->computedCacheControl = array(); + } + } + + /** + * {@inheritdoc} + */ + public function hasCacheControlDirective($key) + { + return array_key_exists($key, $this->computedCacheControl); + } + + /** + * {@inheritdoc} + */ + public function getCacheControlDirective($key) + { + return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null; + } + + /** + * Sets a cookie. + * + * @param Cookie $cookie + */ + public function setCookie(Cookie $cookie) + { + $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; + $this->headerNames['set-cookie'] = 'Set-Cookie'; + } + + /** + * Removes a cookie from the array, but does not unset it in the browser. + * + * @param string $name + * @param string $path + * @param string $domain + */ + public function removeCookie($name, $path = '/', $domain = null) + { + if (null === $path) { + $path = '/'; + } + + unset($this->cookies[$domain][$path][$name]); + + if (empty($this->cookies[$domain][$path])) { + unset($this->cookies[$domain][$path]); + + if (empty($this->cookies[$domain])) { + unset($this->cookies[$domain]); + } + } + + if (empty($this->cookies)) { + unset($this->headerNames['set-cookie']); + } + } + + /** + * Returns an array with all cookies. + * + * @param string $format + * + * @return array + * + * @throws \InvalidArgumentException When the $format is invalid + */ + public function getCookies($format = self::COOKIES_FLAT) + { + if (!in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) { + throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY)))); + } + + if (self::COOKIES_ARRAY === $format) { + return $this->cookies; + } + + $flattenedCookies = array(); + foreach ($this->cookies as $path) { + foreach ($path as $cookies) { + foreach ($cookies as $cookie) { + $flattenedCookies[] = $cookie; + } + } + } + + return $flattenedCookies; + } + + /** + * Clears a cookie in the browser. + * + * @param string $name + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $httpOnly + */ + public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true) + { + $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly)); + } + + /** + * 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 function makeDisposition($disposition, $filename, $filenameFallback = '') + { + if (!in_array($disposition, array(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.'); + } + + $output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback)); + + if ($filename !== $filenameFallback) { + $output .= sprintf("; filename*=utf-8''%s", rawurlencode($filename)); + } + + return $output; + } + + /** + * Returns the calculated value of the cache-control header. + * + * This considers several other headers and calculates or modifies the + * cache-control header to a sensible, conservative value. + * + * @return string + */ + protected function computeCacheControlValue() + { + if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) { + return 'no-cache, private'; + } + + if (!$this->cacheControl) { + // conservative by default + return 'private, must-revalidate'; + } + + $header = $this->getCacheControlHeader(); + if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) { + return $header; + } + + // public if s-maxage is defined, private otherwise + if (!isset($this->cacheControl['s-maxage'])) { + return $header.', private'; + } + + return $header; + } +} diff --git a/vendor/symfony/http-foundation/ServerBag.php b/vendor/symfony/http-foundation/ServerBag.php new file mode 100644 index 00000000..0d38c08a --- /dev/null +++ b/vendor/symfony/http-foundation/ServerBag.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ServerBag is a container for HTTP headers from the $_SERVER variable. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Robert Kiss + */ +class ServerBag extends ParameterBag +{ + /** + * Gets the HTTP headers. + * + * @return array + */ + public function getHeaders() + { + $headers = array(); + $contentHeaders = array('CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true); + foreach ($this->parameters as $key => $value) { + if (0 === strpos($key, 'HTTP_')) { + $headers[substr($key, 5)] = $value; + } + // CONTENT_* are not prefixed with HTTP_ + elseif (isset($contentHeaders[$key])) { + $headers[$key] = $value; + } + } + + if (isset($this->parameters['PHP_AUTH_USER'])) { + $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER']; + $headers['PHP_AUTH_PW'] = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : ''; + } else { + /* + * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default + * For this workaround to work, add these lines to your .htaccess file: + * RewriteCond %{HTTP:Authorization} ^(.+)$ + * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + * + * A sample .htaccess file: + * RewriteEngine On + * RewriteCond %{HTTP:Authorization} ^(.+)$ + * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + * RewriteCond %{REQUEST_FILENAME} !-f + * RewriteRule ^(.*)$ app.php [QSA,L] + */ + + $authorizationHeader = null; + if (isset($this->parameters['HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION']; + } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION']; + } + + if (null !== $authorizationHeader) { + if (0 === stripos($authorizationHeader, 'basic ')) { + // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic + $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2); + if (count($exploded) == 2) { + list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded; + } + } elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) { + // In some circumstances PHP_AUTH_DIGEST needs to be set + $headers['PHP_AUTH_DIGEST'] = $authorizationHeader; + $this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader; + } elseif (0 === stripos($authorizationHeader, 'bearer ')) { + /* + * XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables, + * I'll just set $headers['AUTHORIZATION'] here. + * http://php.net/manual/en/reserved.variables.server.php + */ + $headers['AUTHORIZATION'] = $authorizationHeader; + } + } + } + + if (isset($headers['AUTHORIZATION'])) { + return $headers; + } + + // PHP_AUTH_USER/PHP_AUTH_PW + if (isset($headers['PHP_AUTH_USER'])) { + $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']); + } elseif (isset($headers['PHP_AUTH_DIGEST'])) { + $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST']; + } + + return $headers; + } +} diff --git a/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php b/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php new file mode 100644 index 00000000..af292e37 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +/** + * This class relates to session attribute storage. + */ +class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable +{ + private $name = 'attributes'; + + /** + * @var string + */ + private $storageKey; + + /** + * @var array + */ + protected $attributes = array(); + + /** + * Constructor. + * + * @param string $storageKey The key used to store attributes in the session + */ + public function __construct($storageKey = '_sf2_attributes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$attributes) + { + $this->attributes = &$attributes; + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return array_key_exists($name, $this->attributes); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $this->attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->attributes = array(); + foreach ($attributes as $key => $value) { + $this->set($key, $value); + } + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + $retval = null; + if (array_key_exists($name, $this->attributes)) { + $retval = $this->attributes[$name]; + unset($this->attributes[$name]); + } + + return $retval; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $return = $this->attributes; + $this->attributes = array(); + + return $return; + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->attributes); + } + + /** + * Returns the number of attributes. + * + * @return int The number of attributes + */ + public function count() + { + return count($this->attributes); + } +} diff --git a/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php b/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php new file mode 100644 index 00000000..0d8d1799 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Attributes store. + * + * @author Drak + */ +interface AttributeBagInterface extends SessionBagInterface +{ + /** + * Checks if an attribute is defined. + * + * @param string $name The attribute name + * + * @return bool true if the attribute is defined, false otherwise + */ + public function has($name); + + /** + * Returns an attribute. + * + * @param string $name The attribute name + * @param mixed $default The default value if not found + * + * @return mixed + */ + public function get($name, $default = null); + + /** + * Sets an attribute. + * + * @param string $name + * @param mixed $value + */ + public function set($name, $value); + + /** + * Returns attributes. + * + * @return array Attributes + */ + public function all(); + + /** + * Sets attributes. + * + * @param array $attributes Attributes + */ + public function replace(array $attributes); + + /** + * Removes an attribute. + * + * @param string $name + * + * @return mixed The removed value or null when it does not exist + */ + public function remove($name); +} diff --git a/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php b/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php new file mode 100644 index 00000000..d797a6f2 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +/** + * This class provides structured storage of session attributes using + * a name spacing character in the key. + * + * @author Drak + */ +class NamespacedAttributeBag extends AttributeBag +{ + /** + * Namespace character. + * + * @var string + */ + private $namespaceCharacter; + + /** + * Constructor. + * + * @param string $storageKey Session storage key + * @param string $namespaceCharacter Namespace character to use in keys + */ + public function __construct($storageKey = '_sf2_attributes', $namespaceCharacter = '/') + { + $this->namespaceCharacter = $namespaceCharacter; + parent::__construct($storageKey); + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + if (null === $attributes) { + return false; + } + + return array_key_exists($name, $attributes); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + if (null === $attributes) { + return $default; + } + + return array_key_exists($name, $attributes) ? $attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $attributes = &$this->resolveAttributePath($name, true); + $name = $this->resolveKey($name); + $attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + $retval = null; + $attributes = &$this->resolveAttributePath($name); + $name = $this->resolveKey($name); + if (null !== $attributes && array_key_exists($name, $attributes)) { + $retval = $attributes[$name]; + unset($attributes[$name]); + } + + return $retval; + } + + /** + * Resolves a path in attributes property and returns it as a reference. + * + * This method allows structured namespacing of session attributes. + * + * @param string $name Key name + * @param bool $writeContext Write context, default false + * + * @return array + */ + protected function &resolveAttributePath($name, $writeContext = false) + { + $array = &$this->attributes; + $name = (strpos($name, $this->namespaceCharacter) === 0) ? substr($name, 1) : $name; + + // Check if there is anything to do, else return + if (!$name) { + return $array; + } + + $parts = explode($this->namespaceCharacter, $name); + if (count($parts) < 2) { + if (!$writeContext) { + return $array; + } + + $array[$parts[0]] = array(); + + return $array; + } + + unset($parts[count($parts) - 1]); + + foreach ($parts as $part) { + if (null !== $array && !array_key_exists($part, $array)) { + $array[$part] = $writeContext ? array() : null; + } + + $array = &$array[$part]; + } + + return $array; + } + + /** + * Resolves the key from the name. + * + * This is the last part in a dot separated string. + * + * @param string $name + * + * @return string + */ + protected function resolveKey($name) + { + if (false !== $pos = strrpos($name, $this->namespaceCharacter)) { + $name = substr($name, $pos + 1); + } + + return $name; + } +} diff --git a/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php b/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php new file mode 100644 index 00000000..ddd603fd --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php @@ -0,0 +1,175 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * AutoExpireFlashBag flash message container. + * + * @author Drak + */ +class AutoExpireFlashBag implements FlashBagInterface +{ + private $name = 'flashes'; + + /** + * Flash messages. + * + * @var array + */ + private $flashes = array('display' => array(), 'new' => array()); + + /** + * The storage key for flashes in the session. + * + * @var string + */ + private $storageKey; + + /** + * Constructor. + * + * @param string $storageKey The key used to store flashes in the session + */ + public function __construct($storageKey = '_sf2_flashes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + + // The logic: messages from the last request will be stored in new, so we move them to previous + // This request we will show what is in 'display'. What is placed into 'new' this time round will + // be moved to display next time round. + $this->flashes['display'] = array_key_exists('new', $this->flashes) ? $this->flashes['new'] : array(); + $this->flashes['new'] = array(); + } + + /** + * {@inheritdoc} + */ + public function add($type, $message) + { + $this->flashes['new'][$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function peek($type, array $default = array()) + { + return $this->has($type) ? $this->flashes['display'][$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return array_key_exists('display', $this->flashes) ? (array) $this->flashes['display'] : array(); + } + + /** + * {@inheritdoc} + */ + public function get($type, array $default = array()) + { + $return = $default; + + if (!$this->has($type)) { + return $return; + } + + if (isset($this->flashes['display'][$type])) { + $return = $this->flashes['display'][$type]; + unset($this->flashes['display'][$type]); + } + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->flashes['display']; + $this->flashes = array('new' => array(), 'display' => array()); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes['new'] = $messages; + } + + /** + * {@inheritdoc} + */ + public function set($type, $messages) + { + $this->flashes['new'][$type] = (array) $messages; + } + + /** + * {@inheritdoc} + */ + public function has($type) + { + return array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type]; + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes['display']); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->all(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Flash/FlashBag.php b/vendor/symfony/http-foundation/Session/Flash/FlashBag.php new file mode 100644 index 00000000..85b4f00b --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/FlashBag.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * FlashBag flash message container. + * + * @author Drak + */ +class FlashBag implements FlashBagInterface +{ + private $name = 'flashes'; + + /** + * Flash messages. + * + * @var array + */ + private $flashes = array(); + + /** + * The storage key for flashes in the session. + * + * @var string + */ + private $storageKey; + + /** + * Constructor. + * + * @param string $storageKey The key used to store flashes in the session + */ + public function __construct($storageKey = '_sf2_flashes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + } + + /** + * {@inheritdoc} + */ + public function add($type, $message) + { + $this->flashes[$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function peek($type, array $default = array()) + { + return $this->has($type) ? $this->flashes[$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return $this->flashes; + } + + /** + * {@inheritdoc} + */ + public function get($type, array $default = array()) + { + if (!$this->has($type)) { + return $default; + } + + $return = $this->flashes[$type]; + + unset($this->flashes[$type]); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->peekAll(); + $this->flashes = array(); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function set($type, $messages) + { + $this->flashes[$type] = (array) $messages; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes = $messages; + } + + /** + * {@inheritdoc} + */ + public function has($type) + { + return array_key_exists($type, $this->flashes) && $this->flashes[$type]; + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->all(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php b/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php new file mode 100644 index 00000000..25f3d57b --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * FlashBagInterface. + * + * @author Drak + */ +interface FlashBagInterface extends SessionBagInterface +{ + /** + * Adds a flash message for type. + * + * @param string $type + * @param string $message + */ + public function add($type, $message); + + /** + * Registers a message for a given type. + * + * @param string $type + * @param string|array $message + */ + public function set($type, $message); + + /** + * Gets flash messages for a given type. + * + * @param string $type Message category type + * @param array $default Default value if $type does not exist + * + * @return array + */ + public function peek($type, array $default = array()); + + /** + * Gets all flash messages. + * + * @return array + */ + public function peekAll(); + + /** + * Gets and clears flash from the stack. + * + * @param string $type + * @param array $default Default value if $type does not exist + * + * @return array + */ + public function get($type, array $default = array()); + + /** + * Gets and clears flashes from the stack. + * + * @return array + */ + public function all(); + + /** + * Sets all flash messages. + * + * @param array $messages + */ + public function setAll(array $messages); + + /** + * Has flash messages for a given type? + * + * @param string $type + * + * @return bool + */ + public function has($type); + + /** + * Returns a list of all defined types. + * + * @return array + */ + public function keys(); +} diff --git a/vendor/symfony/http-foundation/Session/Session.php b/vendor/symfony/http-foundation/Session/Session.php new file mode 100644 index 00000000..70bcf3e0 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Session.php @@ -0,0 +1,261 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; + +/** + * Session. + * + * @author Fabien Potencier + * @author Drak + */ +class Session implements SessionInterface, \IteratorAggregate, \Countable +{ + /** + * Storage driver. + * + * @var SessionStorageInterface + */ + protected $storage; + + /** + * @var string + */ + private $flashName; + + /** + * @var string + */ + private $attributeName; + + /** + * Constructor. + * + * @param SessionStorageInterface $storage A SessionStorageInterface instance + * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag) + * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag) + */ + public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) + { + $this->storage = $storage ?: new NativeSessionStorage(); + + $attributes = $attributes ?: new AttributeBag(); + $this->attributeName = $attributes->getName(); + $this->registerBag($attributes); + + $flashes = $flashes ?: new FlashBag(); + $this->flashName = $flashes->getName(); + $this->registerBag($flashes); + } + + /** + * {@inheritdoc} + */ + public function start() + { + return $this->storage->start(); + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return $this->getAttributeBag()->has($name); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + return $this->getAttributeBag()->get($name, $default); + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $this->getAttributeBag()->set($name, $value); + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->getAttributeBag()->all(); + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->getAttributeBag()->replace($attributes); + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + return $this->getAttributeBag()->remove($name); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->storage->getBag($this->attributeName)->clear(); + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->storage->isStarted(); + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->getAttributeBag()->all()); + } + + /** + * Returns the number of attributes. + * + * @return int The number of attributes + */ + public function count() + { + return count($this->getAttributeBag()->all()); + } + + /** + * {@inheritdoc} + */ + public function invalidate($lifetime = null) + { + $this->storage->clear(); + + return $this->migrate(true, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function migrate($destroy = false, $lifetime = null) + { + return $this->storage->regenerate($destroy, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function save() + { + $this->storage->save(); + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->storage->getId(); + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + $this->storage->setId($id); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->storage->getName(); + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->storage->setName($name); + } + + /** + * {@inheritdoc} + */ + public function getMetadataBag() + { + return $this->storage->getMetadataBag(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->storage->registerBag($bag); + } + + /** + * {@inheritdoc} + */ + public function getBag($name) + { + return $this->storage->getBag($name); + } + + /** + * Gets the flashbag interface. + * + * @return FlashBagInterface + */ + public function getFlashBag() + { + return $this->getBag($this->flashName); + } + + /** + * Gets the attributebag interface. + * + * Note that this method was added to help with IDE autocompletion. + * + * @return AttributeBagInterface + */ + private function getAttributeBag() + { + return $this->storage->getBag($this->attributeName); + } +} diff --git a/vendor/symfony/http-foundation/Session/SessionBagInterface.php b/vendor/symfony/http-foundation/Session/SessionBagInterface.php new file mode 100644 index 00000000..aca18aac --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionBagInterface.php @@ -0,0 +1,48 @@ + + * + * 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 Bag store. + * + * @author Drak + */ +interface SessionBagInterface +{ + /** + * Gets this bag's name. + * + * @return string + */ + public function getName(); + + /** + * Initializes the Bag. + * + * @param array $array + */ + public function initialize(array &$array); + + /** + * Gets the storage key for this bag. + * + * @return string + */ + public function getStorageKey(); + + /** + * Clears out data from bag. + * + * @return mixed Whatever data was contained + */ + public function clear(); +} diff --git a/vendor/symfony/http-foundation/Session/SessionInterface.php b/vendor/symfony/http-foundation/Session/SessionInterface.php new file mode 100644 index 00000000..d3fcd2ee --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionInterface.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; + +/** + * Interface for the session. + * + * @author Drak + */ +interface SessionInterface +{ + /** + * Starts the session storage. + * + * @return bool True if session started + * + * @throws \RuntimeException If session fails to start. + */ + public function start(); + + /** + * Returns the session ID. + * + * @return string The session ID + */ + public function getId(); + + /** + * Sets the session ID. + * + * @param string $id + */ + public function setId($id); + + /** + * Returns the session name. + * + * @return mixed The session name + */ + public function getName(); + + /** + * Sets the session name. + * + * @param string $name + */ + public function setName($name); + + /** + * Invalidates the current session. + * + * Clears all session attributes and flashes and regenerates the + * session and deletes the old session from persistence. + * + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool True if session invalidated, false if error + */ + public function invalidate($lifetime = null); + + /** + * Migrates the current session to a new session id while maintaining all + * session attributes. + * + * @param bool $destroy Whether to delete the old session or leave it to garbage collection + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool True if session migrated, false if error + */ + public function migrate($destroy = false, $lifetime = null); + + /** + * Force the session to be saved and closed. + * + * This method is generally not required for real sessions as + * the session will be automatically saved at the end of + * code execution. + */ + public function save(); + + /** + * Checks if an attribute is defined. + * + * @param string $name The attribute name + * + * @return bool true if the attribute is defined, false otherwise + */ + public function has($name); + + /** + * Returns an attribute. + * + * @param string $name The attribute name + * @param mixed $default The default value if not found + * + * @return mixed + */ + public function get($name, $default = null); + + /** + * Sets an attribute. + * + * @param string $name + * @param mixed $value + */ + public function set($name, $value); + + /** + * Returns attributes. + * + * @return array Attributes + */ + public function all(); + + /** + * Sets attributes. + * + * @param array $attributes Attributes + */ + public function replace(array $attributes); + + /** + * Removes an attribute. + * + * @param string $name + * + * @return mixed The removed value or null when it does not exist + */ + public function remove($name); + + /** + * Clears all attributes. + */ + public function clear(); + + /** + * Checks if the session was started. + * + * @return bool + */ + public function isStarted(); + + /** + * Registers a SessionBagInterface with the session. + * + * @param SessionBagInterface $bag + */ + public function registerBag(SessionBagInterface $bag); + + /** + * Gets a bag instance by name. + * + * @param string $name + * + * @return SessionBagInterface + */ + public function getBag($name); + + /** + * Gets session meta. + * + * @return MetadataBag + */ + public function getMetadataBag(); +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php new file mode 100644 index 00000000..4e490a05 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php @@ -0,0 +1,121 @@ + + * + * 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; + +/** + * MemcacheSessionHandler. + * + * @author Drak + */ +class MemcacheSessionHandler implements \SessionHandlerInterface +{ + /** + * @var \Memcache Memcache driver + */ + private $memcache; + + /** + * @var int Time to live in seconds + */ + private $ttl; + + /** + * @var string Key prefix for shared environments + */ + private $prefix; + + /** + * Constructor. + * + * List of available options: + * * prefix: The prefix to use for the memcache keys in order to avoid collision + * * expiretime: The time to live in seconds + * + * @param \Memcache $memcache A \Memcache instance + * @param array $options An associative array of Memcache options + * + * @throws \InvalidArgumentException When unsupported options are passed + */ + public function __construct(\Memcache $memcache, array $options = array()) + { + if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) { + throw new \InvalidArgumentException(sprintf( + 'The following options are not supported "%s"', implode(', ', $diff) + )); + } + + $this->memcache = $memcache; + $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400; + $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s'; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + return $this->memcache->get($this->prefix.$sessionId) ?: ''; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + return $this->memcache->set($this->prefix.$sessionId, $data, 0, time() + $this->ttl); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + $this->memcache->delete($this->prefix.$sessionId); + + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + // not required here because memcache will auto expire the records anyhow. + return true; + } + + /** + * Return a Memcache instance. + * + * @return \Memcache + */ + protected function getMemcache() + { + return $this->memcache; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php new file mode 100644 index 00000000..67a49ad6 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -0,0 +1,127 @@ + + * + * 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; + +/** + * MemcachedSessionHandler. + * + * Memcached based session storage handler based on the Memcached class + * provided by the PHP memcached extension. + * + * @see http://php.net/memcached + * + * @author Drak + */ +class MemcachedSessionHandler implements \SessionHandlerInterface +{ + /** + * @var \Memcached Memcached driver + */ + private $memcached; + + /** + * @var int Time to live in seconds + */ + private $ttl; + + /** + * @var string Key prefix for shared environments + */ + private $prefix; + + /** + * Constructor. + * + * List of available options: + * * prefix: The prefix to use for the memcached keys in order to avoid collision + * * expiretime: The time to live in seconds + * + * @param \Memcached $memcached A \Memcached instance + * @param array $options An associative array of Memcached options + * + * @throws \InvalidArgumentException When unsupported options are passed + */ + public function __construct(\Memcached $memcached, array $options = array()) + { + $this->memcached = $memcached; + + if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) { + throw new \InvalidArgumentException(sprintf( + 'The following options are not supported "%s"', implode(', ', $diff) + )); + } + + $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400; + $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s'; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + return $this->memcached->get($this->prefix.$sessionId) ?: ''; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + $result = $this->memcached->delete($this->prefix.$sessionId); + + return $result || $this->memcached->getResultCode() == \Memcached::RES_NOTFOUND; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + // not required here because memcached will auto expire the records anyhow. + return true; + } + + /** + * Return a Memcached instance. + * + * @return \Memcached + */ + protected function getMemcached() + { + return $this->memcached; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php new file mode 100644 index 00000000..8408f000 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -0,0 +1,232 @@ + + * + * 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; + +/** + * MongoDB session handler. + * + * @author Markus Bachmann + */ +class MongoDbSessionHandler implements \SessionHandlerInterface +{ + /** + * @var \Mongo|\MongoClient|\MongoDB\Client + */ + private $mongo; + + /** + * @var \MongoCollection + */ + private $collection; + + /** + * @var array + */ + private $options; + + /** + * Constructor. + * + * List of available options: + * * database: The name of the database [required] + * * collection: The name of the collection [required] + * * id_field: The field name for storing the session id [default: _id] + * * data_field: The field name for storing the session data [default: data] + * * time_field: The field name for storing the timestamp [default: time] + * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at] + * + * It is strongly recommended to put an index on the `expiry_field` for + * garbage-collection. Alternatively it's possible to automatically expire + * the sessions in the database as described below: + * + * A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions + * automatically. Such an index can for example look like this: + * + * db..ensureIndex( + * { "": 1 }, + * { "expireAfterSeconds": 0 } + * ) + * + * More details on: http://docs.mongodb.org/manual/tutorial/expire-data/ + * + * If you use such an index, you can drop `gc_probability` to 0 since + * no garbage-collection is required. + * + * @param \Mongo|\MongoClient|\MongoDB\Client $mongo A MongoDB\Client, MongoClient or Mongo instance + * @param array $options An associative array of field options + * + * @throws \InvalidArgumentException When MongoClient or Mongo instance not provided + * @throws \InvalidArgumentException When "database" or "collection" not provided + */ + public function __construct($mongo, array $options) + { + if (!($mongo instanceof \MongoDB\Client || $mongo instanceof \MongoClient || $mongo instanceof \Mongo)) { + throw new \InvalidArgumentException('MongoClient or Mongo instance required'); + } + + if (!isset($options['database']) || !isset($options['collection'])) { + throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler'); + } + + $this->mongo = $mongo; + + $this->options = array_merge(array( + 'id_field' => '_id', + 'data_field' => 'data', + 'time_field' => 'time', + 'expiry_field' => 'expires_at', + ), $options); + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove'; + + $this->getCollection()->$methodName(array( + $this->options['id_field'] => $sessionId, + )); + + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove'; + + $this->getCollection()->$methodName(array( + $this->options['expiry_field'] => array('$lt' => $this->createDateTime()), + )); + + return true; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime')); + + $fields = array( + $this->options['time_field'] => $this->createDateTime(), + $this->options['expiry_field'] => $expiry, + ); + + $options = array('upsert' => true); + + if ($this->mongo instanceof \MongoDB\Client) { + $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY); + } else { + $fields[$this->options['data_field']] = new \MongoBinData($data, \MongoBinData::BYTE_ARRAY); + $options['multiple'] = false; + } + + $methodName = $this->mongo instanceof \MongoDB\Client ? 'updateOne' : 'update'; + + $this->getCollection()->$methodName( + array($this->options['id_field'] => $sessionId), + array('$set' => $fields), + $options + ); + + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + $dbData = $this->getCollection()->findOne(array( + $this->options['id_field'] => $sessionId, + $this->options['expiry_field'] => array('$gte' => $this->createDateTime()), + )); + + if (null === $dbData) { + return ''; + } + + if ($dbData[$this->options['data_field']] instanceof \MongoDB\BSON\Binary) { + return $dbData[$this->options['data_field']]->getData(); + } + + return $dbData[$this->options['data_field']]->bin; + } + + /** + * Return a "MongoCollection" instance. + * + * @return \MongoCollection + */ + private function getCollection() + { + if (null === $this->collection) { + $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']); + } + + return $this->collection; + } + + /** + * Return a Mongo instance. + * + * @return \Mongo|\MongoClient|\MongoDB\Client + */ + protected function getMongo() + { + return $this->mongo; + } + + /** + * Create a date object using the class appropriate for the current mongo connection. + * + * Return an instance of a MongoDate or \MongoDB\BSON\UTCDateTime + * + * @param int $seconds An integer representing UTC seconds since Jan 1 1970. Defaults to now. + * + * @return \MongoDate|\MongoDB\BSON\UTCDateTime + */ + private function createDateTime($seconds = null) + { + if (null === $seconds) { + $seconds = time(); + } + + if ($this->mongo instanceof \MongoDB\Client) { + return new \MongoDB\BSON\UTCDateTime($seconds * 1000); + } + + return new \MongoDate($seconds); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php new file mode 100644 index 00000000..1be0a398 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php @@ -0,0 +1,58 @@ + + * + * 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; + +/** + * NativeFileSessionHandler. + * + * Native session handler using PHP's built in file storage. + * + * @author Drak + */ +class NativeFileSessionHandler extends NativeSessionHandler +{ + /** + * Constructor. + * + * @param string $savePath Path of directory to save session files + * Default null will leave setting as defined by PHP. + * '/path', 'N;/path', or 'N;octal-mode;/path + * + * @see http://php.net/session.configuration.php#ini.session.save-path for further details. + * + * @throws \InvalidArgumentException On invalid $savePath + */ + public function __construct($savePath = null) + { + if (null === $savePath) { + $savePath = ini_get('session.save_path'); + } + + $baseDir = $savePath; + + if ($count = substr_count($savePath, ';')) { + if ($count > 2) { + throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'', $savePath)); + } + + // characters after last ';' are the path + $baseDir = ltrim(strrchr($savePath, ';'), ';'); + } + + if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) { + throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $baseDir)); + } + + ini_set('session.save_path', $savePath); + ini_set('session.save_handler', 'files'); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php new file mode 100644 index 00000000..4ae410f9 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php @@ -0,0 +1,21 @@ + + * + * 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; + +/** + * Adds SessionHandler functionality if available. + * + * @see http://php.net/sessionhandler + */ +class NativeSessionHandler extends \SessionHandler +{ +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php new file mode 100644 index 00000000..1516d431 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php @@ -0,0 +1,70 @@ + + * + * 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; + +/** + * NullSessionHandler. + * + * Can be used in unit testing or in a situations where persisted sessions are not desired. + * + * @author Drak + */ +class NullSessionHandler implements \SessionHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + return true; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php new file mode 100644 index 00000000..8909a5f4 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php @@ -0,0 +1,721 @@ + + * + * 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; + +/** + * Session handler using a PDO connection to read and write data. + * + * It works with MySQL, PostgreSQL, Oracle, SQL Server and SQLite and implements + * different locking strategies to handle concurrent access to the same session. + * Locking is necessary to prevent loss of data due to race conditions and to keep + * the session data consistent between read() and write(). With locking, requests + * for the same session will wait until the other one finished writing. For this + * reason it's best practice to close a session as early as possible to improve + * concurrency. PHPs internal files session handler also implements locking. + * + * Attention: Since SQLite does not support row level locks but locks the whole database, + * it means only one session can be accessed at a time. Even different sessions would wait + * for another to finish. So saving session in SQLite should only be considered for + * development or prototypes. + * + * Session data is a binary string that can contain non-printable characters like the null byte. + * For this reason it must be saved in a binary column in the database like BLOB in MySQL. + * Saving it in a character column could corrupt the data. You can use createTable() + * to initialize a correctly defined table. + * + * @see http://php.net/sessionhandlerinterface + * + * @author Fabien Potencier + * @author Michael Williams + * @author Tobias Schultze + */ +class PdoSessionHandler implements \SessionHandlerInterface +{ + /** + * No locking is done. This means sessions are prone to loss of data due to + * race conditions of concurrent requests to the same session. The last session + * write will win in this case. It might be useful when you implement your own + * logic to deal with this like an optimistic approach. + */ + const LOCK_NONE = 0; + + /** + * Creates an application-level lock on a session. The disadvantage is that the + * lock is not enforced by the database and thus other, unaware parts of the + * application could still concurrently modify the session. The advantage is it + * does not require a transaction. + * This mode is not available for SQLite and not yet implemented for oci and sqlsrv. + */ + const LOCK_ADVISORY = 1; + + /** + * Issues a real row lock. Since it uses a transaction between opening and + * closing a session, you have to be careful when you use same database connection + * that you also use for your application logic. This mode is the default because + * it's the only reliable solution across DBMSs. + */ + const LOCK_TRANSACTIONAL = 2; + + /** + * @var \PDO|null PDO instance or null when not connected yet + */ + private $pdo; + + /** + * @var string|null|false DSN string or null for session.save_path or false when lazy connection disabled + */ + private $dsn = false; + + /** + * @var string Database driver + */ + private $driver; + + /** + * @var string Table name + */ + private $table = 'sessions'; + + /** + * @var string Column for session id + */ + private $idCol = 'sess_id'; + + /** + * @var string Column for session data + */ + private $dataCol = 'sess_data'; + + /** + * @var string Column for lifetime + */ + private $lifetimeCol = 'sess_lifetime'; + + /** + * @var string Column for timestamp + */ + private $timeCol = 'sess_time'; + + /** + * @var string Username when lazy-connect + */ + private $username = ''; + + /** + * @var string Password when lazy-connect + */ + private $password = ''; + + /** + * @var array Connection options when lazy-connect + */ + private $connectionOptions = array(); + + /** + * @var int The strategy for locking, see constants + */ + private $lockMode = self::LOCK_TRANSACTIONAL; + + /** + * It's an array to support multiple reads before closing which is manual, non-standard usage. + * + * @var \PDOStatement[] An array of statements to release advisory locks + */ + private $unlockStatements = array(); + + /** + * @var bool True when the current session exists but expired according to session.gc_maxlifetime + */ + private $sessionExpired = false; + + /** + * @var bool Whether a transaction is active + */ + private $inTransaction = false; + + /** + * @var bool Whether gc() has been called + */ + private $gcCalled = false; + + /** + * Constructor. + * + * You can either pass an existing database connection as PDO instance or + * pass a DSN string that will be used to lazy-connect to the database + * when the session is actually used. Furthermore it's possible to pass null + * which will then use the session.save_path ini setting as PDO DSN parameter. + * + * List of available options: + * * db_table: The name of the table [default: sessions] + * * db_id_col: The column where to store the session id [default: sess_id] + * * db_data_col: The column where to store the session data [default: sess_data] + * * db_lifetime_col: The column where to store the lifetime [default: sess_lifetime] + * * db_time_col: The column where to store the timestamp [default: sess_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: array()] + * * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL] + * + * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or null + * @param array $options An associative array of options + * + * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION + */ + public function __construct($pdoOrDsn = null, array $options = array()) + { + if ($pdoOrDsn instanceof \PDO) { + if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->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->pdo = $pdoOrDsn; + $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } else { + $this->dsn = $pdoOrDsn; + } + + $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->lockMode = isset($options['lock_mode']) ? $options['lock_mode'] : $this->lockMode; + } + + /** + * Creates the table to store sessions which can be called once for setup. + * + * Session ID is saved in a column of maximum length 128 because that is enough even + * for a 512 bit configured session.hash_function like Whirlpool. Session data is + * saved in a BLOB. One could also use a shorter inlined varbinary column + * if one was sure the data fits into it. + * + * @throws \PDOException When the table already exists + * @throws \DomainException When an unsupported PDO driver is used + */ + public function createTable() + { + // connect if we are not yet + $this->getConnection(); + + 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(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol MEDIUMINT NOT NULL, $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 NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'pgsql': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'oci': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'sqlsrv': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + default: + throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)); + } + + try { + $this->pdo->exec($sql); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + + /** + * Returns true when the current session exists but expired according to session.gc_maxlifetime. + * + * Can be used to distinguish between a new session and one that expired due to inactivity. + * + * @return bool Whether current session expired + */ + public function isSessionExpired() + { + return $this->sessionExpired; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + if (null === $this->pdo) { + $this->connect($this->dsn ?: $savePath); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + try { + return $this->doRead($sessionId); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + // We delay gc() to close() so that it is executed outside the transactional and blocking read-write process. + // This way, pruning expired sessions does not block them from being started while the current session is used. + $this->gcCalled = true; + + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + // delete the record associated with this id + $sql = "DELETE FROM $this->table WHERE $this->idCol = :id"; + + try { + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->execute(); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + $maxlifetime = (int) ini_get('session.gc_maxlifetime'); + + try { + // We use a single MERGE SQL query when supported by the database. + $mergeStmt = $this->getMergeStatement($sessionId, $data, $maxlifetime); + if (null !== $mergeStmt) { + $mergeStmt->execute(); + + return true; + } + + $updateStmt = $this->pdo->prepare( + "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id" + ); + $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $updateStmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $updateStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); + $updateStmt->execute(); + + // When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in + // duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior). + // We can just catch such an error and re-execute the update. This is similar to a serializable + // transaction with retry logic on serialization failures but without the overhead and without possible + // false positives due to longer gap locking. + if (!$updateStmt->rowCount()) { + try { + $insertStmt = $this->pdo->prepare( + "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)" + ); + $insertStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $insertStmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $insertStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $insertStmt->bindValue(':time', time(), \PDO::PARAM_INT); + $insertStmt->execute(); + } catch (\PDOException $e) { + // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys + if (0 === strpos($e->getCode(), '23')) { + $updateStmt->execute(); + } else { + throw $e; + } + } + } + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->commit(); + + while ($unlockStmt = array_shift($this->unlockStatements)) { + $unlockStmt->execute(); + } + + if ($this->gcCalled) { + $this->gcCalled = false; + + // delete the session records that have expired + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol < :time"; + + $stmt = $this->pdo->prepare($sql); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->execute(); + } + + if (false !== $this->dsn) { + $this->pdo = null; // only close lazy-connection + } + + return true; + } + + /** + * Lazy-connects to the database. + * + * @param string $dsn DSN string + */ + private function connect($dsn) + { + $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions); + $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } + + /** + * Helper method to begin a transaction. + * + * Since SQLite does not support row level locks, we have to acquire a reserved lock + * on the database immediately. Because of https://bugs.php.net/42766 we have to create + * such a transaction manually which also means we cannot use PDO::commit or + * PDO::rollback or PDO::inTransaction for SQLite. + * + * Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions + * due to http://www.mysqlperformanceblog.com/2013/12/12/one-more-innodb-gap-lock-to-avoid/ . + * So we change it to READ COMMITTED. + */ + private function beginTransaction() + { + if (!$this->inTransaction) { + if ('sqlite' === $this->driver) { + $this->pdo->exec('BEGIN IMMEDIATE TRANSACTION'); + } else { + if ('mysql' === $this->driver) { + $this->pdo->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED'); + } + $this->pdo->beginTransaction(); + } + $this->inTransaction = true; + } + } + + /** + * Helper method to commit a transaction. + */ + private function commit() + { + if ($this->inTransaction) { + try { + // commit read-write transaction which also releases the lock + if ('sqlite' === $this->driver) { + $this->pdo->exec('COMMIT'); + } else { + $this->pdo->commit(); + } + $this->inTransaction = false; + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + } + + /** + * Helper method to rollback a transaction. + */ + private function rollback() + { + // We only need to rollback if we are in a transaction. Otherwise the resulting + // error would hide the real problem why rollback was called. We might not be + // in a transaction when not using the transactional locking behavior or when + // two callbacks (e.g. destroy and write) are invoked that both fail. + if ($this->inTransaction) { + if ('sqlite' === $this->driver) { + $this->pdo->exec('ROLLBACK'); + } else { + $this->pdo->rollBack(); + } + $this->inTransaction = false; + } + } + + /** + * Reads the session data in respect to the different locking strategies. + * + * We need to make sure we do not return session data that is already considered garbage according + * to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes. + * + * @param string $sessionId Session ID + * + * @return string The session data + */ + private function doRead($sessionId) + { + $this->sessionExpired = false; + + if (self::LOCK_ADVISORY === $this->lockMode) { + $this->unlockStatements[] = $this->doAdvisoryLock($sessionId); + } + + $selectSql = $this->getSelectSql(); + $selectStmt = $this->pdo->prepare($selectSql); + $selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + + do { + $selectStmt->execute(); + $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM); + + if ($sessionRows) { + if ($sessionRows[0][1] + $sessionRows[0][2] < time()) { + $this->sessionExpired = true; + + return ''; + } + + return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0]; + } + + if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { + // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block + // until other connections to the session are committed. + try { + $insertStmt = $this->pdo->prepare( + "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)" + ); + $insertStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $insertStmt->bindValue(':data', '', \PDO::PARAM_LOB); + $insertStmt->bindValue(':lifetime', 0, \PDO::PARAM_INT); + $insertStmt->bindValue(':time', time(), \PDO::PARAM_INT); + $insertStmt->execute(); + } catch (\PDOException $e) { + // Catch duplicate key error because other connection created the session already. + // It would only not be the case when the other connection destroyed the session. + if (0 === strpos($e->getCode(), '23')) { + // Retrieve finished session data written by concurrent connection by restarting the loop. + // We have to start a new transaction as a failed query will mark the current transaction as + // aborted in PostgreSQL and disallow further queries within it. + $this->rollback(); + $this->beginTransaction(); + continue; + } + + throw $e; + } + } + + return ''; + } while (true); + } + + /** + * Executes an application-level lock on the database. + * + * @param string $sessionId Session ID + * + * @return \PDOStatement The statement that needs to be executed later to release the lock + * + * @throws \DomainException When an unsupported PDO driver is used + * + * @todo implement missing advisory locks + * - for oci using DBMS_LOCK.REQUEST + * - for sqlsrv using sp_getapplock with LockOwner = Session + */ + private function doAdvisoryLock($sessionId) + { + switch ($this->driver) { + case 'mysql': + // should we handle the return value? 0 on timeout, null on error + // we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout + $stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)'); + $stmt->bindValue(':key', $sessionId, \PDO::PARAM_STR); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)'); + $releaseStmt->bindValue(':key', $sessionId, \PDO::PARAM_STR); + + return $releaseStmt; + case 'pgsql': + // Obtaining an exclusive session level advisory lock requires an integer key. + // So we convert the HEX representation of the session id to an integer. + // Since integers are signed, we have to skip one hex char to fit in the range. + if (4 === PHP_INT_SIZE) { + $sessionInt1 = hexdec(substr($sessionId, 0, 7)); + $sessionInt2 = hexdec(substr($sessionId, 7, 7)); + + $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key1, :key2)'); + $stmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); + $stmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key1, :key2)'); + $releaseStmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); + $releaseStmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); + } else { + $sessionBigInt = hexdec(substr($sessionId, 0, 15)); + + $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key)'); + $stmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key)'); + $releaseStmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); + } + + return $releaseStmt; + case 'sqlite': + throw new \DomainException('SQLite does not support advisory locks.'); + default: + throw new \DomainException(sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver)); + } + } + + /** + * Return a locking or nonlocking SQL query to read session information. + * + * @return string The SQL string + * + * @throws \DomainException When an unsupported PDO driver is used + */ + private function getSelectSql() + { + if (self::LOCK_TRANSACTIONAL === $this->lockMode) { + $this->beginTransaction(); + + switch ($this->driver) { + case 'mysql': + case 'oci': + case 'pgsql': + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id FOR UPDATE"; + case 'sqlsrv': + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WITH (UPDLOCK, ROWLOCK) WHERE $this->idCol = :id"; + case 'sqlite': + // we already locked when starting transaction + break; + default: + throw new \DomainException(sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver)); + } + } + + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id"; + } + + /** + * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data. + * + * @param string $sessionId Session ID + * @param string $data Encoded session data + * @param int $maxlifetime session.gc_maxlifetime + * + * @return \PDOStatement|null The merge statement or null when not supported + */ + private function getMergeStatement($sessionId, $data, $maxlifetime) + { + $mergeSql = null; + switch (true) { + case 'mysql' === $this->driver: + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". + "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; + break; + case 'oci' === $this->driver: + // DUAL is Oracle specific dummy table + $mergeSql = "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' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '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 + $mergeSql = "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' === $this->driver: + $mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"; + break; + case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='): + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". + "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; + break; + } + + if (null !== $mergeSql) { + $mergeStmt = $this->pdo->prepare($mergeSql); + + if ('sqlsrv' === $this->driver || 'oci' === $this->driver) { + $mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB); + $mergeStmt->bindParam(4, $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(5, time(), \PDO::PARAM_INT); + $mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB); + $mergeStmt->bindParam(7, $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(8, time(), \PDO::PARAM_INT); + } else { + $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $mergeStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT); + } + + return $mergeStmt; + } + } + + /** + * Return a PDO instance. + * + * @return \PDO + */ + protected function getConnection() + { + if (null === $this->pdo) { + $this->connect($this->dsn ?: ini_get('session.save_path')); + } + + return $this->pdo; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php new file mode 100644 index 00000000..d49c36ca --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php @@ -0,0 +1,91 @@ + + * + * 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; + +/** + * Wraps another SessionHandlerInterface to only write the session when it has been modified. + * + * @author Adrien Brault + */ +class WriteCheckSessionHandler implements \SessionHandlerInterface +{ + /** + * @var \SessionHandlerInterface + */ + private $wrappedSessionHandler; + + /** + * @var array sessionId => session + */ + private $readSessions; + + public function __construct(\SessionHandlerInterface $wrappedSessionHandler) + { + $this->wrappedSessionHandler = $wrappedSessionHandler; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return $this->wrappedSessionHandler->close(); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + return $this->wrappedSessionHandler->destroy($sessionId); + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + return $this->wrappedSessionHandler->gc($maxlifetime); + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return $this->wrappedSessionHandler->open($savePath, $sessionName); + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + $session = $this->wrappedSessionHandler->read($sessionId); + + $this->readSessions[$sessionId] = $session; + + return $session; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + if (isset($this->readSessions[$sessionId]) && $data === $this->readSessions[$sessionId]) { + return true; + } + + return $this->wrappedSessionHandler->write($sessionId, $data); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php b/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php new file mode 100644 index 00000000..322dd560 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php @@ -0,0 +1,170 @@ + + * + * 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; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Metadata container. + * + * Adds metadata to the session. + * + * @author Drak + */ +class MetadataBag implements SessionBagInterface +{ + const CREATED = 'c'; + const UPDATED = 'u'; + const LIFETIME = 'l'; + + /** + * @var string + */ + private $name = '__metadata'; + + /** + * @var string + */ + private $storageKey; + + /** + * @var array + */ + protected $meta = array(self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0); + + /** + * Unix timestamp. + * + * @var int + */ + private $lastUsed; + + /** + * @var int + */ + private $updateThreshold; + + /** + * Constructor. + * + * @param string $storageKey The key used to store bag in the session + * @param int $updateThreshold The time to wait between two UPDATED updates + */ + public function __construct($storageKey = '_sf2_meta', $updateThreshold = 0) + { + $this->storageKey = $storageKey; + $this->updateThreshold = $updateThreshold; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$array) + { + $this->meta = &$array; + + if (isset($array[self::CREATED])) { + $this->lastUsed = $this->meta[self::UPDATED]; + + $timeStamp = time(); + if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) { + $this->meta[self::UPDATED] = $timeStamp; + } + } else { + $this->stampCreated(); + } + } + + /** + * Gets the lifetime that the session cookie was set with. + * + * @return int + */ + public function getLifetime() + { + return $this->meta[self::LIFETIME]; + } + + /** + * Stamps a new session's metadata. + * + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + */ + public function stampNew($lifetime = null) + { + $this->stampCreated($lifetime); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * Gets the created timestamp metadata. + * + * @return int Unix timestamp + */ + public function getCreated() + { + return $this->meta[self::CREATED]; + } + + /** + * Gets the last used metadata. + * + * @return int Unix timestamp + */ + public function getLastUsed() + { + return $this->lastUsed; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // nothing to do + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * Sets name. + * + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + private function stampCreated($lifetime = null) + { + $timeStamp = time(); + $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; + $this->meta[self::LIFETIME] = (null === $lifetime) ? ini_get('session.cookie_lifetime') : $lifetime; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php new file mode 100644 index 00000000..348fd230 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php @@ -0,0 +1,268 @@ + + * + * 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; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * MockArraySessionStorage mocks the session for unit tests. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle. + * + * When doing functional testing, you should use MockFileSessionStorage instead. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Drak + */ +class MockArraySessionStorage implements SessionStorageInterface +{ + /** + * @var string + */ + protected $id = ''; + + /** + * @var string + */ + protected $name; + + /** + * @var bool + */ + protected $started = false; + + /** + * @var bool + */ + protected $closed = false; + + /** + * @var array + */ + protected $data = array(); + + /** + * @var MetadataBag + */ + protected $metadataBag; + + /** + * @var array|SessionBagInterface[] + */ + protected $bags = array(); + + /** + * Constructor. + * + * @param string $name Session name + * @param MetadataBag $metaBag MetadataBag instance + */ + public function __construct($name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + $this->name = $name; + $this->setMetadataBag($metaBag); + } + + /** + * Sets the session data. + * + * @param array $array + */ + public function setSessionData(array $array) + { + $this->data = $array; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (empty($this->id)) { + $this->id = $this->generateId(); + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false, $lifetime = null) + { + if (!$this->started) { + $this->start(); + } + + $this->metadataBag->stampNew($lifetime); + $this->id = $this->generateId(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + if ($this->started) { + throw new \LogicException('Cannot set session ID after the session has started.'); + } + + $this->id = $id; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function save() + { + if (!$this->started || $this->closed) { + throw new \RuntimeException('Trying to save a session that was not started yet or was already closed'); + } + // nothing to do since we don't persist the session data + $this->closed = false; + $this->started = false; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $this->data = array(); + + // reconnect the bags to the session + $this->loadSession(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->bags[$bag->getName()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag($name) + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name)); + } + + if (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->started; + } + + /** + * Sets the MetadataBag. + * + * @param MetadataBag $bag + */ + public function setMetadataBag(MetadataBag $bag = null) + { + if (null === $bag) { + $bag = new MetadataBag(); + } + + $this->metadataBag = $bag; + } + + /** + * Gets the MetadataBag. + * + * @return MetadataBag + */ + public function getMetadataBag() + { + return $this->metadataBag; + } + + /** + * Generates a session ID. + * + * This doesn't need to be particularly cryptographically secure since this is just + * a mock. + * + * @return string + */ + protected function generateId() + { + return hash('sha256', uniqid('ss_mock_', true)); + } + + protected function loadSession() + { + $bags = array_merge($this->bags, array($this->metadataBag)); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : array(); + $bag->initialize($this->data[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php new file mode 100644 index 00000000..71f9e555 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php @@ -0,0 +1,138 @@ + + * + * 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; + +/** + * MockFileSessionStorage is used to mock sessions for + * functional testing when done in a single PHP process. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle and this class does + * not pollute any session related globals, including session_*() functions + * or session.* PHP ini directives. + * + * @author Drak + */ +class MockFileSessionStorage extends MockArraySessionStorage +{ + /** + * @var string + */ + private $savePath; + + /** + * Constructor. + * + * @param string $savePath Path of directory to save session files + * @param string $name Session name + * @param MetadataBag $metaBag MetadataBag instance + */ + public function __construct($savePath = null, $name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + if (null === $savePath) { + $savePath = sys_get_temp_dir(); + } + + if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) { + throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $savePath)); + } + + $this->savePath = $savePath; + + parent::__construct($name, $metaBag); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (!$this->id) { + $this->id = $this->generateId(); + } + + $this->read(); + + $this->started = true; + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false, $lifetime = null) + { + if (!$this->started) { + $this->start(); + } + + if ($destroy) { + $this->destroy(); + } + + return parent::regenerate($destroy, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function save() + { + if (!$this->started) { + throw new \RuntimeException('Trying to save a session that was not started yet or was already closed'); + } + + file_put_contents($this->getFilePath(), serialize($this->data)); + + // this is needed for Silex, where the session object is re-used across requests + // in functional tests. In Symfony, the container is rebooted, so we don't have + // this issue + $this->started = false; + } + + /** + * Deletes a session from persistent storage. + * Deliberately leaves session data in memory intact. + */ + private function destroy() + { + if (is_file($this->getFilePath())) { + unlink($this->getFilePath()); + } + } + + /** + * Calculate path to file. + * + * @return string File path + */ + private function getFilePath() + { + return $this->savePath.'/'.$this->id.'.mocksess'; + } + + /** + * Reads session from storage and loads session. + */ + private function read() + { + $filePath = $this->getFilePath(); + $this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : array(); + + $this->loadSession(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php new file mode 100644 index 00000000..97161b8d --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php @@ -0,0 +1,423 @@ + + * + * 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; + +use Symfony\Component\Debug\Exception\ContextErrorException; +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; + +/** + * This provides a base class for session attribute storage. + * + * @author Drak + */ +class NativeSessionStorage implements SessionStorageInterface +{ + /** + * Array of SessionBagInterface. + * + * @var SessionBagInterface[] + */ + protected $bags; + + /** + * @var bool + */ + protected $started = false; + + /** + * @var bool + */ + protected $closed = false; + + /** + * @var AbstractProxy + */ + protected $saveHandler; + + /** + * @var MetadataBag + */ + protected $metadataBag; + + /** + * Constructor. + * + * Depending on how you want the storage driver to behave you probably + * want to override this constructor entirely. + * + * List of options for $options array with their defaults. + * + * @see http://php.net/session.configuration for options + * but we omit 'session.' from the beginning of the keys for convenience. + * + * ("auto_start", is not supported as it tells PHP to start a session before + * PHP starts to execute user-land code. Setting during runtime has no effect). + * + * cache_limiter, "" (use "0" to prevent headers from being sent entirely). + * cookie_domain, "" + * cookie_httponly, "" + * cookie_lifetime, "0" + * cookie_path, "/" + * cookie_secure, "" + * entropy_file, "" + * entropy_length, "0" + * gc_divisor, "100" + * gc_maxlifetime, "1440" + * gc_probability, "1" + * hash_bits_per_character, "4" + * hash_function, "0" + * name, "PHPSESSID" + * referer_check, "" + * serialize_handler, "php" + * use_strict_mode, "0" + * use_cookies, "1" + * use_only_cookies, "1" + * use_trans_sid, "0" + * upload_progress.enabled, "1" + * upload_progress.cleanup, "1" + * upload_progress.prefix, "upload_progress_" + * upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS" + * upload_progress.freq, "1%" + * upload_progress.min-freq, "1" + * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset=" + * sid_length, "32" + * sid_bits_per_character, "5" + * trans_sid_hosts, $_SERVER['HTTP_HOST'] + * trans_sid_tags, "a=href,area=href,frame=src,form=" + * + * @param array $options Session configuration options + * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler + * @param MetadataBag $metaBag MetadataBag + */ + public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null) + { + session_cache_limiter(''); // disable by default because it's managed by HeaderBag (if used) + ini_set('session.use_cookies', 1); + + session_register_shutdown(); + + $this->setMetadataBag($metaBag); + $this->setOptions($options); + $this->setSaveHandler($handler); + } + + /** + * Gets the save handler instance. + * + * @return AbstractProxy + */ + public function getSaveHandler() + { + return $this->saveHandler; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (\PHP_SESSION_ACTIVE === session_status()) { + throw new \RuntimeException('Failed to start the session: already started by PHP.'); + } + + if (ini_get('session.use_cookies') && headers_sent($file, $line)) { + throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line)); + } + + // ok to try and start the session + if (!session_start()) { + throw new \RuntimeException('Failed to start the session'); + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->saveHandler->getId(); + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + $this->saveHandler->setId($id); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->saveHandler->getName(); + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->saveHandler->setName($name); + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false, $lifetime = null) + { + // Cannot regenerate the session ID for non-active sessions. + if (\PHP_SESSION_ACTIVE !== session_status()) { + return false; + } + + if (null !== $lifetime) { + ini_set('session.cookie_lifetime', $lifetime); + } + + if ($destroy) { + $this->metadataBag->stampNew(); + } + + $isRegenerated = session_regenerate_id($destroy); + + // The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it. + // @see https://bugs.php.net/bug.php?id=70013 + $this->loadSession(); + + return $isRegenerated; + } + + /** + * {@inheritdoc} + */ + public function save() + { + // Register custom error handler to catch a possible failure warning during session write + set_error_handler(function ($errno, $errstr, $errfile, $errline, $errcontext) { + throw new ContextErrorException($errstr, $errno, E_WARNING, $errfile, $errline, $errcontext); + }, E_WARNING); + + try { + session_write_close(); + restore_error_handler(); + } catch (ContextErrorException $e) { + // The default PHP error message is not very helpful, as it does not give any information on the current save handler. + // Therefore, we catch this error and trigger a warning with a better error message + $handler = $this->getSaveHandler(); + if ($handler instanceof SessionHandlerProxy) { + $handler = $handler->getHandler(); + } + + restore_error_handler(); + trigger_error(sprintf('session_write_close(): Failed to write session data with %s handler', get_class($handler)), E_USER_WARNING); + } + + $this->closed = true; + $this->started = false; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $_SESSION = array(); + + // reconnect the bags to the session + $this->loadSession(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + if ($this->started) { + throw new \LogicException('Cannot register a bag when the session is already started.'); + } + + $this->bags[$bag->getName()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag($name) + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name)); + } + + if ($this->saveHandler->isActive() && !$this->started) { + $this->loadSession(); + } elseif (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + /** + * Sets the MetadataBag. + * + * @param MetadataBag $metaBag + */ + public function setMetadataBag(MetadataBag $metaBag = null) + { + if (null === $metaBag) { + $metaBag = new MetadataBag(); + } + + $this->metadataBag = $metaBag; + } + + /** + * Gets the MetadataBag. + * + * @return MetadataBag + */ + public function getMetadataBag() + { + return $this->metadataBag; + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->started; + } + + /** + * Sets session.* ini variables. + * + * For convenience we omit 'session.' from the beginning of the keys. + * Explicitly ignores other ini keys. + * + * @param array $options Session ini directives array(key => value) + * + * @see http://php.net/session.configuration + */ + public function setOptions(array $options) + { + $validOptions = array_flip(array( + 'cache_limiter', 'cookie_domain', 'cookie_httponly', + 'cookie_lifetime', 'cookie_path', 'cookie_secure', + 'entropy_file', 'entropy_length', 'gc_divisor', + 'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character', + 'hash_function', 'name', 'referer_check', + 'serialize_handler', 'use_strict_mode', 'use_cookies', + 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', + 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', + 'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags', + 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags', + )); + + foreach ($options as $key => $value) { + if (isset($validOptions[$key])) { + ini_set('session.'.$key, $value); + } + } + } + + /** + * Registers session save handler as a PHP session handler. + * + * To use internal PHP session save handlers, override this method using ini_set with + * session.save_handler and session.save_path e.g. + * + * ini_set('session.save_handler', 'files'); + * ini_set('session.save_path', '/tmp'); + * + * or pass in a NativeSessionHandler instance which configures session.save_handler in the + * constructor, for a template see NativeFileSessionHandler or use handlers in + * composer package drak/native-session + * + * @see http://php.net/session-set-save-handler + * @see http://php.net/sessionhandlerinterface + * @see http://php.net/sessionhandler + * @see http://github.com/drak/NativeSession + * + * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $saveHandler + * + * @throws \InvalidArgumentException + */ + public function setSaveHandler($saveHandler = null) + { + if (!$saveHandler instanceof AbstractProxy && + !$saveHandler instanceof NativeSessionHandler && + !$saveHandler instanceof \SessionHandlerInterface && + null !== $saveHandler) { + throw new \InvalidArgumentException('Must be instance of AbstractProxy or NativeSessionHandler; implement \SessionHandlerInterface; or be null.'); + } + + // Wrap $saveHandler in proxy and prevent double wrapping of proxy + if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) { + $saveHandler = new SessionHandlerProxy($saveHandler); + } elseif (!$saveHandler instanceof AbstractProxy) { + $saveHandler = new SessionHandlerProxy(new \SessionHandler()); + } + $this->saveHandler = $saveHandler; + + if ($this->saveHandler instanceof \SessionHandlerInterface) { + session_set_save_handler($this->saveHandler, false); + } + } + + /** + * Load the session with attributes. + * + * After starting the session, PHP retrieves the session from whatever handlers + * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()). + * PHP takes the return value from the read() handler, unserializes it + * and populates $_SESSION with the result automatically. + * + * @param array|null $session + */ + protected function loadSession(array &$session = null) + { + if (null === $session) { + $session = &$_SESSION; + } + + $bags = array_merge($this->bags, array($this->metadataBag)); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $session[$key] = isset($session[$key]) ? $session[$key] : array(); + $bag->initialize($session[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php new file mode 100644 index 00000000..6f02a7fd --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php @@ -0,0 +1,64 @@ + + * + * 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; + +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; + +/** + * Allows session to be started by PHP and managed by Symfony. + * + * @author Drak + */ +class PhpBridgeSessionStorage extends NativeSessionStorage +{ + /** + * Constructor. + * + * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler + * @param MetadataBag $metaBag MetadataBag + */ + public function __construct($handler = null, MetadataBag $metaBag = null) + { + $this->setMetadataBag($metaBag); + $this->setSaveHandler($handler); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags and nothing else that may be set + // since the purpose of this driver is to share a handler + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // reconnect the bags to the session + $this->loadSession(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php new file mode 100644 index 00000000..a7478656 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php @@ -0,0 +1,124 @@ + + * + * 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\Proxy; + +/** + * AbstractProxy. + * + * @author Drak + */ +abstract class AbstractProxy +{ + /** + * Flag if handler wraps an internal PHP session handler (using \SessionHandler). + * + * @var bool + */ + protected $wrapper = false; + + /** + * @var string + */ + protected $saveHandlerName; + + /** + * Gets the session.save_handler name. + * + * @return string + */ + public function getSaveHandlerName() + { + return $this->saveHandlerName; + } + + /** + * Is this proxy handler and instance of \SessionHandlerInterface. + * + * @return bool + */ + public function isSessionHandlerInterface() + { + return $this instanceof \SessionHandlerInterface; + } + + /** + * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. + * + * @return bool + */ + public function isWrapper() + { + return $this->wrapper; + } + + /** + * Has a session started? + * + * @return bool + */ + public function isActive() + { + return \PHP_SESSION_ACTIVE === session_status(); + } + + /** + * Gets the session ID. + * + * @return string + */ + public function getId() + { + return session_id(); + } + + /** + * Sets the session ID. + * + * @param string $id + * + * @throws \LogicException + */ + public function setId($id) + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the ID of an active session'); + } + + session_id($id); + } + + /** + * Gets the session name. + * + * @return string + */ + public function getName() + { + return session_name(); + } + + /** + * Sets the session name. + * + * @param string $name + * + * @throws \LogicException + */ + public function setName($name) + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the name of an active session'); + } + + session_name($name); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php new file mode 100644 index 00000000..0db34aa2 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php @@ -0,0 +1,41 @@ + + * + * 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\Proxy; + +/** + * NativeProxy. + * + * This proxy is built-in session handlers in PHP 5.3.x + * + * @author Drak + */ +class NativeProxy extends AbstractProxy +{ + /** + * Constructor. + */ + public function __construct() + { + // this makes an educated guess as to what the handler is since it should already be set. + $this->saveHandlerName = ini_get('session.save_handler'); + } + + /** + * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. + * + * @return bool False + */ + public function isWrapper() + { + return false; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php new file mode 100644 index 00000000..68ed713c --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -0,0 +1,95 @@ + + * + * 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\Proxy; + +/** + * SessionHandler proxy. + * + * @author Drak + */ +class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface +{ + /** + * @var \SessionHandlerInterface + */ + protected $handler; + + /** + * Constructor. + * + * @param \SessionHandlerInterface $handler + */ + public function __construct(\SessionHandlerInterface $handler) + { + $this->handler = $handler; + $this->wrapper = ($handler instanceof \SessionHandler); + $this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user'; + } + + /** + * @return \SessionHandlerInterface + */ + public function getHandler() + { + return $this->handler; + } + + // \SessionHandlerInterface + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return (bool) $this->handler->open($savePath, $sessionName); + } + + /** + * {@inheritdoc} + */ + public function close() + { + return (bool) $this->handler->close(); + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + return (string) $this->handler->read($sessionId); + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + return (bool) $this->handler->write($sessionId, $data); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + return (bool) $this->handler->destroy($sessionId); + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + return (bool) $this->handler->gc($maxlifetime); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php b/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php new file mode 100644 index 00000000..34f6c463 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php @@ -0,0 +1,139 @@ + + * + * 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; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * StorageInterface. + * + * @author Fabien Potencier + * @author Drak + */ +interface SessionStorageInterface +{ + /** + * Starts the session. + * + * @return bool True if started + * + * @throws \RuntimeException If something goes wrong starting the session. + */ + public function start(); + + /** + * Checks if the session is started. + * + * @return bool True if started, false otherwise + */ + public function isStarted(); + + /** + * Returns the session ID. + * + * @return string The session ID or empty + */ + public function getId(); + + /** + * Sets the session ID. + * + * @param string $id + */ + public function setId($id); + + /** + * Returns the session name. + * + * @return mixed The session name + */ + public function getName(); + + /** + * Sets the session name. + * + * @param string $name + */ + public function setName($name); + + /** + * Regenerates id that represents this storage. + * + * This method must invoke session_regenerate_id($destroy) unless + * this interface is used for a storage object designed for unit + * or functional testing where a real PHP session would interfere + * with testing. + * + * Note regenerate+destroy should not clear the session data in memory + * only delete the session data from persistent storage. + * + * Care: When regenerating the session ID no locking is involved in PHP's + * session design. See https://bugs.php.net/bug.php?id=61470 for a discussion. + * So you must make sure the regenerated session is saved BEFORE sending the + * headers with the new ID. Symfony's HttpKernel offers a listener for this. + * See Symfony\Component\HttpKernel\EventListener\SaveSessionListener. + * Otherwise session data could get lost again for concurrent requests with the + * new ID. One result could be that you get logged out after just logging in. + * + * @param bool $destroy Destroy session when regenerating? + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool True if session regenerated, false if error + * + * @throws \RuntimeException If an error occurs while regenerating this storage + */ + public function regenerate($destroy = false, $lifetime = null); + + /** + * Force the session to be saved and closed. + * + * This method must invoke session_write_close() unless this interface is + * used for a storage object design for unit or functional testing where + * a real PHP session would interfere with testing, in which case + * it should actually persist the session data if required. + * + * @throws \RuntimeException If the session is saved without being started, or if the session + * is already closed. + */ + public function save(); + + /** + * Clear all session data in memory. + */ + public function clear(); + + /** + * Gets a SessionBagInterface by name. + * + * @param string $name + * + * @return SessionBagInterface + * + * @throws \InvalidArgumentException If the bag does not exist + */ + public function getBag($name); + + /** + * Registers a SessionBagInterface for use. + * + * @param SessionBagInterface $bag + */ + public function registerBag(SessionBagInterface $bag); + + /** + * @return MetadataBag + */ + public function getMetadataBag(); +} diff --git a/vendor/symfony/http-foundation/StreamedResponse.php b/vendor/symfony/http-foundation/StreamedResponse.php new file mode 100644 index 00000000..92853130 --- /dev/null +++ b/vendor/symfony/http-foundation/StreamedResponse.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * StreamedResponse represents a streamed HTTP response. + * + * A StreamedResponse uses a callback for its content. + * + * The callback should use the standard PHP functions like echo + * to stream the response back to the client. The flush() method + * can also be used if needed. + * + * @see flush() + * + * @author Fabien Potencier + */ +class StreamedResponse extends Response +{ + protected $callback; + protected $streamed; + private $headersSent; + + /** + * Constructor. + * + * @param callable|null $callback A valid PHP callback or null to set it later + * @param int $status The response status code + * @param array $headers An array of response headers + */ + public function __construct(callable $callback = null, $status = 200, $headers = array()) + { + parent::__construct(null, $status, $headers); + + if (null !== $callback) { + $this->setCallback($callback); + } + $this->streamed = false; + $this->headersSent = false; + } + + /** + * Factory method for chainability. + * + * @param callable|null $callback A valid PHP callback or null to set it later + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + */ + public static function create($callback = null, $status = 200, $headers = array()) + { + return new static($callback, $status, $headers); + } + + /** + * Sets the PHP callback associated with this Response. + * + * @param callable $callback A valid PHP callback + */ + public function setCallback(callable $callback) + { + $this->callback = $callback; + } + + /** + * {@inheritdoc} + * + * This method only sends the headers once. + */ + public function sendHeaders() + { + if ($this->headersSent) { + return; + } + + $this->headersSent = true; + + parent::sendHeaders(); + } + + /** + * {@inheritdoc} + * + * This method only sends the content once. + */ + public function sendContent() + { + if ($this->streamed) { + return; + } + + $this->streamed = true; + + if (null === $this->callback) { + throw new \LogicException('The Response callback must not be null.'); + } + + call_user_func($this->callback); + } + + /** + * {@inheritdoc} + * + * @throws \LogicException when the content is not null + */ + public function setContent($content) + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a StreamedResponse instance.'); + } + } + + /** + * {@inheritdoc} + * + * @return false + */ + public function getContent() + { + return false; + } +} diff --git a/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php b/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php new file mode 100644 index 00000000..cb43bb35 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php @@ -0,0 +1,113 @@ + + * + * 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\AcceptHeaderItem; + +class AcceptHeaderItemTest extends TestCase +{ + /** + * @dataProvider provideFromStringData + */ + public function testFromString($string, $value, array $attributes) + { + $item = AcceptHeaderItem::fromString($string); + $this->assertEquals($value, $item->getValue()); + $this->assertEquals($attributes, $item->getAttributes()); + } + + public function provideFromStringData() + { + return array( + array( + 'text/html', + 'text/html', array(), + ), + array( + '"this;should,not=matter"', + 'this;should,not=matter', array(), + ), + array( + "text/plain; charset=utf-8;param=\"this;should,not=matter\";\tfootnotes=true", + 'text/plain', array('charset' => 'utf-8', 'param' => 'this;should,not=matter', 'footnotes' => 'true'), + ), + array( + '"this;should,not=matter";charset=utf-8', + 'this;should,not=matter', array('charset' => 'utf-8'), + ), + ); + } + + /** + * @dataProvider provideToStringData + */ + public function testToString($value, array $attributes, $string) + { + $item = new AcceptHeaderItem($value, $attributes); + $this->assertEquals($string, (string) $item); + } + + public function provideToStringData() + { + return array( + array( + 'text/html', array(), + 'text/html', + ), + array( + 'text/plain', array('charset' => 'utf-8', 'param' => 'this;should,not=matter', 'footnotes' => 'true'), + 'text/plain;charset=utf-8;param="this;should,not=matter";footnotes=true', + ), + ); + } + + public function testValue() + { + $item = new AcceptHeaderItem('value', array()); + $this->assertEquals('value', $item->getValue()); + + $item->setValue('new value'); + $this->assertEquals('new value', $item->getValue()); + + $item->setValue(1); + $this->assertEquals('1', $item->getValue()); + } + + public function testQuality() + { + $item = new AcceptHeaderItem('value', array()); + $this->assertEquals(1.0, $item->getQuality()); + + $item->setQuality(0.5); + $this->assertEquals(0.5, $item->getQuality()); + + $item->setAttribute('q', 0.75); + $this->assertEquals(0.75, $item->getQuality()); + $this->assertFalse($item->hasAttribute('q')); + } + + public function testAttribute() + { + $item = new AcceptHeaderItem('value', array()); + $this->assertEquals(array(), $item->getAttributes()); + $this->assertFalse($item->hasAttribute('test')); + $this->assertNull($item->getAttribute('test')); + $this->assertEquals('default', $item->getAttribute('test', 'default')); + + $item->setAttribute('test', 'value'); + $this->assertEquals(array('test' => 'value'), $item->getAttributes()); + $this->assertTrue($item->hasAttribute('test')); + $this->assertEquals('value', $item->getAttribute('test')); + $this->assertEquals('value', $item->getAttribute('test', 'default')); + } +} diff --git a/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php b/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php new file mode 100644 index 00000000..9929eac2 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php @@ -0,0 +1,103 @@ + + * + * 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\AcceptHeader; +use Symfony\Component\HttpFoundation\AcceptHeaderItem; + +class AcceptHeaderTest extends TestCase +{ + public function testFirst() + { + $header = AcceptHeader::fromString('text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c'); + $this->assertSame('text/html', $header->first()->getValue()); + } + + /** + * @dataProvider provideFromStringData + */ + public function testFromString($string, array $items) + { + $header = AcceptHeader::fromString($string); + $parsed = array_values($header->all()); + // reset index since the fixtures don't have them set + foreach ($parsed as $item) { + $item->setIndex(0); + } + $this->assertEquals($items, $parsed); + } + + public function provideFromStringData() + { + return array( + array('', array()), + array('gzip', array(new AcceptHeaderItem('gzip'))), + array('gzip,deflate,sdch', array(new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch'))), + array("gzip, deflate\t,sdch", array(new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch'))), + array('"this;should,not=matter"', array(new AcceptHeaderItem('this;should,not=matter'))), + ); + } + + /** + * @dataProvider provideToStringData + */ + public function testToString(array $items, $string) + { + $header = new AcceptHeader($items); + $this->assertEquals($string, (string) $header); + } + + public function provideToStringData() + { + return array( + array(array(), ''), + array(array(new AcceptHeaderItem('gzip')), 'gzip'), + array(array(new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch')), 'gzip,deflate,sdch'), + array(array(new AcceptHeaderItem('this;should,not=matter')), 'this;should,not=matter'), + ); + } + + /** + * @dataProvider provideFilterData + */ + public function testFilter($string, $filter, array $values) + { + $header = AcceptHeader::fromString($string)->filter($filter); + $this->assertEquals($values, array_keys($header->all())); + } + + public function provideFilterData() + { + return array( + array('fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4', '/fr.*/', array('fr-FR', 'fr')), + ); + } + + /** + * @dataProvider provideSortingData + */ + public function testSorting($string, array $values) + { + $header = AcceptHeader::fromString($string); + $this->assertEquals($values, array_keys($header->all())); + } + + public function provideSortingData() + { + return array( + 'quality has priority' => array('*;q=0.3,ISO-8859-1,utf-8;q=0.7', array('ISO-8859-1', 'utf-8', '*')), + 'order matters when q is equal' => array('*;q=0.3,ISO-8859-1;q=0.7,utf-8;q=0.7', array('ISO-8859-1', 'utf-8', '*')), + 'order matters when q is equal2' => array('*;q=0.3,utf-8;q=0.7,ISO-8859-1;q=0.7', array('utf-8', 'ISO-8859-1', '*')), + ); + } +} diff --git a/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php b/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php new file mode 100644 index 00000000..157ab90e --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php @@ -0,0 +1,93 @@ + + * + * 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\ApacheRequest; + +class ApacheRequestTest extends TestCase +{ + /** + * @dataProvider provideServerVars + */ + public function testUriMethods($server, $expectedRequestUri, $expectedBaseUrl, $expectedPathInfo) + { + $request = new ApacheRequest(); + $request->server->replace($server); + + $this->assertEquals($expectedRequestUri, $request->getRequestUri(), '->getRequestUri() is correct'); + $this->assertEquals($expectedBaseUrl, $request->getBaseUrl(), '->getBaseUrl() is correct'); + $this->assertEquals($expectedPathInfo, $request->getPathInfo(), '->getPathInfo() is correct'); + } + + public function provideServerVars() + { + return array( + array( + array( + 'REQUEST_URI' => '/foo/app_dev.php/bar', + 'SCRIPT_NAME' => '/foo/app_dev.php', + 'PATH_INFO' => '/bar', + ), + '/foo/app_dev.php/bar', + '/foo/app_dev.php', + '/bar', + ), + array( + array( + 'REQUEST_URI' => '/foo/bar', + 'SCRIPT_NAME' => '/foo/app_dev.php', + ), + '/foo/bar', + '/foo', + '/bar', + ), + array( + array( + 'REQUEST_URI' => '/app_dev.php/foo/bar', + 'SCRIPT_NAME' => '/app_dev.php', + 'PATH_INFO' => '/foo/bar', + ), + '/app_dev.php/foo/bar', + '/app_dev.php', + '/foo/bar', + ), + array( + array( + 'REQUEST_URI' => '/foo/bar', + 'SCRIPT_NAME' => '/app_dev.php', + ), + '/foo/bar', + '', + '/foo/bar', + ), + array( + array( + 'REQUEST_URI' => '/app_dev.php', + 'SCRIPT_NAME' => '/app_dev.php', + ), + '/app_dev.php', + '/app_dev.php', + '/', + ), + array( + array( + 'REQUEST_URI' => '/', + 'SCRIPT_NAME' => '/app_dev.php', + ), + '/', + '', + '/', + ), + ); + } +} diff --git a/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php b/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php new file mode 100644 index 00000000..1b9e5899 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php @@ -0,0 +1,352 @@ + + * + * 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 Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\File\Stream; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Symfony\Component\HttpFoundation\Tests\File\FakeFile; + +class BinaryFileResponseTest extends ResponseTestCase +{ + public function testConstruction() + { + $file = __DIR__.'/../README.md'; + $response = new BinaryFileResponse($file, 404, array('X-Header' => 'Foo'), true, null, true, true); + $this->assertEquals(404, $response->getStatusCode()); + $this->assertEquals('Foo', $response->headers->get('X-Header')); + $this->assertTrue($response->headers->has('ETag')); + $this->assertTrue($response->headers->has('Last-Modified')); + $this->assertFalse($response->headers->has('Content-Disposition')); + + $response = BinaryFileResponse::create($file, 404, array(), true, ResponseHeaderBag::DISPOSITION_INLINE); + $this->assertEquals(404, $response->getStatusCode()); + $this->assertFalse($response->headers->has('ETag')); + $this->assertEquals('inline; filename="README.md"', $response->headers->get('Content-Disposition')); + } + + public function testConstructWithNonAsciiFilename() + { + touch(sys_get_temp_dir().'/fööö.html'); + + $response = new BinaryFileResponse(sys_get_temp_dir().'/fööö.html', 200, array(), true, 'attachment'); + + @unlink(sys_get_temp_dir().'/fööö.html'); + + $this->assertSame('fööö.html', $response->getFile()->getFilename()); + } + + /** + * @expectedException \LogicException + */ + public function testSetContent() + { + $response = new BinaryFileResponse(__FILE__); + $response->setContent('foo'); + } + + public function testGetContent() + { + $response = new BinaryFileResponse(__FILE__); + $this->assertFalse($response->getContent()); + } + + public function testSetContentDispositionGeneratesSafeFallbackFilename() + { + $response = new BinaryFileResponse(__FILE__); + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'föö.html'); + + $this->assertSame('attachment; filename="f__.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html', $response->headers->get('Content-Disposition')); + } + + public function testSetContentDispositionGeneratesSafeFallbackFilenameForWronglyEncodedFilename() + { + $response = new BinaryFileResponse(__FILE__); + + $iso88591EncodedFilename = utf8_decode('föö.html'); + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $iso88591EncodedFilename); + + // the parameter filename* is invalid in this case (rawurldecode('f%F6%F6') does not provide a UTF-8 string but an ISO-8859-1 encoded one) + $this->assertSame('attachment; filename="f__.html"; filename*=utf-8\'\'f%F6%F6.html', $response->headers->get('Content-Disposition')); + } + + /** + * @dataProvider provideRanges + */ + public function testRequests($requestRange, $offset, $length, $responseRange) + { + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag(); + + // do a request to get the ETag + $request = Request::create('/'); + $response->prepare($request); + $etag = $response->headers->get('ETag'); + + // prepare a request for a range of the testing file + $request = Request::create('/'); + $request->headers->set('If-Range', $etag); + $request->headers->set('Range', $requestRange); + + $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r'); + fseek($file, $offset); + $data = fread($file, $length); + fclose($file); + + $this->expectOutputString($data); + $response = clone $response; + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(206, $response->getStatusCode()); + $this->assertEquals($responseRange, $response->headers->get('Content-Range')); + $this->assertSame($length, $response->headers->get('Content-Length')); + } + + /** + * @dataProvider provideRanges + */ + public function testRequestsWithoutEtag($requestRange, $offset, $length, $responseRange) + { + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream')); + + // do a request to get the LastModified + $request = Request::create('/'); + $response->prepare($request); + $lastModified = $response->headers->get('Last-Modified'); + + // prepare a request for a range of the testing file + $request = Request::create('/'); + $request->headers->set('If-Range', $lastModified); + $request->headers->set('Range', $requestRange); + + $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r'); + fseek($file, $offset); + $data = fread($file, $length); + fclose($file); + + $this->expectOutputString($data); + $response = clone $response; + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(206, $response->getStatusCode()); + $this->assertEquals($responseRange, $response->headers->get('Content-Range')); + } + + public function provideRanges() + { + return array( + array('bytes=1-4', 1, 4, 'bytes 1-4/35'), + array('bytes=-5', 30, 5, 'bytes 30-34/35'), + array('bytes=30-', 30, 5, 'bytes 30-34/35'), + array('bytes=30-30', 30, 1, 'bytes 30-30/35'), + array('bytes=30-34', 30, 5, 'bytes 30-34/35'), + ); + } + + public function testRangeRequestsWithoutLastModifiedDate() + { + // prevent auto last modified + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'), true, null, false, false); + + // prepare a request for a range of the testing file + $request = Request::create('/'); + $request->headers->set('If-Range', date('D, d M Y H:i:s').' GMT'); + $request->headers->set('Range', 'bytes=1-4'); + + $this->expectOutputString(file_get_contents(__DIR__.'/File/Fixtures/test.gif')); + $response = clone $response; + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertNull($response->headers->get('Content-Range')); + } + + /** + * @dataProvider provideFullFileRanges + */ + public function testFullFileRequests($requestRange) + { + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag(); + + // prepare a request for a range of the testing file + $request = Request::create('/'); + $request->headers->set('Range', $requestRange); + + $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r'); + $data = fread($file, 35); + fclose($file); + + $this->expectOutputString($data); + $response = clone $response; + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(200, $response->getStatusCode()); + } + + public function provideFullFileRanges() + { + return array( + array('bytes=0-'), + array('bytes=0-34'), + array('bytes=-35'), + // Syntactical invalid range-request should also return the full resource + array('bytes=20-10'), + array('bytes=50-40'), + ); + } + + /** + * @dataProvider provideInvalidRanges + */ + public function testInvalidRequests($requestRange) + { + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag(); + + // prepare a request for a range of the testing file + $request = Request::create('/'); + $request->headers->set('Range', $requestRange); + + $response = clone $response; + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(416, $response->getStatusCode()); + $this->assertEquals('bytes */35', $response->headers->get('Content-Range')); + } + + public function provideInvalidRanges() + { + return array( + array('bytes=-40'), + array('bytes=30-40'), + ); + } + + /** + * @dataProvider provideXSendfileFiles + */ + public function testXSendfile($file) + { + $request = Request::create('/'); + $request->headers->set('X-Sendfile-Type', 'X-Sendfile'); + + BinaryFileResponse::trustXSendfileTypeHeader(); + $response = BinaryFileResponse::create($file, 200, array('Content-Type' => 'application/octet-stream')); + $response->prepare($request); + + $this->expectOutputString(''); + $response->sendContent(); + + $this->assertContains('README.md', $response->headers->get('X-Sendfile')); + } + + public function provideXSendfileFiles() + { + return array( + array(__DIR__.'/../README.md'), + array('file://'.__DIR__.'/../README.md'), + ); + } + + /** + * @dataProvider getSampleXAccelMappings + */ + public function testXAccelMapping($realpath, $mapping, $virtual) + { + $request = Request::create('/'); + $request->headers->set('X-Sendfile-Type', 'X-Accel-Redirect'); + $request->headers->set('X-Accel-Mapping', $mapping); + + $file = new FakeFile($realpath, __DIR__.'/File/Fixtures/test'); + + BinaryFileResponse::trustXSendfileTypeHeader(); + $response = new BinaryFileResponse($file, 200, array('Content-Type' => 'application/octet-stream')); + $reflection = new \ReflectionObject($response); + $property = $reflection->getProperty('file'); + $property->setAccessible(true); + $property->setValue($response, $file); + + $response->prepare($request); + $this->assertEquals($virtual, $response->headers->get('X-Accel-Redirect')); + } + + public function testDeleteFileAfterSend() + { + $request = Request::create('/'); + + $path = __DIR__.'/File/Fixtures/to_delete'; + touch($path); + $realPath = realpath($path); + $this->assertFileExists($realPath); + + $response = new BinaryFileResponse($realPath, 200, array('Content-Type' => 'application/octet-stream')); + $response->deleteFileAfterSend(true); + + $response->prepare($request); + $response->sendContent(); + + $this->assertFileNotExists($path); + } + + public function testAcceptRangeOnUnsafeMethods() + { + $request = Request::create('/', 'POST'); + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream')); + $response->prepare($request); + + $this->assertEquals('none', $response->headers->get('Accept-Ranges')); + } + + public function testAcceptRangeNotOverriden() + { + $request = Request::create('/', 'POST'); + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream')); + $response->headers->set('Accept-Ranges', 'foo'); + $response->prepare($request); + + $this->assertEquals('foo', $response->headers->get('Accept-Ranges')); + } + + public function getSampleXAccelMappings() + { + return array( + array('/var/www/var/www/files/foo.txt', '/var/www/=/files/', '/files/var/www/files/foo.txt'), + array('/home/foo/bar.txt', '/var/www/=/files/,/home/foo/=/baz/', '/baz/bar.txt'), + ); + } + + public function testStream() + { + $request = Request::create('/'); + $response = new BinaryFileResponse(new Stream(__DIR__.'/../README.md'), 200, array('Content-Type' => 'text/plain')); + $response->prepare($request); + + $this->assertNull($response->headers->get('Content-Length')); + } + + protected function provideResponse() + { + return new BinaryFileResponse(__DIR__.'/../README.md', 200, array('Content-Type' => 'application/octet-stream')); + } + + public static function tearDownAfterClass() + { + $path = __DIR__.'/../Fixtures/to_delete'; + if (file_exists($path)) { + @unlink($path); + } + } +} diff --git a/vendor/symfony/http-foundation/Tests/CookieTest.php b/vendor/symfony/http-foundation/Tests/CookieTest.php new file mode 100644 index 00000000..070b7dd4 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/CookieTest.php @@ -0,0 +1,223 @@ + + * + * 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\Cookie; + +/** + * CookieTest. + * + * @author John Kary + * @author Hugo Hamon + * + * @group time-sensitive + */ +class CookieTest extends TestCase +{ + public function invalidNames() + { + return array( + array(''), + array(',MyName'), + array(';MyName'), + array(' MyName'), + array("\tMyName"), + array("\rMyName"), + array("\nMyName"), + array("\013MyName"), + array("\014MyName"), + ); + } + + /** + * @dataProvider invalidNames + * @expectedException \InvalidArgumentException + */ + public function testInstantiationThrowsExceptionIfCookieNameContainsInvalidCharacters($name) + { + new Cookie($name); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidExpiration() + { + new Cookie('MyCookie', 'foo', 'bar'); + } + + public function testNegativeExpirationIsNotPossible() + { + $cookie = new Cookie('foo', 'bar', -100); + + $this->assertSame(0, $cookie->getExpiresTime()); + } + + public function testGetValue() + { + $value = 'MyValue'; + $cookie = new Cookie('MyCookie', $value); + + $this->assertSame($value, $cookie->getValue(), '->getValue() returns the proper value'); + } + + public function testGetPath() + { + $cookie = new Cookie('foo', 'bar'); + + $this->assertSame('/', $cookie->getPath(), '->getPath() returns / as the default path'); + } + + public function testGetExpiresTime() + { + $cookie = new Cookie('foo', 'bar'); + + $this->assertEquals(0, $cookie->getExpiresTime(), '->getExpiresTime() returns the default expire date'); + + $cookie = new Cookie('foo', 'bar', $expire = time() + 3600); + + $this->assertEquals($expire, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date'); + } + + public function testGetExpiresTimeIsCastToInt() + { + $cookie = new Cookie('foo', 'bar', 3600.9); + + $this->assertSame(3600, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date as an integer'); + } + + public function testConstructorWithDateTime() + { + $expire = new \DateTime(); + $cookie = new Cookie('foo', 'bar', $expire); + + $this->assertEquals($expire->format('U'), $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date'); + } + + /** + * @requires PHP 5.5 + */ + public function testConstructorWithDateTimeImmutable() + { + $expire = new \DateTimeImmutable(); + $cookie = new Cookie('foo', 'bar', $expire); + + $this->assertEquals($expire->format('U'), $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date'); + } + + public function testGetExpiresTimeWithStringValue() + { + $value = '+1 day'; + $cookie = new Cookie('foo', 'bar', $value); + $expire = strtotime($value); + + $this->assertEquals($expire, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date', 1); + } + + public function testGetDomain() + { + $cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com'); + + $this->assertEquals('.myfoodomain.com', $cookie->getDomain(), '->getDomain() returns the domain name on which the cookie is valid'); + } + + public function testIsSecure() + { + $cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com', true); + + $this->assertTrue($cookie->isSecure(), '->isSecure() returns whether the cookie is transmitted over HTTPS'); + } + + public function testIsHttpOnly() + { + $cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com', false, true); + + $this->assertTrue($cookie->isHttpOnly(), '->isHttpOnly() returns whether the cookie is only transmitted over HTTP'); + } + + public function testCookieIsNotCleared() + { + $cookie = new Cookie('foo', 'bar', time() + 3600 * 24); + + $this->assertFalse($cookie->isCleared(), '->isCleared() returns false if the cookie did not expire yet'); + } + + public function testCookieIsCleared() + { + $cookie = new Cookie('foo', 'bar', time() - 20); + + $this->assertTrue($cookie->isCleared(), '->isCleared() returns true if the cookie has expired'); + } + + public function testToString() + { + $cookie = new Cookie('foo', 'bar', $expire = strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true); + $this->assertEquals('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; max-age='.($expire - time()).'; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() returns string representation of the cookie'); + + $cookie = new Cookie('foo', 'bar with white spaces', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true); + $this->assertEquals('foo=bar%20with%20white%20spaces; expires=Fri, 20-May-2011 15:25:52 GMT; max-age='.($expire - time()).'; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() encodes the value of the cookie according to RFC 3986 (white space = %20)'); + + $cookie = new Cookie('foo', null, 1, '/admin/', '.myfoodomain.com'); + $this->assertEquals('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', $expire = time() - 31536001).'; max-age='.($expire - time()).'; path=/admin/; domain=.myfoodomain.com; httponly', (string) $cookie, '->__toString() returns string representation of a cleared cookie if value is NULL'); + + $cookie = new Cookie('foo', 'bar', 0, '/', ''); + $this->assertEquals('foo=bar; path=/; httponly', (string) $cookie); + } + + public function testRawCookie() + { + $cookie = new Cookie('foo', 'b a r', 0, '/', null, false, false); + $this->assertFalse($cookie->isRaw()); + $this->assertEquals('foo=b%20a%20r; path=/', (string) $cookie); + + $cookie = new Cookie('foo', 'b+a+r', 0, '/', null, false, false, true); + $this->assertTrue($cookie->isRaw()); + $this->assertEquals('foo=b+a+r; path=/', (string) $cookie); + } + + public function testGetMaxAge() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertEquals(0, $cookie->getMaxAge()); + + $cookie = new Cookie('foo', 'bar', $expire = time() + 100); + $this->assertEquals($expire - time(), $cookie->getMaxAge()); + + $cookie = new Cookie('foo', 'bar', $expire = time() - 100); + $this->assertEquals($expire - time(), $cookie->getMaxAge()); + } + + public function testFromString() + { + $cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure; httponly'); + $this->assertEquals(new Cookie('foo', 'bar', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true, true, true), $cookie); + + $cookie = Cookie::fromString('foo=bar', true); + $this->assertEquals(new Cookie('foo', 'bar', 0, '/', null, false, false), $cookie); + } + + public function testFromStringWithHttpOnly() + { + $cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure; httponly'); + $this->assertTrue($cookie->isHttpOnly()); + + $cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure'); + $this->assertFalse($cookie->isHttpOnly()); + } + + public function testSameSiteAttributeIsCaseInsensitive() + { + $cookie = new Cookie('foo', 'bar', 0, '/', null, false, true, false, 'Lax'); + $this->assertEquals('lax', $cookie->getSameSite()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php b/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php new file mode 100644 index 00000000..1152e46c --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php @@ -0,0 +1,69 @@ + + * + * 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\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\ExpressionRequestMatcher; +use Symfony\Component\HttpFoundation\Request; + +class ExpressionRequestMatcherTest extends TestCase +{ + /** + * @expectedException \LogicException + */ + public function testWhenNoExpressionIsSet() + { + $expressionRequestMatcher = new ExpressionRequestMatcher(); + $expressionRequestMatcher->matches(new Request()); + } + + /** + * @dataProvider provideExpressions + */ + public function testMatchesWhenParentMatchesIsTrue($expression, $expected) + { + $request = Request::create('/foo'); + $expressionRequestMatcher = new ExpressionRequestMatcher(); + + $expressionRequestMatcher->setExpression(new ExpressionLanguage(), $expression); + $this->assertSame($expected, $expressionRequestMatcher->matches($request)); + } + + /** + * @dataProvider provideExpressions + */ + public function testMatchesWhenParentMatchesIsFalse($expression) + { + $request = Request::create('/foo'); + $request->attributes->set('foo', 'foo'); + $expressionRequestMatcher = new ExpressionRequestMatcher(); + $expressionRequestMatcher->matchAttribute('foo', 'bar'); + + $expressionRequestMatcher->setExpression(new ExpressionLanguage(), $expression); + $this->assertFalse($expressionRequestMatcher->matches($request)); + } + + public function provideExpressions() + { + return array( + array('request.getMethod() == method', true), + array('request.getPathInfo() == path', true), + array('request.getHost() == host', true), + array('request.getClientIp() == ip', true), + array('request.attributes.all() == attributes', true), + array('request.getMethod() == method && request.getPathInfo() == path && request.getHost() == host && request.getClientIp() == ip && request.attributes.all() == attributes', true), + array('request.getMethod() != method', false), + array('request.getMethod() != method && request.getPathInfo() == path && request.getHost() == host && request.getClientIp() == ip && request.attributes.all() == attributes', false), + ); + } +} diff --git a/vendor/symfony/http-foundation/Tests/File/FakeFile.php b/vendor/symfony/http-foundation/Tests/File/FakeFile.php new file mode 100644 index 00000000..c415989f --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/File/FakeFile.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\File; + +use Symfony\Component\HttpFoundation\File\File as OrigFile; + +class FakeFile extends OrigFile +{ + private $realpath; + + public function __construct($realpath, $path) + { + $this->realpath = $realpath; + parent::__construct($path, false); + } + + public function isReadable() + { + return true; + } + + public function getRealpath() + { + return $this->realpath; + } + + public function getSize() + { + return 42; + } + + public function getMTime() + { + return time(); + } +} diff --git a/vendor/symfony/http-foundation/Tests/File/FileTest.php b/vendor/symfony/http-foundation/Tests/File/FileTest.php new file mode 100644 index 00000000..dbd9c44b --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/File/FileTest.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\File; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; + +class FileTest extends TestCase +{ + protected $file; + + public function testGetMimeTypeUsesMimeTypeGuessers() + { + $file = new File(__DIR__.'/Fixtures/test.gif'); + $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif'); + + MimeTypeGuesser::getInstance()->register($guesser); + + $this->assertEquals('image/gif', $file->getMimeType()); + } + + public function testGuessExtensionWithoutGuesser() + { + $file = new File(__DIR__.'/Fixtures/directory/.empty'); + + $this->assertNull($file->guessExtension()); + } + + public function testGuessExtensionIsBasedOnMimeType() + { + $file = new File(__DIR__.'/Fixtures/test'); + $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif'); + + MimeTypeGuesser::getInstance()->register($guesser); + + $this->assertEquals('gif', $file->guessExtension()); + } + + /** + * @requires extension fileinfo + */ + public function testGuessExtensionWithReset() + { + $file = new File(__DIR__.'/Fixtures/other-file.example'); + $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif'); + MimeTypeGuesser::getInstance()->register($guesser); + + $this->assertEquals('gif', $file->guessExtension()); + + MimeTypeGuesser::reset(); + + $this->assertNull($file->guessExtension()); + } + + public function testConstructWhenFileNotExists() + { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + + new File(__DIR__.'/Fixtures/not_here'); + } + + public function testMove() + { + $path = __DIR__.'/Fixtures/test.copy.gif'; + $targetDir = __DIR__.'/Fixtures/directory'; + $targetPath = $targetDir.'/test.copy.gif'; + @unlink($path); + @unlink($targetPath); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new File($path); + $movedFile = $file->move($targetDir); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\File\File', $movedFile); + + $this->assertFileExists($targetPath); + $this->assertFileNotExists($path); + $this->assertEquals(realpath($targetPath), $movedFile->getRealPath()); + + @unlink($targetPath); + } + + public function testMoveWithNewName() + { + $path = __DIR__.'/Fixtures/test.copy.gif'; + $targetDir = __DIR__.'/Fixtures/directory'; + $targetPath = $targetDir.'/test.newname.gif'; + @unlink($path); + @unlink($targetPath); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new File($path); + $movedFile = $file->move($targetDir, 'test.newname.gif'); + + $this->assertFileExists($targetPath); + $this->assertFileNotExists($path); + $this->assertEquals(realpath($targetPath), $movedFile->getRealPath()); + + @unlink($targetPath); + } + + public function getFilenameFixtures() + { + return array( + array('original.gif', 'original.gif'), + array('..\\..\\original.gif', 'original.gif'), + array('../../original.gif', 'original.gif'), + array('файлfile.gif', 'файлfile.gif'), + array('..\\..\\файлfile.gif', 'файлfile.gif'), + array('../../файлfile.gif', 'файлfile.gif'), + ); + } + + /** + * @dataProvider getFilenameFixtures + */ + public function testMoveWithNonLatinName($filename, $sanitizedFilename) + { + $path = __DIR__.'/Fixtures/'.$sanitizedFilename; + $targetDir = __DIR__.'/Fixtures/directory/'; + $targetPath = $targetDir.$sanitizedFilename; + @unlink($path); + @unlink($targetPath); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new File($path); + $movedFile = $file->move($targetDir, $filename); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\File\File', $movedFile); + + $this->assertFileExists($targetPath); + $this->assertFileNotExists($path); + $this->assertEquals(realpath($targetPath), $movedFile->getRealPath()); + + @unlink($targetPath); + } + + public function testMoveToAnUnexistentDirectory() + { + $sourcePath = __DIR__.'/Fixtures/test.copy.gif'; + $targetDir = __DIR__.'/Fixtures/directory/sub'; + $targetPath = $targetDir.'/test.copy.gif'; + @unlink($sourcePath); + @unlink($targetPath); + @rmdir($targetDir); + copy(__DIR__.'/Fixtures/test.gif', $sourcePath); + + $file = new File($sourcePath); + $movedFile = $file->move($targetDir); + + $this->assertFileExists($targetPath); + $this->assertFileNotExists($sourcePath); + $this->assertEquals(realpath($targetPath), $movedFile->getRealPath()); + + @unlink($sourcePath); + @unlink($targetPath); + @rmdir($targetDir); + } + + protected function createMockGuesser($path, $mimeType) + { + $guesser = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface')->getMock(); + $guesser + ->expects($this->once()) + ->method('guess') + ->with($this->equalTo($path)) + ->will($this->returnValue($mimeType)) + ; + + return $guesser; + } +} diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension b/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension new file mode 100644 index 00000000..4d1ae35b --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension @@ -0,0 +1 @@ +f \ No newline at end of file diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty b/vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example b/vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example new file mode 100644 index 00000000..e69de29b 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 0000000000000000000000000000000000000000..b636f4b8df536b0a85e7cea1a6cf3f0bd3179b96 GIT binary patch literal 35 jcmZ?wbh9u|WMp7uXkcLY4+c66KmZb9U}AD%WUvMRyAlZ1 literal 0 HcmV?d00001 diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif b/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif new file mode 100644 index 0000000000000000000000000000000000000000..b636f4b8df536b0a85e7cea1a6cf3f0bd3179b96 GIT binary patch literal 35 jcmZ?wbh9u|WMp7uXkcLY4+c66KmZb9U}AD%WUvMRyAlZ1 literal 0 HcmV?d00001 diff --git a/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php b/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php new file mode 100644 index 00000000..5a2b7a21 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\File\MimeType; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; +use Symfony\Component\HttpFoundation\File\MimeType\FileBinaryMimeTypeGuesser; + +/** + * @requires extension fileinfo + */ +class MimeTypeTest extends TestCase +{ + protected $path; + + public function testGuessImageWithoutExtension() + { + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); + } + + public function testGuessImageWithDirectory() + { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + + MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/directory'); + } + + public function testGuessImageWithFileBinaryMimeTypeGuesser() + { + $guesser = MimeTypeGuesser::getInstance(); + $guesser->register(new FileBinaryMimeTypeGuesser()); + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); + } + + public function testGuessImageWithKnownExtension() + { + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test.gif')); + } + + public function testGuessFileWithUnknownExtension() + { + $this->assertEquals('application/octet-stream', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/.unknownextension')); + } + + public function testGuessWithIncorrectPath() + { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/not_here'); + } + + public function testGuessWithNonReadablePath() + { + 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/to_delete'; + touch($path); + @chmod($path, 0333); + + if (substr(sprintf('%o', fileperms($path)), -4) == '0333') { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException'); + MimeTypeGuesser::getInstance()->guess($path); + } else { + $this->markTestSkipped('Can not verify chmod operations, change of file permissions failed'); + } + } + + public static function tearDownAfterClass() + { + $path = __DIR__.'/../Fixtures/to_delete'; + if (file_exists($path)) { + @chmod($path, 0666); + @unlink($path); + } + } +} diff --git a/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php b/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php new file mode 100644 index 00000000..36f122fe --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php @@ -0,0 +1,273 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\File; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\File\UploadedFile; + +class UploadedFileTest extends TestCase +{ + protected function setUp() + { + if (!ini_get('file_uploads')) { + $this->markTestSkipped('file_uploads is disabled in php.ini'); + } + } + + public function testConstructWhenFileNotExists() + { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + + new UploadedFile( + __DIR__.'/Fixtures/not_here', + 'original.gif', + null + ); + } + + public function testFileUploadsWithNoMimeType() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/test.gif'), + UPLOAD_ERR_OK + ); + + $this->assertEquals('application/octet-stream', $file->getClientMimeType()); + + if (extension_loaded('fileinfo')) { + $this->assertEquals('image/gif', $file->getMimeType()); + } + } + + public function testFileUploadsWithUnknownMimeType() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/.unknownextension', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/.unknownextension'), + UPLOAD_ERR_OK + ); + + $this->assertEquals('application/octet-stream', $file->getClientMimeType()); + } + + public function testGuessClientExtension() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('gif', $file->guessClientExtension()); + } + + public function testGuessClientExtensionWithIncorrectMimeType() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/jpeg', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('jpeg', $file->guessClientExtension()); + } + + public function testErrorIsOkByDefault() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals(UPLOAD_ERR_OK, $file->getError()); + } + + public function testGetClientOriginalName() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('original.gif', $file->getClientOriginalName()); + } + + public function testGetClientOriginalExtension() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('gif', $file->getClientOriginalExtension()); + } + + /** + * @expectedException \Symfony\Component\HttpFoundation\File\Exception\FileException + */ + public function testMoveLocalFileIsNotAllowed() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + UPLOAD_ERR_OK + ); + + $movedFile = $file->move(__DIR__.'/Fixtures/directory'); + } + + public function testMoveLocalFileIsAllowedInTestMode() + { + $path = __DIR__.'/Fixtures/test.copy.gif'; + $targetDir = __DIR__.'/Fixtures/directory'; + $targetPath = $targetDir.'/test.copy.gif'; + @unlink($path); + @unlink($targetPath); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new UploadedFile( + $path, + 'original.gif', + 'image/gif', + filesize($path), + UPLOAD_ERR_OK, + true + ); + + $movedFile = $file->move(__DIR__.'/Fixtures/directory'); + + $this->assertFileExists($targetPath); + $this->assertFileNotExists($path); + $this->assertEquals(realpath($targetPath), $movedFile->getRealPath()); + + @unlink($targetPath); + } + + public function testGetClientOriginalNameSanitizeFilename() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + '../../original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('original.gif', $file->getClientOriginalName()); + } + + public function testGetSize() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals(filesize(__DIR__.'/Fixtures/test.gif'), $file->getSize()); + + $file = new UploadedFile( + __DIR__.'/Fixtures/test', + 'original.gif', + 'image/gif' + ); + + $this->assertEquals(filesize(__DIR__.'/Fixtures/test'), $file->getSize()); + } + + public function testGetExtension() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null + ); + + $this->assertEquals('gif', $file->getExtension()); + } + + public function testIsValid() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/test.gif'), + UPLOAD_ERR_OK, + true + ); + + $this->assertTrue($file->isValid()); + } + + /** + * @dataProvider uploadedFileErrorProvider + */ + public function testIsInvalidOnUploadError($error) + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/test.gif'), + $error + ); + + $this->assertFalse($file->isValid()); + } + + public function uploadedFileErrorProvider() + { + return array( + array(UPLOAD_ERR_INI_SIZE), + array(UPLOAD_ERR_FORM_SIZE), + array(UPLOAD_ERR_PARTIAL), + array(UPLOAD_ERR_NO_TMP_DIR), + array(UPLOAD_ERR_EXTENSION), + ); + } + + public function testIsInvalidIfNotHttpUpload() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/test.gif'), + UPLOAD_ERR_OK + ); + + $this->assertFalse($file->isValid()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/FileBagTest.php b/vendor/symfony/http-foundation/Tests/FileBagTest.php new file mode 100644 index 00000000..e7defa67 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/FileBagTest.php @@ -0,0 +1,149 @@ + + * + * 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\File\UploadedFile; +use Symfony\Component\HttpFoundation\FileBag; + +/** + * FileBagTest. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + */ +class FileBagTest extends TestCase +{ + /** + * @expectedException \InvalidArgumentException + */ + public function testFileMustBeAnArrayOrUploadedFile() + { + new FileBag(array('file' => 'foo')); + } + + public function testShouldConvertsUploadedFiles() + { + $tmpFile = $this->createTempFile(); + $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0); + + $bag = new FileBag(array('file' => array( + 'name' => basename($tmpFile), + 'type' => 'text/plain', + 'tmp_name' => $tmpFile, + 'error' => 0, + 'size' => 100, + ))); + + $this->assertEquals($file, $bag->get('file')); + } + + public function testShouldSetEmptyUploadedFilesToNull() + { + $bag = new FileBag(array('file' => array( + 'name' => '', + 'type' => '', + 'tmp_name' => '', + 'error' => UPLOAD_ERR_NO_FILE, + 'size' => 0, + ))); + + $this->assertNull($bag->get('file')); + } + + public function testShouldConvertUploadedFilesWithPhpBug() + { + $tmpFile = $this->createTempFile(); + $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0); + + $bag = new FileBag(array( + 'child' => array( + 'name' => array( + 'file' => basename($tmpFile), + ), + 'type' => array( + 'file' => 'text/plain', + ), + 'tmp_name' => array( + 'file' => $tmpFile, + ), + 'error' => array( + 'file' => 0, + ), + 'size' => array( + 'file' => 100, + ), + ), + )); + + $files = $bag->all(); + $this->assertEquals($file, $files['child']['file']); + } + + public function testShouldConvertNestedUploadedFilesWithPhpBug() + { + $tmpFile = $this->createTempFile(); + $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0); + + $bag = new FileBag(array( + 'child' => array( + 'name' => array( + 'sub' => array('file' => basename($tmpFile)), + ), + 'type' => array( + 'sub' => array('file' => 'text/plain'), + ), + 'tmp_name' => array( + 'sub' => array('file' => $tmpFile), + ), + 'error' => array( + 'sub' => array('file' => 0), + ), + 'size' => array( + 'sub' => array('file' => 100), + ), + ), + )); + + $files = $bag->all(); + $this->assertEquals($file, $files['child']['sub']['file']); + } + + public function testShouldNotConvertNestedUploadedFiles() + { + $tmpFile = $this->createTempFile(); + $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0); + $bag = new FileBag(array('image' => array('file' => $file))); + + $files = $bag->all(); + $this->assertEquals($file, $files['image']['file']); + } + + protected function createTempFile() + { + return tempnam(sys_get_temp_dir().'/form_test', 'FormTest'); + } + + protected function setUp() + { + mkdir(sys_get_temp_dir().'/form_test', 0777, true); + } + + protected function tearDown() + { + foreach (glob(sys_get_temp_dir().'/form_test/*') as $file) { + unlink($file); + } + + rmdir(sys_get_temp_dir().'/form_test'); + } +} diff --git a/vendor/symfony/http-foundation/Tests/HeaderBagTest.php b/vendor/symfony/http-foundation/Tests/HeaderBagTest.php new file mode 100644 index 00000000..1acf5930 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/HeaderBagTest.php @@ -0,0 +1,205 @@ + + * + * 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\HeaderBag; + +class HeaderBagTest extends TestCase +{ + public function testConstructor() + { + $bag = new HeaderBag(array('foo' => 'bar')); + $this->assertTrue($bag->has('foo')); + } + + public function testToStringNull() + { + $bag = new HeaderBag(); + $this->assertEquals('', $bag->__toString()); + } + + public function testToStringNotNull() + { + $bag = new HeaderBag(array('foo' => 'bar')); + $this->assertEquals("Foo: bar\r\n", $bag->__toString()); + } + + public function testKeys() + { + $bag = new HeaderBag(array('foo' => 'bar')); + $keys = $bag->keys(); + $this->assertEquals('foo', $keys[0]); + } + + public function testGetDate() + { + $bag = new HeaderBag(array('foo' => 'Tue, 4 Sep 2012 20:00:00 +0200')); + $headerDate = $bag->getDate('foo'); + $this->assertInstanceOf('DateTime', $headerDate); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetDateException() + { + $bag = new HeaderBag(array('foo' => 'Tue')); + $headerDate = $bag->getDate('foo'); + } + + public function testGetCacheControlHeader() + { + $bag = new HeaderBag(); + $bag->addCacheControlDirective('public', '#a'); + $this->assertTrue($bag->hasCacheControlDirective('public')); + $this->assertEquals('#a', $bag->getCacheControlDirective('public')); + } + + public function testAll() + { + $bag = new HeaderBag(array('foo' => 'bar')); + $this->assertEquals(array('foo' => array('bar')), $bag->all(), '->all() gets all the input'); + + $bag = new HeaderBag(array('FOO' => 'BAR')); + $this->assertEquals(array('foo' => array('BAR')), $bag->all(), '->all() gets all the input key are lower case'); + } + + public function testReplace() + { + $bag = new HeaderBag(array('foo' => 'bar')); + + $bag->replace(array('NOPE' => 'BAR')); + $this->assertEquals(array('nope' => array('BAR')), $bag->all(), '->replace() replaces the input with the argument'); + $this->assertFalse($bag->has('foo'), '->replace() overrides previously set the input'); + } + + public function testGet() + { + $bag = new HeaderBag(array('foo' => 'bar', 'fuzz' => 'bizz')); + $this->assertEquals('bar', $bag->get('foo'), '->get return current value'); + $this->assertEquals('bar', $bag->get('FoO'), '->get key in case insensitive'); + $this->assertEquals(array('bar'), $bag->get('foo', 'nope', false), '->get return the value as array'); + + // defaults + $this->assertNull($bag->get('none'), '->get unknown values returns null'); + $this->assertEquals('default', $bag->get('none', 'default'), '->get unknown values returns default'); + $this->assertEquals(array('default'), $bag->get('none', 'default', false), '->get unknown values returns default as array'); + + $bag->set('foo', 'bor', false); + $this->assertEquals('bar', $bag->get('foo'), '->get return first value'); + $this->assertEquals(array('bar', 'bor'), $bag->get('foo', 'nope', false), '->get return all values as array'); + } + + public function testSetAssociativeArray() + { + $bag = new HeaderBag(); + $bag->set('foo', array('bad-assoc-index' => 'value')); + $this->assertSame('value', $bag->get('foo')); + $this->assertEquals(array('value'), $bag->get('foo', 'nope', false), 'assoc indices of multi-valued headers are ignored'); + } + + public function testContains() + { + $bag = new HeaderBag(array('foo' => 'bar', 'fuzz' => 'bizz')); + $this->assertTrue($bag->contains('foo', 'bar'), '->contains first value'); + $this->assertTrue($bag->contains('fuzz', 'bizz'), '->contains second value'); + $this->assertFalse($bag->contains('nope', 'nope'), '->contains unknown value'); + $this->assertFalse($bag->contains('foo', 'nope'), '->contains unknown value'); + + // Multiple values + $bag->set('foo', 'bor', false); + $this->assertTrue($bag->contains('foo', 'bar'), '->contains first value'); + $this->assertTrue($bag->contains('foo', 'bor'), '->contains second value'); + $this->assertFalse($bag->contains('foo', 'nope'), '->contains unknown value'); + } + + public function testCacheControlDirectiveAccessors() + { + $bag = new HeaderBag(); + $bag->addCacheControlDirective('public'); + + $this->assertTrue($bag->hasCacheControlDirective('public')); + $this->assertTrue($bag->getCacheControlDirective('public')); + $this->assertEquals('public', $bag->get('cache-control')); + + $bag->addCacheControlDirective('max-age', 10); + $this->assertTrue($bag->hasCacheControlDirective('max-age')); + $this->assertEquals(10, $bag->getCacheControlDirective('max-age')); + $this->assertEquals('max-age=10, public', $bag->get('cache-control')); + + $bag->removeCacheControlDirective('max-age'); + $this->assertFalse($bag->hasCacheControlDirective('max-age')); + } + + public function testCacheControlDirectiveParsing() + { + $bag = new HeaderBag(array('cache-control' => 'public, max-age=10')); + $this->assertTrue($bag->hasCacheControlDirective('public')); + $this->assertTrue($bag->getCacheControlDirective('public')); + + $this->assertTrue($bag->hasCacheControlDirective('max-age')); + $this->assertEquals(10, $bag->getCacheControlDirective('max-age')); + + $bag->addCacheControlDirective('s-maxage', 100); + $this->assertEquals('max-age=10, public, s-maxage=100', $bag->get('cache-control')); + } + + public function testCacheControlDirectiveParsingQuotedZero() + { + $bag = new HeaderBag(array('cache-control' => 'max-age="0"')); + $this->assertTrue($bag->hasCacheControlDirective('max-age')); + $this->assertEquals(0, $bag->getCacheControlDirective('max-age')); + } + + public function testCacheControlDirectiveOverrideWithReplace() + { + $bag = new HeaderBag(array('cache-control' => 'private, max-age=100')); + $bag->replace(array('cache-control' => 'public, max-age=10')); + $this->assertTrue($bag->hasCacheControlDirective('public')); + $this->assertTrue($bag->getCacheControlDirective('public')); + + $this->assertTrue($bag->hasCacheControlDirective('max-age')); + $this->assertEquals(10, $bag->getCacheControlDirective('max-age')); + } + + public function testCacheControlClone() + { + $headers = array('foo' => 'bar'); + $bag1 = new HeaderBag($headers); + $bag2 = new HeaderBag($bag1->all()); + + $this->assertEquals($bag1->all(), $bag2->all()); + } + + public function testGetIterator() + { + $headers = array('foo' => 'bar', 'hello' => 'world', 'third' => 'charm'); + $headerBag = new HeaderBag($headers); + + $i = 0; + foreach ($headerBag as $key => $val) { + ++$i; + $this->assertEquals(array($headers[$key]), $val); + } + + $this->assertEquals(count($headers), $i); + } + + public function testCount() + { + $headers = array('foo' => 'bar', 'HELLO' => 'WORLD'); + $headerBag = new HeaderBag($headers); + + $this->assertEquals(count($headers), count($headerBag)); + } +} diff --git a/vendor/symfony/http-foundation/Tests/IpUtilsTest.php b/vendor/symfony/http-foundation/Tests/IpUtilsTest.php new file mode 100644 index 00000000..297ee3d8 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/IpUtilsTest.php @@ -0,0 +1,85 @@ + + * + * 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\IpUtils; + +class IpUtilsTest extends TestCase +{ + /** + * @dataProvider getIpv4Data + */ + public function testIpv4($matches, $remoteAddr, $cidr) + { + $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr)); + } + + public function getIpv4Data() + { + return array( + array(true, '192.168.1.1', '192.168.1.1'), + array(true, '192.168.1.1', '192.168.1.1/1'), + array(true, '192.168.1.1', '192.168.1.0/24'), + array(false, '192.168.1.1', '1.2.3.4/1'), + array(false, '192.168.1.1', '192.168.1.1/33'), // invalid subnet + array(true, '192.168.1.1', array('1.2.3.4/1', '192.168.1.0/24')), + array(true, '192.168.1.1', array('192.168.1.0/24', '1.2.3.4/1')), + array(false, '192.168.1.1', array('1.2.3.4/1', '4.3.2.1/1')), + array(true, '1.2.3.4', '0.0.0.0/0'), + array(true, '1.2.3.4', '192.168.1.0/0'), + array(false, '1.2.3.4', '256.256.256/0'), // invalid CIDR notation + array(false, 'an_invalid_ip', '192.168.1.0/24'), + ); + } + + /** + * @dataProvider getIpv6Data + */ + public function testIpv6($matches, $remoteAddr, $cidr) + { + if (!defined('AF_INET6')) { + $this->markTestSkipped('Only works when PHP is compiled without the option "disable-ipv6".'); + } + + $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr)); + } + + public function getIpv6Data() + { + return array( + array(true, '2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'), + array(false, '2a00:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'), + array(false, '2a01:198:603:0:396e:4789:8e99:890f', '::1'), + array(true, '0:0:0:0:0:0:0:1', '::1'), + array(false, '0:0:603:0:396e:4789:8e99:0001', '::1'), + array(true, '2a01:198:603:0:396e:4789:8e99:890f', array('::1', '2a01:198:603:0::/65')), + array(true, '2a01:198:603:0:396e:4789:8e99:890f', array('2a01:198:603:0::/65', '::1')), + array(false, '2a01:198:603:0:396e:4789:8e99:890f', array('::1', '1a01:198:603:0::/65')), + array(false, '}__test|O:21:"JDatabaseDriverMysqli":3:{s:2', '::1'), + array(false, '2a01:198:603:0:396e:4789:8e99:890f', 'unknown'), + ); + } + + /** + * @expectedException \RuntimeException + * @requires extension sockets + */ + public function testAnIpv6WithOptionDisabledIpv6() + { + if (defined('AF_INET6')) { + $this->markTestSkipped('Only works when PHP is compiled with the option "disable-ipv6".'); + } + + IpUtils::checkIp('2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'); + } +} diff --git a/vendor/symfony/http-foundation/Tests/JsonResponseTest.php b/vendor/symfony/http-foundation/Tests/JsonResponseTest.php new file mode 100644 index 00000000..201839f8 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/JsonResponseTest.php @@ -0,0 +1,257 @@ + + * + * 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\JsonResponse; + +class JsonResponseTest extends TestCase +{ + public function testConstructorEmptyCreatesJsonObject() + { + $response = new JsonResponse(); + $this->assertSame('{}', $response->getContent()); + } + + public function testConstructorWithArrayCreatesJsonArray() + { + $response = new JsonResponse(array(0, 1, 2, 3)); + $this->assertSame('[0,1,2,3]', $response->getContent()); + } + + public function testConstructorWithAssocArrayCreatesJsonObject() + { + $response = new JsonResponse(array('foo' => 'bar')); + $this->assertSame('{"foo":"bar"}', $response->getContent()); + } + + public function testConstructorWithSimpleTypes() + { + $response = new JsonResponse('foo'); + $this->assertSame('"foo"', $response->getContent()); + + $response = new JsonResponse(0); + $this->assertSame('0', $response->getContent()); + + $response = new JsonResponse(0.1); + $this->assertSame('0.1', $response->getContent()); + + $response = new JsonResponse(true); + $this->assertSame('true', $response->getContent()); + } + + public function testConstructorWithCustomStatus() + { + $response = new JsonResponse(array(), 202); + $this->assertSame(202, $response->getStatusCode()); + } + + public function testConstructorAddsContentTypeHeader() + { + $response = new JsonResponse(); + $this->assertSame('application/json', $response->headers->get('Content-Type')); + } + + public function testConstructorWithCustomHeaders() + { + $response = new JsonResponse(array(), 200, array('ETag' => 'foo')); + $this->assertSame('application/json', $response->headers->get('Content-Type')); + $this->assertSame('foo', $response->headers->get('ETag')); + } + + public function testConstructorWithCustomContentType() + { + $headers = array('Content-Type' => 'application/vnd.acme.blog-v1+json'); + + $response = new JsonResponse(array(), 200, $headers); + $this->assertSame('application/vnd.acme.blog-v1+json', $response->headers->get('Content-Type')); + } + + public function testSetJson() + { + $response = new JsonResponse('1', 200, array(), true); + $this->assertEquals('1', $response->getContent()); + + $response = new JsonResponse('[1]', 200, array(), true); + $this->assertEquals('[1]', $response->getContent()); + + $response = new JsonResponse(null, 200, array()); + $response->setJson('true'); + $this->assertEquals('true', $response->getContent()); + } + + public function testCreate() + { + $response = JsonResponse::create(array('foo' => 'bar'), 204); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertEquals('{"foo":"bar"}', $response->getContent()); + $this->assertEquals(204, $response->getStatusCode()); + } + + public function testStaticCreateEmptyJsonObject() + { + $response = JsonResponse::create(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('{}', $response->getContent()); + } + + public function testStaticCreateJsonArray() + { + $response = JsonResponse::create(array(0, 1, 2, 3)); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('[0,1,2,3]', $response->getContent()); + } + + public function testStaticCreateJsonObject() + { + $response = JsonResponse::create(array('foo' => 'bar')); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('{"foo":"bar"}', $response->getContent()); + } + + public function testStaticCreateWithSimpleTypes() + { + $response = JsonResponse::create('foo'); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('"foo"', $response->getContent()); + + $response = JsonResponse::create(0); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('0', $response->getContent()); + + $response = JsonResponse::create(0.1); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('0.1', $response->getContent()); + + $response = JsonResponse::create(true); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('true', $response->getContent()); + } + + public function testStaticCreateWithCustomStatus() + { + $response = JsonResponse::create(array(), 202); + $this->assertSame(202, $response->getStatusCode()); + } + + public function testStaticCreateAddsContentTypeHeader() + { + $response = JsonResponse::create(); + $this->assertSame('application/json', $response->headers->get('Content-Type')); + } + + public function testStaticCreateWithCustomHeaders() + { + $response = JsonResponse::create(array(), 200, array('ETag' => 'foo')); + $this->assertSame('application/json', $response->headers->get('Content-Type')); + $this->assertSame('foo', $response->headers->get('ETag')); + } + + public function testStaticCreateWithCustomContentType() + { + $headers = array('Content-Type' => 'application/vnd.acme.blog-v1+json'); + + $response = JsonResponse::create(array(), 200, $headers); + $this->assertSame('application/vnd.acme.blog-v1+json', $response->headers->get('Content-Type')); + } + + public function testSetCallback() + { + $response = JsonResponse::create(array('foo' => 'bar'))->setCallback('callback'); + + $this->assertEquals('/**/callback({"foo":"bar"});', $response->getContent()); + $this->assertEquals('text/javascript', $response->headers->get('Content-Type')); + } + + public function testJsonEncodeFlags() + { + $response = new JsonResponse('<>\'&"'); + + $this->assertEquals('"\u003C\u003E\u0027\u0026\u0022"', $response->getContent()); + } + + public function testGetEncodingOptions() + { + $response = new JsonResponse(); + + $this->assertEquals(JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT, $response->getEncodingOptions()); + } + + public function testSetEncodingOptions() + { + $response = new JsonResponse(); + $response->setData(array(array(1, 2, 3))); + + $this->assertEquals('[[1,2,3]]', $response->getContent()); + + $response->setEncodingOptions(JSON_FORCE_OBJECT); + + $this->assertEquals('{"0":{"0":1,"1":2,"2":3}}', $response->getContent()); + } + + public function testItAcceptsJsonAsString() + { + $response = JsonResponse::fromJsonString('{"foo":"bar"}'); + $this->assertSame('{"foo":"bar"}', $response->getContent()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSetCallbackInvalidIdentifier() + { + $response = new JsonResponse('foo'); + $response->setCallback('+invalid'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSetContent() + { + JsonResponse::create("\xB1\x31"); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage This error is expected + */ + public function testSetContentJsonSerializeError() + { + if (!interface_exists('JsonSerializable', false)) { + $this->markTestSkipped('JsonSerializable is required.'); + } + + $serializable = new JsonSerializableObject(); + + JsonResponse::create($serializable); + } + + public function testSetComplexCallback() + { + $response = JsonResponse::create(array('foo' => 'bar')); + $response->setCallback('ಠ_ಠ["foo"].bar[0]'); + + $this->assertEquals('/**/ಠ_ಠ["foo"].bar[0]({"foo":"bar"});', $response->getContent()); + } +} + +if (interface_exists('JsonSerializable', false)) { + class JsonSerializableObject implements \JsonSerializable + { + public function jsonSerialize() + { + throw new \Exception('This error is expected'); + } + } +} diff --git a/vendor/symfony/http-foundation/Tests/ParameterBagTest.php b/vendor/symfony/http-foundation/Tests/ParameterBagTest.php new file mode 100644 index 00000000..5311a0d8 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ParameterBagTest.php @@ -0,0 +1,194 @@ + + * + * 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\ParameterBag; + +class ParameterBagTest extends TestCase +{ + public function testConstructor() + { + $this->testAll(); + } + + public function testAll() + { + $bag = new ParameterBag(array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $bag->all(), '->all() gets all the input'); + } + + public function testKeys() + { + $bag = new ParameterBag(array('foo' => 'bar')); + $this->assertEquals(array('foo'), $bag->keys()); + } + + public function testAdd() + { + $bag = new ParameterBag(array('foo' => 'bar')); + $bag->add(array('bar' => 'bas')); + $this->assertEquals(array('foo' => 'bar', 'bar' => 'bas'), $bag->all()); + } + + public function testRemove() + { + $bag = new ParameterBag(array('foo' => 'bar')); + $bag->add(array('bar' => 'bas')); + $this->assertEquals(array('foo' => 'bar', 'bar' => 'bas'), $bag->all()); + $bag->remove('bar'); + $this->assertEquals(array('foo' => 'bar'), $bag->all()); + } + + public function testReplace() + { + $bag = new ParameterBag(array('foo' => 'bar')); + + $bag->replace(array('FOO' => 'BAR')); + $this->assertEquals(array('FOO' => 'BAR'), $bag->all(), '->replace() replaces the input with the argument'); + $this->assertFalse($bag->has('foo'), '->replace() overrides previously set the input'); + } + + public function testGet() + { + $bag = new ParameterBag(array('foo' => 'bar', 'null' => null)); + + $this->assertEquals('bar', $bag->get('foo'), '->get() gets the value of a parameter'); + $this->assertEquals('default', $bag->get('unknown', 'default'), '->get() returns second argument as default if a parameter is not defined'); + $this->assertNull($bag->get('null', 'default'), '->get() returns null if null is set'); + } + + public function testGetDoesNotUseDeepByDefault() + { + $bag = new ParameterBag(array('foo' => array('bar' => 'moo'))); + + $this->assertNull($bag->get('foo[bar]')); + } + + public function testSet() + { + $bag = new ParameterBag(array()); + + $bag->set('foo', 'bar'); + $this->assertEquals('bar', $bag->get('foo'), '->set() sets the value of parameter'); + + $bag->set('foo', 'baz'); + $this->assertEquals('baz', $bag->get('foo'), '->set() overrides previously set parameter'); + } + + public function testHas() + { + $bag = new ParameterBag(array('foo' => 'bar')); + + $this->assertTrue($bag->has('foo'), '->has() returns true if a parameter is defined'); + $this->assertFalse($bag->has('unknown'), '->has() return false if a parameter is not defined'); + } + + public function testGetAlpha() + { + $bag = new ParameterBag(array('word' => 'foo_BAR_012')); + + $this->assertEquals('fooBAR', $bag->getAlpha('word'), '->getAlpha() gets only alphabetic characters'); + $this->assertEquals('', $bag->getAlpha('unknown'), '->getAlpha() returns empty string if a parameter is not defined'); + } + + public function testGetAlnum() + { + $bag = new ParameterBag(array('word' => 'foo_BAR_012')); + + $this->assertEquals('fooBAR012', $bag->getAlnum('word'), '->getAlnum() gets only alphanumeric characters'); + $this->assertEquals('', $bag->getAlnum('unknown'), '->getAlnum() returns empty string if a parameter is not defined'); + } + + public function testGetDigits() + { + $bag = new ParameterBag(array('word' => 'foo_BAR_012')); + + $this->assertEquals('012', $bag->getDigits('word'), '->getDigits() gets only digits as string'); + $this->assertEquals('', $bag->getDigits('unknown'), '->getDigits() returns empty string if a parameter is not defined'); + } + + public function testGetInt() + { + $bag = new ParameterBag(array('digits' => '0123')); + + $this->assertEquals(123, $bag->getInt('digits'), '->getInt() gets a value of parameter as integer'); + $this->assertEquals(0, $bag->getInt('unknown'), '->getInt() returns zero if a parameter is not defined'); + } + + public function testFilter() + { + $bag = new ParameterBag(array( + 'digits' => '0123ab', + 'email' => 'example@example.com', + 'url' => 'http://example.com/foo', + 'dec' => '256', + 'hex' => '0x100', + 'array' => array('bang'), + )); + + $this->assertEmpty($bag->filter('nokey'), '->filter() should return empty by default if no key is found'); + + $this->assertEquals('0123', $bag->filter('digits', '', FILTER_SANITIZE_NUMBER_INT), '->filter() gets a value of parameter as integer filtering out invalid characters'); + + $this->assertEquals('example@example.com', $bag->filter('email', '', FILTER_VALIDATE_EMAIL), '->filter() gets a value of parameter as email'); + + $this->assertEquals('http://example.com/foo', $bag->filter('url', '', FILTER_VALIDATE_URL, array('flags' => FILTER_FLAG_PATH_REQUIRED)), '->filter() gets a value of parameter as URL with a path'); + + // This test is repeated for code-coverage + $this->assertEquals('http://example.com/foo', $bag->filter('url', '', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED), '->filter() gets a value of parameter as URL with a path'); + + $this->assertFalse($bag->filter('dec', '', FILTER_VALIDATE_INT, array( + 'flags' => FILTER_FLAG_ALLOW_HEX, + 'options' => array('min_range' => 1, 'max_range' => 0xff), + )), '->filter() gets a value of parameter as integer between boundaries'); + + $this->assertFalse($bag->filter('hex', '', FILTER_VALIDATE_INT, array( + 'flags' => FILTER_FLAG_ALLOW_HEX, + 'options' => array('min_range' => 1, 'max_range' => 0xff), + )), '->filter() gets a value of parameter as integer between boundaries'); + + $this->assertEquals(array('bang'), $bag->filter('array', ''), '->filter() gets a value of parameter as an array'); + } + + public function testGetIterator() + { + $parameters = array('foo' => 'bar', 'hello' => 'world'); + $bag = new ParameterBag($parameters); + + $i = 0; + foreach ($bag as $key => $val) { + ++$i; + $this->assertEquals($parameters[$key], $val); + } + + $this->assertEquals(count($parameters), $i); + } + + public function testCount() + { + $parameters = array('foo' => 'bar', 'hello' => 'world'); + $bag = new ParameterBag($parameters); + + $this->assertEquals(count($parameters), count($bag)); + } + + public function testGetBoolean() + { + $parameters = array('string_true' => 'true', 'string_false' => 'false'); + $bag = new ParameterBag($parameters); + + $this->assertTrue($bag->getBoolean('string_true'), '->getBoolean() gets the string true as boolean true'); + $this->assertFalse($bag->getBoolean('string_false'), '->getBoolean() gets the string false as boolean false'); + $this->assertFalse($bag->getBoolean('unknown'), '->getBoolean() returns false if a parameter is not defined'); + } +} diff --git a/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php b/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php new file mode 100644 index 00000000..d389e83d --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php @@ -0,0 +1,97 @@ + + * + * 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\RedirectResponse; + +class RedirectResponseTest extends TestCase +{ + public function testGenerateMetaRedirect() + { + $response = new RedirectResponse('foo.bar'); + + $this->assertEquals(1, preg_match( + '##', + preg_replace(array('/\s+/', '/\'/'), array(' ', '"'), $response->getContent()) + )); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRedirectResponseConstructorNullUrl() + { + $response = new RedirectResponse(null); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRedirectResponseConstructorWrongStatusCode() + { + $response = new RedirectResponse('foo.bar', 404); + } + + public function testGenerateLocationHeader() + { + $response = new RedirectResponse('foo.bar'); + + $this->assertTrue($response->headers->has('Location')); + $this->assertEquals('foo.bar', $response->headers->get('Location')); + } + + public function testGetTargetUrl() + { + $response = new RedirectResponse('foo.bar'); + + $this->assertEquals('foo.bar', $response->getTargetUrl()); + } + + public function testSetTargetUrl() + { + $response = new RedirectResponse('foo.bar'); + $response->setTargetUrl('baz.beep'); + + $this->assertEquals('baz.beep', $response->getTargetUrl()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSetTargetUrlNull() + { + $response = new RedirectResponse('foo.bar'); + $response->setTargetUrl(null); + } + + public function testCreate() + { + $response = RedirectResponse::create('foo', 301); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response); + $this->assertEquals(301, $response->getStatusCode()); + } + + public function testCacheHeaders() + { + $response = new RedirectResponse('foo.bar', 301); + $this->assertFalse($response->headers->hasCacheControlDirective('no-cache')); + + $response = new RedirectResponse('foo.bar', 301, array('cache-control' => 'max-age=86400')); + $this->assertFalse($response->headers->hasCacheControlDirective('no-cache')); + $this->assertTrue($response->headers->hasCacheControlDirective('max-age')); + + $response = new RedirectResponse('foo.bar', 302); + $this->assertTrue($response->headers->hasCacheControlDirective('no-cache')); + } +} diff --git a/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php b/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php new file mode 100644 index 00000000..b5d80048 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php @@ -0,0 +1,151 @@ + + * + * 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\RequestMatcher; +use Symfony\Component\HttpFoundation\Request; + +class RequestMatcherTest extends TestCase +{ + /** + * @dataProvider getMethodData + */ + public function testMethod($requestMethod, $matcherMethod, $isMatch) + { + $matcher = new RequestMatcher(); + $matcher->matchMethod($matcherMethod); + $request = Request::create('', $requestMethod); + $this->assertSame($isMatch, $matcher->matches($request)); + + $matcher = new RequestMatcher(null, null, $matcherMethod); + $request = Request::create('', $requestMethod); + $this->assertSame($isMatch, $matcher->matches($request)); + } + + public function getMethodData() + { + return array( + array('get', 'get', true), + array('get', array('get', 'post'), true), + array('get', 'post', false), + array('get', 'GET', true), + array('get', array('GET', 'POST'), true), + array('get', 'POST', false), + ); + } + + public function testScheme() + { + $httpRequest = $request = $request = Request::create(''); + $httpsRequest = $request = $request = Request::create('', 'get', array(), array(), array(), array('HTTPS' => 'on')); + + $matcher = new RequestMatcher(); + $matcher->matchScheme('https'); + $this->assertFalse($matcher->matches($httpRequest)); + $this->assertTrue($matcher->matches($httpsRequest)); + + $matcher->matchScheme('http'); + $this->assertFalse($matcher->matches($httpsRequest)); + $this->assertTrue($matcher->matches($httpRequest)); + + $matcher = new RequestMatcher(); + $this->assertTrue($matcher->matches($httpsRequest)); + $this->assertTrue($matcher->matches($httpRequest)); + } + + /** + * @dataProvider getHostData + */ + public function testHost($pattern, $isMatch) + { + $matcher = new RequestMatcher(); + $request = Request::create('', 'get', array(), array(), array(), array('HTTP_HOST' => 'foo.example.com')); + + $matcher->matchHost($pattern); + $this->assertSame($isMatch, $matcher->matches($request)); + + $matcher = new RequestMatcher(null, $pattern); + $this->assertSame($isMatch, $matcher->matches($request)); + } + + public function getHostData() + { + return array( + array('.*\.example\.com', true), + array('\.example\.com$', true), + array('^.*\.example\.com$', true), + array('.*\.sensio\.com', false), + array('.*\.example\.COM', true), + array('\.example\.COM$', true), + array('^.*\.example\.COM$', true), + array('.*\.sensio\.COM', false), + ); + } + + public function testPath() + { + $matcher = new RequestMatcher(); + + $request = Request::create('/admin/foo'); + + $matcher->matchPath('/admin/.*'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchPath('/admin'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchPath('^/admin/.*$'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchMethod('/blog/.*'); + $this->assertFalse($matcher->matches($request)); + } + + public function testPathWithLocaleIsNotSupported() + { + $matcher = new RequestMatcher(); + $request = Request::create('/en/login'); + $request->setLocale('en'); + + $matcher->matchPath('^/{_locale}/login$'); + $this->assertFalse($matcher->matches($request)); + } + + public function testPathWithEncodedCharacters() + { + $matcher = new RequestMatcher(); + $request = Request::create('/admin/fo%20o'); + $matcher->matchPath('^/admin/fo o*$'); + $this->assertTrue($matcher->matches($request)); + } + + public function testAttributes() + { + $matcher = new RequestMatcher(); + + $request = Request::create('/admin/foo'); + $request->attributes->set('foo', 'foo_bar'); + + $matcher->matchAttribute('foo', 'foo_.*'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchAttribute('foo', 'foo'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchAttribute('foo', '^foo_bar$'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchAttribute('foo', 'babar'); + $this->assertFalse($matcher->matches($request)); + } +} diff --git a/vendor/symfony/http-foundation/Tests/RequestStackTest.php b/vendor/symfony/http-foundation/Tests/RequestStackTest.php new file mode 100644 index 00000000..a84fb26f --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/RequestStackTest.php @@ -0,0 +1,70 @@ + + * + * 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; + +class RequestStackTest extends TestCase +{ + public function testGetCurrentRequest() + { + $requestStack = new RequestStack(); + $this->assertNull($requestStack->getCurrentRequest()); + + $request = Request::create('/foo'); + + $requestStack->push($request); + $this->assertSame($request, $requestStack->getCurrentRequest()); + + $this->assertSame($request, $requestStack->pop()); + $this->assertNull($requestStack->getCurrentRequest()); + + $this->assertNull($requestStack->pop()); + } + + public function testGetMasterRequest() + { + $requestStack = new RequestStack(); + $this->assertNull($requestStack->getMasterRequest()); + + $masterRequest = Request::create('/foo'); + $subRequest = Request::create('/bar'); + + $requestStack->push($masterRequest); + $requestStack->push($subRequest); + + $this->assertSame($masterRequest, $requestStack->getMasterRequest()); + } + + public function testGetParentRequest() + { + $requestStack = new RequestStack(); + $this->assertNull($requestStack->getParentRequest()); + + $masterRequest = Request::create('/foo'); + + $requestStack->push($masterRequest); + $this->assertNull($requestStack->getParentRequest()); + + $firstSubRequest = Request::create('/bar'); + + $requestStack->push($firstSubRequest); + $this->assertSame($masterRequest, $requestStack->getParentRequest()); + + $secondSubRequest = Request::create('/baz'); + + $requestStack->push($secondSubRequest); + $this->assertSame($firstSubRequest, $requestStack->getParentRequest()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/RequestTest.php b/vendor/symfony/http-foundation/Tests/RequestTest.php new file mode 100644 index 00000000..b36fbb7e --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/RequestTest.php @@ -0,0 +1,2207 @@ + + * + * 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\Exception\SuspiciousOperationException; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Request; + +class RequestTest extends TestCase +{ + protected function tearDown() + { + // reset + Request::setTrustedProxies(array(), -1); + } + + public function testInitialize() + { + $request = new Request(); + + $request->initialize(array('foo' => 'bar')); + $this->assertEquals('bar', $request->query->get('foo'), '->initialize() takes an array of query parameters as its first argument'); + + $request->initialize(array(), array('foo' => 'bar')); + $this->assertEquals('bar', $request->request->get('foo'), '->initialize() takes an array of request parameters as its second argument'); + + $request->initialize(array(), array(), array('foo' => 'bar')); + $this->assertEquals('bar', $request->attributes->get('foo'), '->initialize() takes an array of attributes as its third argument'); + + $request->initialize(array(), array(), array(), array(), array(), array('HTTP_FOO' => 'bar')); + $this->assertEquals('bar', $request->headers->get('FOO'), '->initialize() takes an array of HTTP headers as its sixth argument'); + } + + public function testGetLocale() + { + $request = new Request(); + $request->setLocale('pl'); + $locale = $request->getLocale(); + $this->assertEquals('pl', $locale); + } + + public function testGetUser() + { + $request = Request::create('http://user_test:password_test@test.com/'); + $user = $request->getUser(); + + $this->assertEquals('user_test', $user); + } + + public function testGetPassword() + { + $request = Request::create('http://user_test:password_test@test.com/'); + $password = $request->getPassword(); + + $this->assertEquals('password_test', $password); + } + + public function testIsNoCache() + { + $request = new Request(); + $isNoCache = $request->isNoCache(); + + $this->assertFalse($isNoCache); + } + + public function testGetContentType() + { + $request = new Request(); + $contentType = $request->getContentType(); + + $this->assertNull($contentType); + } + + public function testSetDefaultLocale() + { + $request = new Request(); + $request->setDefaultLocale('pl'); + $locale = $request->getLocale(); + + $this->assertEquals('pl', $locale); + } + + public function testCreate() + { + $request = Request::create('http://test.com/foo?bar=baz'); + $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('bar=baz', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com/foo', 'GET', array('bar' => 'baz')); + $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('bar=baz', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com/foo?bar=foo', 'GET', array('bar' => 'baz')); + $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('bar=baz', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('https://test.com/foo?bar=baz'); + $this->assertEquals('https://test.com/foo?bar=baz', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('bar=baz', $request->getQueryString()); + $this->assertEquals(443, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertTrue($request->isSecure()); + + $request = Request::create('test.com:90/foo'); + $this->assertEquals('http://test.com:90/foo', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('test.com', $request->getHost()); + $this->assertEquals('test.com:90', $request->getHttpHost()); + $this->assertEquals(90, $request->getPort()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('https://test.com:90/foo'); + $this->assertEquals('https://test.com:90/foo', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('test.com', $request->getHost()); + $this->assertEquals('test.com:90', $request->getHttpHost()); + $this->assertEquals(90, $request->getPort()); + $this->assertTrue($request->isSecure()); + + $request = Request::create('https://127.0.0.1:90/foo'); + $this->assertEquals('https://127.0.0.1:90/foo', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('127.0.0.1', $request->getHost()); + $this->assertEquals('127.0.0.1:90', $request->getHttpHost()); + $this->assertEquals(90, $request->getPort()); + $this->assertTrue($request->isSecure()); + + $request = Request::create('https://[::1]:90/foo'); + $this->assertEquals('https://[::1]:90/foo', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('[::1]', $request->getHost()); + $this->assertEquals('[::1]:90', $request->getHttpHost()); + $this->assertEquals(90, $request->getPort()); + $this->assertTrue($request->isSecure()); + + $request = Request::create('https://[::1]/foo'); + $this->assertEquals('https://[::1]/foo', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('[::1]', $request->getHost()); + $this->assertEquals('[::1]', $request->getHttpHost()); + $this->assertEquals(443, $request->getPort()); + $this->assertTrue($request->isSecure()); + + $json = '{"jsonrpc":"2.0","method":"echo","id":7,"params":["Hello World"]}'; + $request = Request::create('http://example.com/jsonrpc', 'POST', array(), array(), array(), array(), $json); + $this->assertEquals($json, $request->getContent()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com'); + $this->assertEquals('http://test.com/', $request->getUri()); + $this->assertEquals('/', $request->getPathInfo()); + $this->assertEquals('', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com?test=1'); + $this->assertEquals('http://test.com/?test=1', $request->getUri()); + $this->assertEquals('/', $request->getPathInfo()); + $this->assertEquals('test=1', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com:90/?test=1'); + $this->assertEquals('http://test.com:90/?test=1', $request->getUri()); + $this->assertEquals('/', $request->getPathInfo()); + $this->assertEquals('test=1', $request->getQueryString()); + $this->assertEquals(90, $request->getPort()); + $this->assertEquals('test.com:90', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://username:password@test.com'); + $this->assertEquals('http://test.com/', $request->getUri()); + $this->assertEquals('/', $request->getPathInfo()); + $this->assertEquals('', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertEquals('username', $request->getUser()); + $this->assertEquals('password', $request->getPassword()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://username@test.com'); + $this->assertEquals('http://test.com/', $request->getUri()); + $this->assertEquals('/', $request->getPathInfo()); + $this->assertEquals('', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertEquals('username', $request->getUser()); + $this->assertSame('', $request->getPassword()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com/?foo'); + $this->assertEquals('/?foo', $request->getRequestUri()); + $this->assertEquals(array('foo' => ''), $request->query->all()); + + // assume rewrite rule: (.*) --> app/app.php; app/ is a symlink to a symfony web/ directory + $request = Request::create('http://test.com/apparthotel-1234', 'GET', array(), array(), array(), + array( + 'DOCUMENT_ROOT' => '/var/www/www.test.com', + 'SCRIPT_FILENAME' => '/var/www/www.test.com/app/app.php', + 'SCRIPT_NAME' => '/app/app.php', + 'PHP_SELF' => '/app/app.php/apparthotel-1234', + )); + $this->assertEquals('http://test.com/apparthotel-1234', $request->getUri()); + $this->assertEquals('/apparthotel-1234', $request->getPathInfo()); + $this->assertEquals('', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + } + + public function testCreateCheckPrecedence() + { + // server is used by default + $request = Request::create('/', 'DELETE', array(), array(), array(), array( + 'HTTP_HOST' => 'example.com', + 'HTTPS' => 'on', + 'SERVER_PORT' => 443, + 'PHP_AUTH_USER' => 'fabien', + 'PHP_AUTH_PW' => 'pa$$', + 'QUERY_STRING' => 'foo=bar', + 'CONTENT_TYPE' => 'application/json', + )); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(443, $request->getPort()); + $this->assertTrue($request->isSecure()); + $this->assertEquals('fabien', $request->getUser()); + $this->assertEquals('pa$$', $request->getPassword()); + $this->assertEquals('', $request->getQueryString()); + $this->assertEquals('application/json', $request->headers->get('CONTENT_TYPE')); + + // URI has precedence over server + $request = Request::create('http://thomas:pokemon@example.net:8080/?foo=bar', 'GET', array(), array(), array(), array( + 'HTTP_HOST' => 'example.com', + 'HTTPS' => 'on', + 'SERVER_PORT' => 443, + )); + $this->assertEquals('example.net', $request->getHost()); + $this->assertEquals(8080, $request->getPort()); + $this->assertFalse($request->isSecure()); + $this->assertEquals('thomas', $request->getUser()); + $this->assertEquals('pokemon', $request->getPassword()); + $this->assertEquals('foo=bar', $request->getQueryString()); + } + + public function testDuplicate() + { + $request = new Request(array('foo' => 'bar'), array('foo' => 'bar'), array('foo' => 'bar'), array(), array(), array('HTTP_FOO' => 'bar')); + $dup = $request->duplicate(); + + $this->assertEquals($request->query->all(), $dup->query->all(), '->duplicate() duplicates a request an copy the current query parameters'); + $this->assertEquals($request->request->all(), $dup->request->all(), '->duplicate() duplicates a request an copy the current request parameters'); + $this->assertEquals($request->attributes->all(), $dup->attributes->all(), '->duplicate() duplicates a request an copy the current attributes'); + $this->assertEquals($request->headers->all(), $dup->headers->all(), '->duplicate() duplicates a request an copy the current HTTP headers'); + + $dup = $request->duplicate(array('foo' => 'foobar'), array('foo' => 'foobar'), array('foo' => 'foobar'), array(), array(), array('HTTP_FOO' => 'foobar')); + + $this->assertEquals(array('foo' => 'foobar'), $dup->query->all(), '->duplicate() overrides the query parameters if provided'); + $this->assertEquals(array('foo' => 'foobar'), $dup->request->all(), '->duplicate() overrides the request parameters if provided'); + $this->assertEquals(array('foo' => 'foobar'), $dup->attributes->all(), '->duplicate() overrides the attributes if provided'); + $this->assertEquals(array('foo' => array('foobar')), $dup->headers->all(), '->duplicate() overrides the HTTP header if provided'); + } + + public function testDuplicateWithFormat() + { + $request = new Request(array(), array(), array('_format' => 'json')); + $dup = $request->duplicate(); + + $this->assertEquals('json', $dup->getRequestFormat()); + $this->assertEquals('json', $dup->attributes->get('_format')); + + $request = new Request(); + $request->setRequestFormat('xml'); + $dup = $request->duplicate(); + + $this->assertEquals('xml', $dup->getRequestFormat()); + } + + /** + * @dataProvider getFormatToMimeTypeMapProviderWithAdditionalNullFormat + */ + public function testGetFormatFromMimeType($format, $mimeTypes) + { + $request = new Request(); + foreach ($mimeTypes as $mime) { + $this->assertEquals($format, $request->getFormat($mime)); + } + $request->setFormat($format, $mimeTypes); + foreach ($mimeTypes as $mime) { + $this->assertEquals($format, $request->getFormat($mime)); + + if (null !== $format) { + $this->assertEquals($mimeTypes[0], $request->getMimeType($format)); + } + } + } + + public function getFormatToMimeTypeMapProviderWithAdditionalNullFormat() + { + return array_merge( + array(array(null, array(null, 'unexistent-mime-type'))), + $this->getFormatToMimeTypeMapProvider() + ); + } + + public function testGetFormatFromMimeTypeWithParameters() + { + $request = new Request(); + $this->assertEquals('json', $request->getFormat('application/json; charset=utf-8')); + } + + /** + * @dataProvider getFormatToMimeTypeMapProvider + */ + public function testGetMimeTypeFromFormat($format, $mimeTypes) + { + $request = new Request(); + $this->assertEquals($mimeTypes[0], $request->getMimeType($format)); + } + + /** + * @dataProvider getFormatToMimeTypeMapProvider + */ + public function testGetMimeTypesFromFormat($format, $mimeTypes) + { + $this->assertEquals($mimeTypes, Request::getMimeTypes($format)); + } + + public function testGetMimeTypesFromInexistentFormat() + { + $request = new Request(); + $this->assertNull($request->getMimeType('foo')); + $this->assertEquals(array(), Request::getMimeTypes('foo')); + } + + public function testGetFormatWithCustomMimeType() + { + $request = new Request(); + $request->setFormat('custom', 'application/vnd.foo.api;myversion=2.3'); + $this->assertEquals('custom', $request->getFormat('application/vnd.foo.api;myversion=2.3')); + } + + public function getFormatToMimeTypeMapProvider() + { + return array( + array('txt', array('text/plain')), + array('js', array('application/javascript', 'application/x-javascript', 'text/javascript')), + array('css', array('text/css')), + array('json', array('application/json', 'application/x-json')), + array('xml', array('text/xml', 'application/xml', 'application/x-xml')), + array('rdf', array('application/rdf+xml')), + array('atom', array('application/atom+xml')), + ); + } + + public function testGetUri() + { + $server = array(); + + // Standard Request on non default PORT + // http://host:8080/index.php/path/info?query=string + + $server['HTTP_HOST'] = 'host:8080'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '8080'; + + $server['QUERY_STRING'] = 'query=string'; + $server['REQUEST_URI'] = '/index.php/path/info?query=string'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['PATH_INFO'] = '/path/info'; + $server['PATH_TRANSLATED'] = 'redirect:/index.php/path/info'; + $server['PHP_SELF'] = '/index_dev.php/path/info'; + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + + $request = new Request(); + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host:8080/index.php/path/info?query=string', $request->getUri(), '->getUri() with non default port'); + + // Use std port number + $server['HTTP_HOST'] = 'host'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host/index.php/path/info?query=string', $request->getUri(), '->getUri() with default port'); + + // Without HOST HEADER + unset($server['HTTP_HOST']); + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://servername/index.php/path/info?query=string', $request->getUri(), '->getUri() with default port without HOST_HEADER'); + + // Request with URL REWRITING (hide index.php) + // RewriteCond %{REQUEST_FILENAME} !-f + // RewriteRule ^(.*)$ index.php [QSA,L] + // http://host:8080/path/info?query=string + $server = array(); + $server['HTTP_HOST'] = 'host:8080'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '8080'; + + $server['REDIRECT_QUERY_STRING'] = 'query=string'; + $server['REDIRECT_URL'] = '/path/info'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['QUERY_STRING'] = 'query=string'; + $server['REQUEST_URI'] = '/path/info?toto=test&1=1'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['PHP_SELF'] = '/index.php'; + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://host:8080/path/info?query=string', $request->getUri(), '->getUri() with rewrite'); + + // Use std port number + // http://host/path/info?query=string + $server['HTTP_HOST'] = 'host'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host/path/info?query=string', $request->getUri(), '->getUri() with rewrite and default port'); + + // Without HOST HEADER + unset($server['HTTP_HOST']); + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://servername/path/info?query=string', $request->getUri(), '->getUri() with rewrite, default port without HOST_HEADER'); + + // With encoded characters + + $server = array( + 'HTTP_HOST' => 'host:8080', + 'SERVER_NAME' => 'servername', + 'SERVER_PORT' => '8080', + 'QUERY_STRING' => 'query=string', + 'REQUEST_URI' => '/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', + 'SCRIPT_NAME' => '/ba se/index_dev.php', + 'PATH_TRANSLATED' => 'redirect:/index.php/foo bar/in+fo', + 'PHP_SELF' => '/ba se/index_dev.php/path/info', + 'SCRIPT_FILENAME' => '/some/where/ba se/index_dev.php', + ); + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals( + 'http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', + $request->getUri() + ); + + // with user info + + $server['PHP_AUTH_USER'] = 'fabien'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', $request->getUri()); + + $server['PHP_AUTH_PW'] = 'symfony'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', $request->getUri()); + } + + public function testGetUriForPath() + { + $request = Request::create('http://test.com/foo?bar=baz'); + $this->assertEquals('http://test.com/some/path', $request->getUriForPath('/some/path')); + + $request = Request::create('http://test.com:90/foo?bar=baz'); + $this->assertEquals('http://test.com:90/some/path', $request->getUriForPath('/some/path')); + + $request = Request::create('https://test.com/foo?bar=baz'); + $this->assertEquals('https://test.com/some/path', $request->getUriForPath('/some/path')); + + $request = Request::create('https://test.com:90/foo?bar=baz'); + $this->assertEquals('https://test.com:90/some/path', $request->getUriForPath('/some/path')); + + $server = array(); + + // Standard Request on non default PORT + // http://host:8080/index.php/path/info?query=string + + $server['HTTP_HOST'] = 'host:8080'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '8080'; + + $server['QUERY_STRING'] = 'query=string'; + $server['REQUEST_URI'] = '/index.php/path/info?query=string'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['PATH_INFO'] = '/path/info'; + $server['PATH_TRANSLATED'] = 'redirect:/index.php/path/info'; + $server['PHP_SELF'] = '/index_dev.php/path/info'; + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + + $request = new Request(); + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host:8080/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with non default port'); + + // Use std port number + $server['HTTP_HOST'] = 'host'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with default port'); + + // Without HOST HEADER + unset($server['HTTP_HOST']); + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://servername/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with default port without HOST_HEADER'); + + // Request with URL REWRITING (hide index.php) + // RewriteCond %{REQUEST_FILENAME} !-f + // RewriteRule ^(.*)$ index.php [QSA,L] + // http://host:8080/path/info?query=string + $server = array(); + $server['HTTP_HOST'] = 'host:8080'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '8080'; + + $server['REDIRECT_QUERY_STRING'] = 'query=string'; + $server['REDIRECT_URL'] = '/path/info'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['QUERY_STRING'] = 'query=string'; + $server['REQUEST_URI'] = '/path/info?toto=test&1=1'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['PHP_SELF'] = '/index.php'; + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://host:8080/some/path', $request->getUriForPath('/some/path'), '->getUri() with rewrite'); + + // Use std port number + // http://host/path/info?query=string + $server['HTTP_HOST'] = 'host'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with rewrite and default port'); + + // Without HOST HEADER + unset($server['HTTP_HOST']); + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with rewrite, default port without HOST_HEADER'); + $this->assertEquals('servername', $request->getHttpHost()); + + // with user info + + $server['PHP_AUTH_USER'] = 'fabien'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path')); + + $server['PHP_AUTH_PW'] = 'symfony'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path')); + } + + /** + * @dataProvider getRelativeUriForPathData() + */ + public function testGetRelativeUriForPath($expected, $pathinfo, $path) + { + $this->assertEquals($expected, Request::create($pathinfo)->getRelativeUriForPath($path)); + } + + public function getRelativeUriForPathData() + { + return array( + array('me.png', '/foo', '/me.png'), + array('../me.png', '/foo/bar', '/me.png'), + array('me.png', '/foo/bar', '/foo/me.png'), + array('../baz/me.png', '/foo/bar/b', '/foo/baz/me.png'), + array('../../fooz/baz/me.png', '/foo/bar/b', '/fooz/baz/me.png'), + array('baz/me.png', '/foo/bar/b', 'baz/me.png'), + ); + } + + public function testGetUserInfo() + { + $request = new Request(); + + $server = array('PHP_AUTH_USER' => 'fabien'); + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('fabien', $request->getUserInfo()); + + $server['PHP_AUTH_USER'] = '0'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('0', $request->getUserInfo()); + + $server['PHP_AUTH_PW'] = '0'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('0:0', $request->getUserInfo()); + } + + public function testGetSchemeAndHttpHost() + { + $request = new Request(); + + $server = array(); + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '90'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost()); + + $server['PHP_AUTH_USER'] = 'fabien'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost()); + + $server['PHP_AUTH_USER'] = '0'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost()); + + $server['PHP_AUTH_PW'] = '0'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost()); + } + + /** + * @dataProvider getQueryStringNormalizationData + */ + public function testGetQueryString($query, $expectedQuery, $msg) + { + $request = new Request(); + + $request->server->set('QUERY_STRING', $query); + $this->assertSame($expectedQuery, $request->getQueryString(), $msg); + } + + public function getQueryStringNormalizationData() + { + return array( + array('foo', 'foo', 'works with valueless parameters'), + array('foo=', 'foo=', 'includes a dangling equal sign'), + array('bar=&foo=bar', 'bar=&foo=bar', '->works with empty parameters'), + array('foo=bar&bar=', 'bar=&foo=bar', 'sorts keys alphabetically'), + + // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded). + // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. + array('him=John%20Doe&her=Jane+Doe', 'her=Jane%20Doe&him=John%20Doe', 'normalizes spaces in both encodings "%20" and "+"'), + + array('foo[]=1&foo[]=2', 'foo%5B%5D=1&foo%5B%5D=2', 'allows array notation'), + array('foo=1&foo=2', 'foo=1&foo=2', 'allows repeated parameters'), + array('pa%3Dram=foo%26bar%3Dbaz&test=test', 'pa%3Dram=foo%26bar%3Dbaz&test=test', 'works with encoded delimiters'), + array('0', '0', 'allows "0"'), + array('Jane Doe&John%20Doe', 'Jane%20Doe&John%20Doe', 'normalizes encoding in keys'), + array('her=Jane Doe&him=John%20Doe', 'her=Jane%20Doe&him=John%20Doe', 'normalizes encoding in values'), + array('foo=bar&&&test&&', 'foo=bar&test', 'removes unneeded delimiters'), + array('formula=e=m*c^2', 'formula=e%3Dm%2Ac%5E2', 'correctly treats only the first "=" as delimiter and the next as value'), + + // Ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway. + // PHP also does not include them when building _GET. + array('foo=bar&=a=b&=x=y', 'foo=bar', 'removes params with empty key'), + ); + } + + public function testGetQueryStringReturnsNull() + { + $request = new Request(); + + $this->assertNull($request->getQueryString(), '->getQueryString() returns null for non-existent query string'); + + $request->server->set('QUERY_STRING', ''); + $this->assertNull($request->getQueryString(), '->getQueryString() returns null for empty query string'); + } + + public function testGetHost() + { + $request = new Request(); + + $request->initialize(array('foo' => 'bar')); + $this->assertEquals('', $request->getHost(), '->getHost() return empty string if not initialized'); + + $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.example.com')); + $this->assertEquals('www.example.com', $request->getHost(), '->getHost() from Host Header'); + + // Host header with port number + $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.example.com:8080')); + $this->assertEquals('www.example.com', $request->getHost(), '->getHost() from Host Header with port number'); + + // Server values + $request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.example.com')); + $this->assertEquals('www.example.com', $request->getHost(), '->getHost() from server name'); + + $request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.example.com', 'HTTP_HOST' => 'www.host.com')); + $this->assertEquals('www.host.com', $request->getHost(), '->getHost() value from Host header has priority over SERVER_NAME '); + } + + public function testGetPort() + { + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'https', + 'HTTP_X_FORWARDED_PORT' => '443', + )); + $port = $request->getPort(); + + $this->assertEquals(80, $port, 'Without trusted proxies FORWARDED_PROTO and FORWARDED_PORT are ignored.'); + + Request::setTrustedProxies(array('1.1.1.1'), Request::HEADER_X_FORWARDED_ALL); + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'https', + 'HTTP_X_FORWARDED_PORT' => '8443', + )); + $this->assertEquals(80, $request->getPort(), 'With PROTO and PORT on untrusted connection server value takes precedence.'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $this->assertEquals(8443, $request->getPort(), 'With PROTO and PORT set PORT takes precedence.'); + + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'https', + )); + $this->assertEquals(80, $request->getPort(), 'With only PROTO set getPort() ignores trusted headers on untrusted connection.'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $this->assertEquals(443, $request->getPort(), 'With only PROTO set getPort() defaults to 443.'); + + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'http', + )); + $this->assertEquals(80, $request->getPort(), 'If X_FORWARDED_PROTO is set to HTTP getPort() ignores trusted headers on untrusted connection.'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $this->assertEquals(80, $request->getPort(), 'If X_FORWARDED_PROTO is set to HTTP getPort() returns port of the original request.'); + + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'On', + )); + $this->assertEquals(80, $request->getPort(), 'With only PROTO set and value is On, getPort() ignores trusted headers on untrusted connection.'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $this->assertEquals(443, $request->getPort(), 'With only PROTO set and value is On, getPort() defaults to 443.'); + + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => '1', + )); + $this->assertEquals(80, $request->getPort(), 'With only PROTO set and value is 1, getPort() ignores trusted headers on untrusted connection.'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $this->assertEquals(443, $request->getPort(), 'With only PROTO set and value is 1, getPort() defaults to 443.'); + + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'something-else', + )); + $port = $request->getPort(); + $this->assertEquals(80, $port, 'With only PROTO set and value is not recognized, getPort() defaults to 80.'); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetHostWithFakeHttpHostValue() + { + $request = new Request(); + $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.host.com?query=string')); + $request->getHost(); + } + + public function testGetSetMethod() + { + $request = new Request(); + + $this->assertEquals('GET', $request->getMethod(), '->getMethod() returns GET if no method is defined'); + + $request->setMethod('get'); + $this->assertEquals('GET', $request->getMethod(), '->getMethod() returns an uppercased string'); + + $request->setMethod('PURGE'); + $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method even if it is not a standard one'); + + $request->setMethod('POST'); + $this->assertEquals('POST', $request->getMethod(), '->getMethod() returns the method POST if no _method is defined'); + + $request->setMethod('POST'); + $request->request->set('_method', 'purge'); + $this->assertEquals('POST', $request->getMethod(), '->getMethod() does not return the method from _method if defined and POST but support not enabled'); + + $request = new Request(); + $request->setMethod('POST'); + $request->request->set('_method', 'purge'); + + $this->assertFalse(Request::getHttpMethodParameterOverride(), 'httpMethodParameterOverride should be disabled by default'); + + Request::enableHttpMethodParameterOverride(); + + $this->assertTrue(Request::getHttpMethodParameterOverride(), 'httpMethodParameterOverride should be enabled now but it is not'); + + $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST'); + $this->disableHttpMethodParameterOverride(); + + $request = new Request(); + $request->setMethod('POST'); + $request->query->set('_method', 'purge'); + $this->assertEquals('POST', $request->getMethod(), '->getMethod() does not return the method from _method if defined and POST but support not enabled'); + + $request = new Request(); + $request->setMethod('POST'); + $request->query->set('_method', 'purge'); + Request::enableHttpMethodParameterOverride(); + $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST'); + $this->disableHttpMethodParameterOverride(); + + $request = new Request(); + $request->setMethod('POST'); + $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'delete'); + $this->assertEquals('DELETE', $request->getMethod(), '->getMethod() returns the method from X-HTTP-Method-Override even though _method is set if defined and POST'); + + $request = new Request(); + $request->setMethod('POST'); + $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'delete'); + $this->assertEquals('DELETE', $request->getMethod(), '->getMethod() returns the method from X-HTTP-Method-Override if defined and POST'); + } + + /** + * @dataProvider getClientIpsProvider + */ + public function testGetClientIp($expected, $remoteAddr, $httpForwardedFor, $trustedProxies) + { + $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies); + + $this->assertEquals($expected[0], $request->getClientIp()); + } + + /** + * @dataProvider getClientIpsProvider + */ + public function testGetClientIps($expected, $remoteAddr, $httpForwardedFor, $trustedProxies) + { + $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies); + + $this->assertEquals($expected, $request->getClientIps()); + } + + /** + * @dataProvider getClientIpsForwardedProvider + */ + public function testGetClientIpsForwarded($expected, $remoteAddr, $httpForwarded, $trustedProxies) + { + $request = $this->getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies); + + $this->assertEquals($expected, $request->getClientIps()); + } + + public function getClientIpsForwardedProvider() + { + // $expected $remoteAddr $httpForwarded $trustedProxies + return array( + array(array('127.0.0.1'), '127.0.0.1', 'for="_gazonk"', null), + array(array('127.0.0.1'), '127.0.0.1', 'for="_gazonk"', array('127.0.0.1')), + array(array('88.88.88.88'), '127.0.0.1', 'for="88.88.88.88:80"', array('127.0.0.1')), + array(array('192.0.2.60'), '::1', 'for=192.0.2.60;proto=http;by=203.0.113.43', array('::1')), + array(array('2620:0:1cfe:face:b00c::3', '192.0.2.43'), '::1', 'for=192.0.2.43, for=2620:0:1cfe:face:b00c::3', array('::1')), + array(array('2001:db8:cafe::17'), '::1', 'for="[2001:db8:cafe::17]:4711', array('::1')), + ); + } + + public function getClientIpsProvider() + { + // $expected $remoteAddr $httpForwardedFor $trustedProxies + return array( + // simple IPv4 + array(array('88.88.88.88'), '88.88.88.88', null, null), + // trust the IPv4 remote addr + array(array('88.88.88.88'), '88.88.88.88', null, array('88.88.88.88')), + + // simple IPv6 + array(array('::1'), '::1', null, null), + // trust the IPv6 remote addr + array(array('::1'), '::1', null, array('::1')), + + // forwarded for with remote IPv4 addr not trusted + array(array('127.0.0.1'), '127.0.0.1', '88.88.88.88', null), + // forwarded for with remote IPv4 addr trusted + array(array('88.88.88.88'), '127.0.0.1', '88.88.88.88', array('127.0.0.1')), + // forwarded for with remote IPv4 and all FF addrs trusted + array(array('88.88.88.88'), '127.0.0.1', '88.88.88.88', array('127.0.0.1', '88.88.88.88')), + // forwarded for with remote IPv4 range trusted + array(array('88.88.88.88'), '123.45.67.89', '88.88.88.88', array('123.45.67.0/24')), + + // forwarded for with remote IPv6 addr not trusted + array(array('1620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3', null), + // forwarded for with remote IPv6 addr trusted + array(array('2620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3')), + // forwarded for with remote IPv6 range trusted + array(array('88.88.88.88'), '2a01:198:603:0:396e:4789:8e99:890f', '88.88.88.88', array('2a01:198:603:0::/65')), + + // multiple forwarded for with remote IPv4 addr trusted + array(array('88.88.88.88', '87.65.43.21', '127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89')), + // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted + array(array('87.65.43.21', '127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '88.88.88.88')), + // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted but in the middle + array(array('88.88.88.88', '127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '87.65.43.21')), + // multiple forwarded for with remote IPv4 addr and all reverse proxies trusted + array(array('127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '87.65.43.21', '88.88.88.88', '127.0.0.1')), + + // multiple forwarded for with remote IPv6 addr trusted + array(array('2620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3')), + // multiple forwarded for with remote IPv6 addr and some reverse proxies trusted + array(array('3620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3')), + // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted but in the middle + array(array('2620:0:1cfe:face:b00c::3', '4620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '4620:0:1cfe:face:b00c::3,3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3')), + + // client IP with port + array(array('88.88.88.88'), '127.0.0.1', '88.88.88.88:12345, 127.0.0.1', array('127.0.0.1')), + + // invalid forwarded IP is ignored + array(array('88.88.88.88'), '127.0.0.1', 'unknown,88.88.88.88', array('127.0.0.1')), + array(array('88.88.88.88'), '127.0.0.1', '}__test|O:21:"JDatabaseDriverMysqli":3:{s:2,88.88.88.88', array('127.0.0.1')), + ); + } + + /** + * @expectedException \Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException + * @dataProvider getClientIpsWithConflictingHeadersProvider + */ + public function testGetClientIpsWithConflictingHeaders($httpForwarded, $httpXForwardedFor) + { + $request = new Request(); + + $server = array( + 'REMOTE_ADDR' => '88.88.88.88', + 'HTTP_FORWARDED' => $httpForwarded, + 'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor, + ); + + Request::setTrustedProxies(array('88.88.88.88'), Request::HEADER_X_FORWARDED_ALL | Request::HEADER_FORWARDED); + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $request->getClientIps(); + } + + public function getClientIpsWithConflictingHeadersProvider() + { + // $httpForwarded $httpXForwardedFor + return array( + array('for=87.65.43.21', '192.0.2.60'), + array('for=87.65.43.21, for=192.0.2.60', '192.0.2.60'), + array('for=192.0.2.60', '192.0.2.60,87.65.43.21'), + array('for="::face", for=192.0.2.60', '192.0.2.60,192.0.2.43'), + array('for=87.65.43.21, for=192.0.2.60', '192.0.2.60,87.65.43.21'), + ); + } + + /** + * @dataProvider getClientIpsWithAgreeingHeadersProvider + */ + public function testGetClientIpsWithAgreeingHeaders($httpForwarded, $httpXForwardedFor, $expectedIps) + { + $request = new Request(); + + $server = array( + 'REMOTE_ADDR' => '88.88.88.88', + 'HTTP_FORWARDED' => $httpForwarded, + 'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor, + ); + + Request::setTrustedProxies(array('88.88.88.88'), Request::HEADER_X_FORWARDED_ALL); + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $clientIps = $request->getClientIps(); + + $this->assertSame($expectedIps, $clientIps); + } + + public function getClientIpsWithAgreeingHeadersProvider() + { + // $httpForwarded $httpXForwardedFor + return array( + array('for="192.0.2.60"', '192.0.2.60', array('192.0.2.60')), + array('for=192.0.2.60, for=87.65.43.21', '192.0.2.60,87.65.43.21', array('87.65.43.21', '192.0.2.60')), + array('for="[::face]", for=192.0.2.60', '::face,192.0.2.60', array('192.0.2.60', '::face')), + array('for="192.0.2.60:80"', '192.0.2.60', array('192.0.2.60')), + array('for=192.0.2.60;proto=http;by=203.0.113.43', '192.0.2.60', array('192.0.2.60')), + array('for="[2001:db8:cafe::17]:4711"', '2001:db8:cafe::17', array('2001:db8:cafe::17')), + ); + } + + public function testGetContentWorksTwiceInDefaultMode() + { + $req = new Request(); + $this->assertEquals('', $req->getContent()); + $this->assertEquals('', $req->getContent()); + } + + public function testGetContentReturnsResource() + { + $req = new Request(); + $retval = $req->getContent(true); + $this->assertInternalType('resource', $retval); + $this->assertEquals('', fread($retval, 1)); + $this->assertTrue(feof($retval)); + } + + public function testGetContentReturnsResourceWhenContentSetInConstructor() + { + $req = new Request(array(), array(), array(), array(), array(), array(), 'MyContent'); + $resource = $req->getContent(true); + + $this->assertInternalType('resource', $resource); + $this->assertEquals('MyContent', stream_get_contents($resource)); + } + + public function testContentAsResource() + { + $resource = fopen('php://memory', 'r+'); + fwrite($resource, 'My other content'); + rewind($resource); + + $req = new Request(array(), array(), array(), array(), array(), array(), $resource); + $this->assertEquals('My other content', stream_get_contents($req->getContent(true))); + $this->assertEquals('My other content', $req->getContent()); + } + + /** + * @expectedException \LogicException + * @dataProvider getContentCantBeCalledTwiceWithResourcesProvider + */ + public function testGetContentCantBeCalledTwiceWithResources($first, $second) + { + if (\PHP_VERSION_ID >= 50600) { + $this->markTestSkipped('PHP >= 5.6 allows to open php://input several times.'); + } + + $req = new Request(); + $req->getContent($first); + $req->getContent($second); + } + + public function getContentCantBeCalledTwiceWithResourcesProvider() + { + return array( + 'Resource then fetch' => array(true, false), + 'Resource then resource' => array(true, true), + ); + } + + /** + * @dataProvider getContentCanBeCalledTwiceWithResourcesProvider + * @requires PHP 5.6 + */ + public function testGetContentCanBeCalledTwiceWithResources($first, $second) + { + $req = new Request(); + $a = $req->getContent($first); + $b = $req->getContent($second); + + if ($first) { + $a = stream_get_contents($a); + } + + if ($second) { + $b = stream_get_contents($b); + } + + $this->assertSame($a, $b); + } + + public function getContentCanBeCalledTwiceWithResourcesProvider() + { + return array( + 'Fetch then fetch' => array(false, false), + 'Fetch then resource' => array(false, true), + 'Resource then fetch' => array(true, false), + 'Resource then resource' => array(true, true), + ); + } + + public function provideOverloadedMethods() + { + return array( + array('PUT'), + array('DELETE'), + array('PATCH'), + array('put'), + array('delete'), + array('patch'), + ); + } + + /** + * @dataProvider provideOverloadedMethods + */ + public function testCreateFromGlobals($method) + { + $normalizedMethod = strtoupper($method); + + $_GET['foo1'] = 'bar1'; + $_POST['foo2'] = 'bar2'; + $_COOKIE['foo3'] = 'bar3'; + $_FILES['foo4'] = array('bar4'); + $_SERVER['foo5'] = 'bar5'; + + $request = Request::createFromGlobals(); + $this->assertEquals('bar1', $request->query->get('foo1'), '::fromGlobals() uses values from $_GET'); + $this->assertEquals('bar2', $request->request->get('foo2'), '::fromGlobals() uses values from $_POST'); + $this->assertEquals('bar3', $request->cookies->get('foo3'), '::fromGlobals() uses values from $_COOKIE'); + $this->assertEquals(array('bar4'), $request->files->get('foo4'), '::fromGlobals() uses values from $_FILES'); + $this->assertEquals('bar5', $request->server->get('foo5'), '::fromGlobals() uses values from $_SERVER'); + + unset($_GET['foo1'], $_POST['foo2'], $_COOKIE['foo3'], $_FILES['foo4'], $_SERVER['foo5']); + + $_SERVER['REQUEST_METHOD'] = $method; + $_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + $request = RequestContentProxy::createFromGlobals(); + $this->assertEquals($normalizedMethod, $request->getMethod()); + $this->assertEquals('mycontent', $request->request->get('content')); + + unset($_SERVER['REQUEST_METHOD'], $_SERVER['CONTENT_TYPE']); + + Request::createFromGlobals(); + Request::enableHttpMethodParameterOverride(); + $_POST['_method'] = $method; + $_POST['foo6'] = 'bar6'; + $_SERVER['REQUEST_METHOD'] = 'PoSt'; + $request = Request::createFromGlobals(); + $this->assertEquals($normalizedMethod, $request->getMethod()); + $this->assertEquals('POST', $request->getRealMethod()); + $this->assertEquals('bar6', $request->request->get('foo6')); + + unset($_POST['_method'], $_POST['foo6'], $_SERVER['REQUEST_METHOD']); + $this->disableHttpMethodParameterOverride(); + } + + public function testOverrideGlobals() + { + $request = new Request(); + $request->initialize(array('foo' => 'bar')); + + // as the Request::overrideGlobals really work, it erase $_SERVER, so we must backup it + $server = $_SERVER; + + $request->overrideGlobals(); + + $this->assertEquals(array('foo' => 'bar'), $_GET); + + $request->initialize(array(), array('foo' => 'bar')); + $request->overrideGlobals(); + + $this->assertEquals(array('foo' => 'bar'), $_POST); + + $this->assertArrayNotHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER); + + $request->headers->set('X_FORWARDED_PROTO', 'https'); + + Request::setTrustedProxies(array('1.1.1.1'), Request::HEADER_X_FORWARDED_ALL); + $this->assertFalse($request->isSecure()); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $this->assertTrue($request->isSecure()); + + $request->overrideGlobals(); + + $this->assertArrayHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER); + + $request->headers->set('CONTENT_TYPE', 'multipart/form-data'); + $request->headers->set('CONTENT_LENGTH', 12345); + + $request->overrideGlobals(); + + $this->assertArrayHasKey('CONTENT_TYPE', $_SERVER); + $this->assertArrayHasKey('CONTENT_LENGTH', $_SERVER); + + $request->initialize(array('foo' => 'bar', 'baz' => 'foo')); + $request->query->remove('baz'); + + $request->overrideGlobals(); + + $this->assertEquals(array('foo' => 'bar'), $_GET); + $this->assertEquals('foo=bar', $_SERVER['QUERY_STRING']); + $this->assertEquals('foo=bar', $request->server->get('QUERY_STRING')); + + // restore initial $_SERVER array + $_SERVER = $server; + } + + public function testGetScriptName() + { + $request = new Request(); + $this->assertEquals('', $request->getScriptName()); + + $server = array(); + $server['SCRIPT_NAME'] = '/index.php'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/index.php', $request->getScriptName()); + + $server = array(); + $server['ORIG_SCRIPT_NAME'] = '/frontend.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/frontend.php', $request->getScriptName()); + + $server = array(); + $server['SCRIPT_NAME'] = '/index.php'; + $server['ORIG_SCRIPT_NAME'] = '/frontend.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/index.php', $request->getScriptName()); + } + + public function testGetBasePath() + { + $request = new Request(); + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $server['SCRIPT_NAME'] = '/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $server['PHP_SELF'] = '/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $server['ORIG_SCRIPT_NAME'] = '/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('', $request->getBasePath()); + } + + public function testGetPathInfo() + { + $request = new Request(); + $this->assertEquals('/', $request->getPathInfo()); + + $server = array(); + $server['REQUEST_URI'] = '/path/info'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/path/info', $request->getPathInfo()); + + $server = array(); + $server['REQUEST_URI'] = '/path%20test/info'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/path%20test/info', $request->getPathInfo()); + } + + public function testGetParameterPrecedence() + { + $request = new Request(); + $request->attributes->set('foo', 'attr'); + $request->query->set('foo', 'query'); + $request->request->set('foo', 'body'); + + $this->assertSame('attr', $request->get('foo')); + + $request->attributes->remove('foo'); + $this->assertSame('query', $request->get('foo')); + + $request->query->remove('foo'); + $this->assertSame('body', $request->get('foo')); + + $request->request->remove('foo'); + $this->assertNull($request->get('foo')); + } + + public function testGetPreferredLanguage() + { + $request = new Request(); + $this->assertNull($request->getPreferredLanguage()); + $this->assertNull($request->getPreferredLanguage(array())); + $this->assertEquals('fr', $request->getPreferredLanguage(array('fr'))); + $this->assertEquals('fr', $request->getPreferredLanguage(array('fr', 'en'))); + $this->assertEquals('en', $request->getPreferredLanguage(array('en', 'fr'))); + $this->assertEquals('fr-ch', $request->getPreferredLanguage(array('fr-ch', 'fr-fr'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + $this->assertEquals('en', $request->getPreferredLanguage(array('en', 'en-us'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8'); + $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, fr-fr; q=0.6, fr; q=0.5'); + $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en'))); + } + + public function testIsXmlHttpRequest() + { + $request = new Request(); + $this->assertFalse($request->isXmlHttpRequest()); + + $request->headers->set('X-Requested-With', 'XMLHttpRequest'); + $this->assertTrue($request->isXmlHttpRequest()); + + $request->headers->remove('X-Requested-With'); + $this->assertFalse($request->isXmlHttpRequest()); + } + + /** + * @requires extension intl + */ + public function testIntlLocale() + { + $request = new Request(); + + $request->setDefaultLocale('fr'); + $this->assertEquals('fr', $request->getLocale()); + $this->assertEquals('fr', \Locale::getDefault()); + + $request->setLocale('en'); + $this->assertEquals('en', $request->getLocale()); + $this->assertEquals('en', \Locale::getDefault()); + + $request->setDefaultLocale('de'); + $this->assertEquals('en', $request->getLocale()); + $this->assertEquals('en', \Locale::getDefault()); + } + + public function testGetCharsets() + { + $request = new Request(); + $this->assertEquals(array(), $request->getCharsets()); + $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6'); + $this->assertEquals(array(), $request->getCharsets()); // testing caching + + $request = new Request(); + $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6'); + $this->assertEquals(array('ISO-8859-1', 'US-ASCII', 'UTF-8', 'ISO-10646-UCS-2'), $request->getCharsets()); + + $request = new Request(); + $request->headers->set('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'); + $this->assertEquals(array('ISO-8859-1', 'utf-8', '*'), $request->getCharsets()); + } + + public function testGetEncodings() + { + $request = new Request(); + $this->assertEquals(array(), $request->getEncodings()); + $request->headers->set('Accept-Encoding', 'gzip,deflate,sdch'); + $this->assertEquals(array(), $request->getEncodings()); // testing caching + + $request = new Request(); + $request->headers->set('Accept-Encoding', 'gzip,deflate,sdch'); + $this->assertEquals(array('gzip', 'deflate', 'sdch'), $request->getEncodings()); + + $request = new Request(); + $request->headers->set('Accept-Encoding', 'gzip;q=0.4,deflate;q=0.9,compress;q=0.7'); + $this->assertEquals(array('deflate', 'compress', 'gzip'), $request->getEncodings()); + } + + public function testGetAcceptableContentTypes() + { + $request = new Request(); + $this->assertEquals(array(), $request->getAcceptableContentTypes()); + $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*'); + $this->assertEquals(array(), $request->getAcceptableContentTypes()); // testing caching + + $request = new Request(); + $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*'); + $this->assertEquals(array('application/vnd.wap.wmlscriptc', 'text/vnd.wap.wml', 'application/vnd.wap.xhtml+xml', 'application/xhtml+xml', 'text/html', 'multipart/mixed', '*/*'), $request->getAcceptableContentTypes()); + } + + public function testGetLanguages() + { + $request = new Request(); + $this->assertEquals(array(), $request->getLanguages()); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + $this->assertEquals(array('zh', 'en_US', 'en'), $request->getLanguages()); + $this->assertEquals(array('zh', 'en_US', 'en'), $request->getLanguages()); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.6, en; q=0.8'); + $this->assertEquals(array('zh', 'en', 'en_US'), $request->getLanguages()); // Test out of order qvalues + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en, en-us'); + $this->assertEquals(array('zh', 'en', 'en_US'), $request->getLanguages()); // Test equal weighting without qvalues + + $request = new Request(); + $request->headers->set('Accept-language', 'zh; q=0.6, en, en-us; q=0.6'); + $this->assertEquals(array('en', 'zh', 'en_US'), $request->getLanguages()); // Test equal weighting with qvalues + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, i-cherokee; q=0.6'); + $this->assertEquals(array('zh', 'cherokee'), $request->getLanguages()); + } + + public function testGetRequestFormat() + { + $request = new Request(); + $this->assertEquals('html', $request->getRequestFormat()); + + // Ensure that setting different default values over time is possible, + // aka. setRequestFormat determines the state. + $this->assertEquals('json', $request->getRequestFormat('json')); + $this->assertEquals('html', $request->getRequestFormat('html')); + + $request = new Request(); + $this->assertNull($request->getRequestFormat(null)); + + $request = new Request(); + $this->assertNull($request->setRequestFormat('foo')); + $this->assertEquals('foo', $request->getRequestFormat(null)); + + $request = new Request(array('_format' => 'foo')); + $this->assertEquals('html', $request->getRequestFormat()); + } + + public function testHasSession() + { + $request = new Request(); + + $this->assertFalse($request->hasSession()); + $request->setSession(new Session(new MockArraySessionStorage())); + $this->assertTrue($request->hasSession()); + } + + public function testGetSession() + { + $request = new Request(); + + $request->setSession(new Session(new MockArraySessionStorage())); + $this->assertTrue($request->hasSession()); + + $session = $request->getSession(); + $this->assertObjectHasAttribute('storage', $session); + $this->assertObjectHasAttribute('flashName', $session); + $this->assertObjectHasAttribute('attributeName', $session); + } + + public function testHasPreviousSession() + { + $request = new Request(); + + $this->assertFalse($request->hasPreviousSession()); + $request->cookies->set('MOCKSESSID', 'foo'); + $this->assertFalse($request->hasPreviousSession()); + $request->setSession(new Session(new MockArraySessionStorage())); + $this->assertTrue($request->hasPreviousSession()); + } + + public function testToString() + { + $request = new Request(); + + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + + $this->assertContains('Accept-Language: zh, en-us; q=0.8, en; q=0.6', $request->__toString()); + } + + public function testIsMethod() + { + $request = new Request(); + $request->setMethod('POST'); + $this->assertTrue($request->isMethod('POST')); + $this->assertTrue($request->isMethod('post')); + $this->assertFalse($request->isMethod('GET')); + $this->assertFalse($request->isMethod('get')); + + $request->setMethod('GET'); + $this->assertTrue($request->isMethod('GET')); + $this->assertTrue($request->isMethod('get')); + $this->assertFalse($request->isMethod('POST')); + $this->assertFalse($request->isMethod('post')); + } + + /** + * @dataProvider getBaseUrlData + */ + public function testGetBaseUrl($uri, $server, $expectedBaseUrl, $expectedPathInfo) + { + $request = Request::create($uri, 'GET', array(), array(), array(), $server); + + $this->assertSame($expectedBaseUrl, $request->getBaseUrl(), 'baseUrl'); + $this->assertSame($expectedPathInfo, $request->getPathInfo(), 'pathInfo'); + } + + public function getBaseUrlData() + { + return array( + array( + '/fruit/strawberry/1234index.php/blah', + array( + 'SCRIPT_FILENAME' => 'E:/Sites/cc-new/public_html/fruit/index.php', + 'SCRIPT_NAME' => '/fruit/index.php', + 'PHP_SELF' => '/fruit/index.php', + ), + '/fruit', + '/strawberry/1234index.php/blah', + ), + array( + '/fruit/strawberry/1234index.php/blah', + array( + 'SCRIPT_FILENAME' => 'E:/Sites/cc-new/public_html/index.php', + 'SCRIPT_NAME' => '/index.php', + 'PHP_SELF' => '/index.php', + ), + '', + '/fruit/strawberry/1234index.php/blah', + ), + array( + '/foo%20bar/', + array( + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php', + 'SCRIPT_NAME' => '/foo bar/app.php', + 'PHP_SELF' => '/foo bar/app.php', + ), + '/foo%20bar', + '/', + ), + array( + '/foo%20bar/home', + array( + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php', + 'SCRIPT_NAME' => '/foo bar/app.php', + 'PHP_SELF' => '/foo bar/app.php', + ), + '/foo%20bar', + '/home', + ), + array( + '/foo%20bar/app.php/home', + array( + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php', + 'SCRIPT_NAME' => '/foo bar/app.php', + 'PHP_SELF' => '/foo bar/app.php', + ), + '/foo%20bar/app.php', + '/home', + ), + array( + '/foo%20bar/app.php/home%3Dbaz', + array( + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php', + 'SCRIPT_NAME' => '/foo bar/app.php', + 'PHP_SELF' => '/foo bar/app.php', + ), + '/foo%20bar/app.php', + '/home%3Dbaz', + ), + array( + '/foo/bar+baz', + array( + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo/app.php', + 'SCRIPT_NAME' => '/foo/app.php', + 'PHP_SELF' => '/foo/app.php', + ), + '/foo', + '/bar+baz', + ), + ); + } + + /** + * @dataProvider urlencodedStringPrefixData + */ + public function testUrlencodedStringPrefix($string, $prefix, $expect) + { + $request = new Request(); + + $me = new \ReflectionMethod($request, 'getUrlencodedPrefix'); + $me->setAccessible(true); + + $this->assertSame($expect, $me->invoke($request, $string, $prefix)); + } + + public function urlencodedStringPrefixData() + { + return array( + array('foo', 'foo', 'foo'), + array('fo%6f', 'foo', 'fo%6f'), + array('foo/bar', 'foo', 'foo'), + array('fo%6f/bar', 'foo', 'fo%6f'), + array('f%6f%6f/bar', 'foo', 'f%6f%6f'), + array('%66%6F%6F/bar', 'foo', '%66%6F%6F'), + array('fo+o/bar', 'fo+o', 'fo+o'), + array('fo%2Bo/bar', 'fo+o', 'fo%2Bo'), + ); + } + + private function disableHttpMethodParameterOverride() + { + $class = new \ReflectionClass('Symfony\\Component\\HttpFoundation\\Request'); + $property = $class->getProperty('httpMethodParameterOverride'); + $property->setAccessible(true); + $property->setValue(false); + } + + private function getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies) + { + $request = new Request(); + + $server = array('REMOTE_ADDR' => $remoteAddr); + if (null !== $httpForwardedFor) { + $server['HTTP_X_FORWARDED_FOR'] = $httpForwardedFor; + } + + if ($trustedProxies) { + Request::setTrustedProxies($trustedProxies, Request::HEADER_X_FORWARDED_ALL); + } + + $request->initialize(array(), array(), array(), array(), array(), $server); + + return $request; + } + + private function getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies) + { + $request = new Request(); + + $server = array('REMOTE_ADDR' => $remoteAddr); + + if (null !== $httpForwarded) { + $server['HTTP_FORWARDED'] = $httpForwarded; + } + + if ($trustedProxies) { + Request::setTrustedProxies($trustedProxies, Request::HEADER_FORWARDED); + } + + $request->initialize(array(), array(), array(), array(), array(), $server); + + return $request; + } + + public function testTrustedProxiesXForwardedFor() + { + $request = Request::create('http://example.com/'); + $request->server->set('REMOTE_ADDR', '3.3.3.3'); + $request->headers->set('X_FORWARDED_FOR', '1.1.1.1, 2.2.2.2'); + $request->headers->set('X_FORWARDED_HOST', 'foo.example.com:1234, real.example.com:8080'); + $request->headers->set('X_FORWARDED_PROTO', 'https'); + $request->headers->set('X_FORWARDED_PORT', 443); + + // no trusted proxies + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // disabling proxy trusting + Request::setTrustedProxies(array(), Request::HEADER_X_FORWARDED_ALL); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // request is forwarded by a non-trusted proxy + Request::setTrustedProxies(array('2.2.2.2'), Request::HEADER_X_FORWARDED_ALL); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // trusted proxy via setTrustedProxies() + Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL); + $this->assertEquals('1.1.1.1', $request->getClientIp()); + $this->assertEquals('foo.example.com', $request->getHost()); + $this->assertEquals(443, $request->getPort()); + $this->assertTrue($request->isSecure()); + + // trusted proxy via setTrustedProxies() + Request::setTrustedProxies(array('3.3.3.4', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // check various X_FORWARDED_PROTO header values + Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL); + $request->headers->set('X_FORWARDED_PROTO', 'ssl'); + $this->assertTrue($request->isSecure()); + + $request->headers->set('X_FORWARDED_PROTO', 'https, http'); + $this->assertTrue($request->isSecure()); + } + + /** + * @group legacy + * @expectedDeprecation The "Symfony\Component\HttpFoundation\Request::setTrustedHeaderName()" method is deprecated since version 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead. + */ + public function testLegacyTrustedProxies() + { + $request = Request::create('http://example.com/'); + $request->server->set('REMOTE_ADDR', '3.3.3.3'); + $request->headers->set('X_FORWARDED_FOR', '1.1.1.1, 2.2.2.2'); + $request->headers->set('X_FORWARDED_HOST', 'foo.example.com, real.example.com:8080'); + $request->headers->set('X_FORWARDED_PROTO', 'https'); + $request->headers->set('X_FORWARDED_PORT', 443); + $request->headers->set('X_MY_FOR', '3.3.3.3, 4.4.4.4'); + $request->headers->set('X_MY_HOST', 'my.example.com'); + $request->headers->set('X_MY_PROTO', 'http'); + $request->headers->set('X_MY_PORT', 81); + + Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL); + + // custom header names + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_MY_FOR'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_MY_HOST'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_MY_PORT'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_MY_PROTO'); + $this->assertEquals('4.4.4.4', $request->getClientIp()); + $this->assertEquals('my.example.com', $request->getHost()); + $this->assertEquals(81, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // disabling via empty header names + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, null); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, null); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, null); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, null); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + //reset + Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'FORWARDED'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_FORWARDED_FOR'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_FORWARDED_HOST'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_FORWARDED_PORT'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_FORWARDED_PROTO'); + } + + public function testTrustedProxiesForwarded() + { + $request = Request::create('http://example.com/'); + $request->server->set('REMOTE_ADDR', '3.3.3.3'); + $request->headers->set('FORWARDED', 'for=1.1.1.1, host=foo.example.com:8080, proto=https, for=2.2.2.2, host=real.example.com:8080'); + + // no trusted proxies + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // disabling proxy trusting + Request::setTrustedProxies(array(), Request::HEADER_FORWARDED); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // request is forwarded by a non-trusted proxy + Request::setTrustedProxies(array('2.2.2.2'), Request::HEADER_FORWARDED); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // trusted proxy via setTrustedProxies() + Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_FORWARDED); + $this->assertEquals('1.1.1.1', $request->getClientIp()); + $this->assertEquals('foo.example.com', $request->getHost()); + $this->assertEquals(8080, $request->getPort()); + $this->assertTrue($request->isSecure()); + + // trusted proxy via setTrustedProxies() + Request::setTrustedProxies(array('3.3.3.4', '2.2.2.2'), Request::HEADER_FORWARDED); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // check various X_FORWARDED_PROTO header values + Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_FORWARDED); + $request->headers->set('FORWARDED', 'proto=ssl'); + $this->assertTrue($request->isSecure()); + + $request->headers->set('FORWARDED', 'proto=https, proto=http'); + $this->assertTrue($request->isSecure()); + } + + /** + * @group legacy + * @expectedException \InvalidArgumentException + */ + public function testSetTrustedProxiesInvalidHeaderName() + { + Request::create('http://example.com/'); + Request::setTrustedHeaderName('bogus name', 'X_MY_FOR'); + } + + /** + * @group legacy + * @expectedException \InvalidArgumentException + */ + public function testGetTrustedProxiesInvalidHeaderName() + { + Request::create('http://example.com/'); + Request::getTrustedHeaderName('bogus name'); + } + + /** + * @dataProvider iisRequestUriProvider + */ + public function testIISRequestUri($headers, $server, $expectedRequestUri) + { + $request = new Request(); + $request->headers->replace($headers); + $request->server->replace($server); + + $this->assertEquals($expectedRequestUri, $request->getRequestUri(), '->getRequestUri() is correct'); + + $subRequestUri = '/bar/foo'; + $subRequest = Request::create($subRequestUri, 'get', array(), array(), array(), $request->server->all()); + $this->assertEquals($subRequestUri, $subRequest->getRequestUri(), '->getRequestUri() is correct in sub request'); + } + + public function iisRequestUriProvider() + { + return array( + array( + array( + 'X_ORIGINAL_URL' => '/foo/bar', + ), + array(), + '/foo/bar', + ), + array( + array( + 'X_REWRITE_URL' => '/foo/bar', + ), + array(), + '/foo/bar', + ), + array( + array(), + array( + 'IIS_WasUrlRewritten' => '1', + 'UNENCODED_URL' => '/foo/bar', + ), + '/foo/bar', + ), + array( + array( + 'X_ORIGINAL_URL' => '/foo/bar', + ), + array( + 'HTTP_X_ORIGINAL_URL' => '/foo/bar', + ), + '/foo/bar', + ), + array( + array( + 'X_ORIGINAL_URL' => '/foo/bar', + ), + array( + 'IIS_WasUrlRewritten' => '1', + 'UNENCODED_URL' => '/foo/bar', + ), + '/foo/bar', + ), + array( + array( + 'X_ORIGINAL_URL' => '/foo/bar', + ), + array( + 'HTTP_X_ORIGINAL_URL' => '/foo/bar', + 'IIS_WasUrlRewritten' => '1', + 'UNENCODED_URL' => '/foo/bar', + ), + '/foo/bar', + ), + array( + array(), + array( + 'ORIG_PATH_INFO' => '/foo/bar', + ), + '/foo/bar', + ), + array( + array(), + array( + 'ORIG_PATH_INFO' => '/foo/bar', + 'QUERY_STRING' => 'foo=bar', + ), + '/foo/bar?foo=bar', + ), + ); + } + + public function testTrustedHosts() + { + // create a request + $request = Request::create('/'); + + // no trusted host set -> no host check + $request->headers->set('host', 'evil.com'); + $this->assertEquals('evil.com', $request->getHost()); + + // add a trusted domain and all its subdomains + Request::setTrustedHosts(array('^([a-z]{9}\.)?trusted\.com$')); + + // untrusted host + $request->headers->set('host', 'evil.com'); + try { + $request->getHost(); + $this->fail('Request::getHost() should throw an exception when host is not trusted.'); + } catch (SuspiciousOperationException $e) { + $this->assertEquals('Untrusted Host "evil.com".', $e->getMessage()); + } + + // trusted hosts + $request->headers->set('host', 'trusted.com'); + $this->assertEquals('trusted.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + + $request->server->set('HTTPS', true); + $request->headers->set('host', 'trusted.com'); + $this->assertEquals('trusted.com', $request->getHost()); + $this->assertEquals(443, $request->getPort()); + $request->server->set('HTTPS', false); + + $request->headers->set('host', 'trusted.com:8000'); + $this->assertEquals('trusted.com', $request->getHost()); + $this->assertEquals(8000, $request->getPort()); + + $request->headers->set('host', 'subdomain.trusted.com'); + $this->assertEquals('subdomain.trusted.com', $request->getHost()); + + // reset request for following tests + Request::setTrustedHosts(array()); + } + + public function testFactory() + { + Request::setFactory(function (array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) { + return new NewRequest(); + }); + + $this->assertEquals('foo', Request::create('/')->getFoo()); + + Request::setFactory(null); + } + + /** + * @dataProvider getLongHostNames + */ + public function testVeryLongHosts($host) + { + $start = microtime(true); + + $request = Request::create('/'); + $request->headers->set('host', $host); + $this->assertEquals($host, $request->getHost()); + $this->assertLessThan(5, microtime(true) - $start); + } + + /** + * @dataProvider getHostValidities + */ + public function testHostValidity($host, $isValid, $expectedHost = null, $expectedPort = null) + { + $request = Request::create('/'); + $request->headers->set('host', $host); + + if ($isValid) { + $this->assertSame($expectedHost ?: $host, $request->getHost()); + if ($expectedPort) { + $this->assertSame($expectedPort, $request->getPort()); + } + } else { + if (method_exists($this, 'expectException')) { + $this->expectException(SuspiciousOperationException::class); + $this->expectExceptionMessage('Invalid Host'); + } else { + $this->setExpectedException(SuspiciousOperationException::class, 'Invalid Host'); + } + + $request->getHost(); + } + } + + public function getHostValidities() + { + return array( + array('.a', false), + array('a..', false), + array('a.', true), + array("\xE9", false), + array('[::1]', true), + array('[::1]:80', true, '[::1]', 80), + array(str_repeat('.', 101), false), + ); + } + + public function getLongHostNames() + { + return array( + array('a'.str_repeat('.a', 40000)), + array(str_repeat(':', 101)), + ); + } + + /** + * @dataProvider methodIdempotentProvider + */ + public function testMethodIdempotent($method, $idempotent) + { + $request = new Request(); + $request->setMethod($method); + $this->assertEquals($idempotent, $request->isMethodIdempotent()); + } + + public function methodIdempotentProvider() + { + return array( + array('HEAD', true), + array('GET', true), + array('POST', false), + array('PUT', true), + array('PATCH', false), + array('DELETE', true), + array('PURGE', true), + array('OPTIONS', true), + array('TRACE', true), + array('CONNECT', false), + ); + } + + /** + * @dataProvider methodSafeProvider + */ + public function testMethodSafe($method, $safe) + { + $request = new Request(); + $request->setMethod($method); + $this->assertEquals($safe, $request->isMethodSafe(false)); + } + + public function methodSafeProvider() + { + return array( + array('HEAD', true), + array('GET', true), + array('POST', false), + array('PUT', false), + array('PATCH', false), + array('DELETE', false), + array('PURGE', false), + array('OPTIONS', true), + array('TRACE', true), + array('CONNECT', false), + ); + } + + /** + * @group legacy + * @expectedDeprecation Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is deprecated since version 3.2 and will throw an exception in 4.0. Disable checking only for cacheable methods by calling the method with `false` as first argument or use the Request::isMethodCacheable() instead. + */ + public function testMethodSafeChecksCacheable() + { + $request = new Request(); + $request->setMethod('OPTIONS'); + $this->assertFalse($request->isMethodSafe()); + } + + /** + * @dataProvider methodCacheableProvider + */ + public function testMethodCacheable($method, $chacheable) + { + $request = new Request(); + $request->setMethod($method); + $this->assertEquals($chacheable, $request->isMethodCacheable()); + } + + public function methodCacheableProvider() + { + return array( + array('HEAD', true), + array('GET', true), + array('POST', false), + array('PUT', false), + array('PATCH', false), + array('DELETE', false), + array('PURGE', false), + array('OPTIONS', false), + array('TRACE', false), + array('CONNECT', false), + ); + } + + /** + * @group legacy + */ + public function testGetTrustedHeaderName() + { + Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_X_FORWARDED_ALL); + + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_FORWARDED)); + $this->assertSame('X_FORWARDED_FOR', Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)); + $this->assertSame('X_FORWARDED_HOST', Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST)); + $this->assertSame('X_FORWARDED_PORT', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT)); + $this->assertSame('X_FORWARDED_PROTO', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO)); + + Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_FORWARDED); + + $this->assertSame('FORWARDED', Request::getTrustedHeaderName(Request::HEADER_FORWARDED)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO)); + + Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'A'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'B'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'C'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'D'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'E'); + + Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_FORWARDED); + + $this->assertSame('A', Request::getTrustedHeaderName(Request::HEADER_FORWARDED)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO)); + + Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_X_FORWARDED_ALL); + + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_FORWARDED)); + $this->assertSame('B', Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)); + $this->assertSame('C', Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST)); + $this->assertSame('D', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT)); + $this->assertSame('E', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO)); + + Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_FORWARDED); + + $this->assertSame('A', Request::getTrustedHeaderName(Request::HEADER_FORWARDED)); + + //reset + Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'FORWARDED'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_FORWARDED_FOR'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_FORWARDED_HOST'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_FORWARDED_PORT'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_FORWARDED_PROTO'); + } +} + +class RequestContentProxy extends Request +{ + public function getContent($asResource = false) + { + return http_build_query(array('_method' => 'PUT', 'content' => 'mycontent')); + } +} + +class NewRequest extends Request +{ + public function getFoo() + { + return 'foo'; + } +} diff --git a/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php b/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php new file mode 100644 index 00000000..41365672 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php @@ -0,0 +1,339 @@ + + * + * 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\ResponseHeaderBag; +use Symfony\Component\HttpFoundation\Cookie; + +/** + * @group time-sensitive + */ +class ResponseHeaderBagTest extends TestCase +{ + /** + * @dataProvider provideAllPreserveCase + */ + public function testAllPreserveCase($headers, $expected) + { + $bag = new ResponseHeaderBag($headers); + + $this->assertEquals($expected, $bag->allPreserveCase(), '->allPreserveCase() gets all input keys in original case'); + } + + public function provideAllPreserveCase() + { + return array( + array( + array('fOo' => 'BAR'), + array('fOo' => array('BAR'), 'Cache-Control' => array('no-cache, private')), + ), + array( + array('ETag' => 'xyzzy'), + array('ETag' => array('xyzzy'), 'Cache-Control' => array('private, must-revalidate')), + ), + array( + array('Content-MD5' => 'Q2hlY2sgSW50ZWdyaXR5IQ=='), + array('Content-MD5' => array('Q2hlY2sgSW50ZWdyaXR5IQ=='), 'Cache-Control' => array('no-cache, private')), + ), + array( + array('P3P' => 'CP="CAO PSA OUR"'), + array('P3P' => array('CP="CAO PSA OUR"'), 'Cache-Control' => array('no-cache, private')), + ), + array( + array('WWW-Authenticate' => 'Basic realm="WallyWorld"'), + array('WWW-Authenticate' => array('Basic realm="WallyWorld"'), 'Cache-Control' => array('no-cache, private')), + ), + array( + array('X-UA-Compatible' => 'IE=edge,chrome=1'), + array('X-UA-Compatible' => array('IE=edge,chrome=1'), 'Cache-Control' => array('no-cache, private')), + ), + array( + array('X-XSS-Protection' => '1; mode=block'), + array('X-XSS-Protection' => array('1; mode=block'), 'Cache-Control' => array('no-cache, private')), + ), + ); + } + + public function testCacheControlHeader() + { + $bag = new ResponseHeaderBag(array()); + $this->assertEquals('no-cache, private', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('no-cache')); + + $bag = new ResponseHeaderBag(array('Cache-Control' => 'public')); + $this->assertEquals('public', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('public')); + + $bag = new ResponseHeaderBag(array('ETag' => 'abcde')); + $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('private')); + $this->assertTrue($bag->hasCacheControlDirective('must-revalidate')); + $this->assertFalse($bag->hasCacheControlDirective('max-age')); + + $bag = new ResponseHeaderBag(array('Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT')); + $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array( + 'Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT', + 'Cache-Control' => 'max-age=3600', + )); + $this->assertEquals('max-age=3600, private', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('Last-Modified' => 'abcde')); + $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('Etag' => 'abcde', 'Last-Modified' => 'abcde')); + $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('cache-control' => 'max-age=100')); + $this->assertEquals('max-age=100, private', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('cache-control' => 's-maxage=100')); + $this->assertEquals('s-maxage=100', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('cache-control' => 'private, max-age=100')); + $this->assertEquals('max-age=100, private', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('cache-control' => 'public, max-age=100')); + $this->assertEquals('max-age=100, public', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(); + $bag->set('Last-Modified', 'abcde'); + $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + } + + public function testCacheControlClone() + { + $headers = array('foo' => 'bar'); + $bag1 = new ResponseHeaderBag($headers); + $bag2 = new ResponseHeaderBag($bag1->allPreserveCase()); + $this->assertEquals($bag1->allPreserveCase(), $bag2->allPreserveCase()); + } + + public function testToStringIncludesCookieHeaders() + { + $bag = new ResponseHeaderBag(array()); + $bag->setCookie(new Cookie('foo', 'bar')); + + $this->assertSetCookieHeader('foo=bar; path=/; httponly', $bag); + + $bag->clearCookie('foo'); + + $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001; path=/; httponly', $bag); + } + + public function testClearCookieSecureNotHttpOnly() + { + $bag = new ResponseHeaderBag(array()); + + $bag->clearCookie('foo', '/', null, true, false); + + $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001; path=/; secure', $bag); + } + + public function testReplace() + { + $bag = new ResponseHeaderBag(array()); + $this->assertEquals('no-cache, private', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('no-cache')); + + $bag->replace(array('Cache-Control' => 'public')); + $this->assertEquals('public', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('public')); + } + + public function testReplaceWithRemove() + { + $bag = new ResponseHeaderBag(array()); + $this->assertEquals('no-cache, private', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('no-cache')); + + $bag->remove('Cache-Control'); + $bag->replace(array()); + $this->assertEquals('no-cache, private', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('no-cache')); + } + + public function testCookiesWithSameNames() + { + $bag = new ResponseHeaderBag(); + $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar')); + $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'foo.bar')); + $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'bar.foo')); + $bag->setCookie(new Cookie('foo', 'bar')); + + $this->assertCount(4, $bag->getCookies()); + $this->assertEquals('foo=bar; path=/path/foo; domain=foo.bar; httponly', $bag->get('set-cookie')); + $this->assertEquals(array( + 'foo=bar; path=/path/foo; domain=foo.bar; httponly', + 'foo=bar; path=/path/bar; domain=foo.bar; httponly', + 'foo=bar; path=/path/bar; domain=bar.foo; httponly', + 'foo=bar; path=/; httponly', + ), $bag->get('set-cookie', null, false)); + + $this->assertSetCookieHeader('foo=bar; path=/path/foo; domain=foo.bar; httponly', $bag); + $this->assertSetCookieHeader('foo=bar; path=/path/bar; domain=foo.bar; httponly', $bag); + $this->assertSetCookieHeader('foo=bar; path=/path/bar; domain=bar.foo; httponly', $bag); + $this->assertSetCookieHeader('foo=bar; path=/; httponly', $bag); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + + $this->assertTrue(isset($cookies['foo.bar']['/path/foo']['foo'])); + $this->assertTrue(isset($cookies['foo.bar']['/path/bar']['foo'])); + $this->assertTrue(isset($cookies['bar.foo']['/path/bar']['foo'])); + $this->assertTrue(isset($cookies['']['/']['foo'])); + } + + public function testRemoveCookie() + { + $bag = new ResponseHeaderBag(); + $this->assertFalse($bag->has('set-cookie')); + + $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar')); + $bag->setCookie(new Cookie('bar', 'foo', 0, '/path/bar', 'foo.bar')); + $this->assertTrue($bag->has('set-cookie')); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertTrue(isset($cookies['foo.bar']['/path/foo'])); + + $bag->removeCookie('foo', '/path/foo', 'foo.bar'); + $this->assertTrue($bag->has('set-cookie')); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertFalse(isset($cookies['foo.bar']['/path/foo'])); + + $bag->removeCookie('bar', '/path/bar', 'foo.bar'); + $this->assertFalse($bag->has('set-cookie')); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertFalse(isset($cookies['foo.bar'])); + } + + public function testRemoveCookieWithNullRemove() + { + $bag = new ResponseHeaderBag(); + $bag->setCookie(new Cookie('foo', 'bar', 0)); + $bag->setCookie(new Cookie('bar', 'foo', 0)); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertTrue(isset($cookies['']['/'])); + + $bag->removeCookie('foo', null); + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertFalse(isset($cookies['']['/']['foo'])); + + $bag->removeCookie('bar', null); + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertFalse(isset($cookies['']['/']['bar'])); + } + + public function testSetCookieHeader() + { + $bag = new ResponseHeaderBag(); + $bag->set('set-cookie', 'foo=bar'); + $this->assertEquals(array(new Cookie('foo', 'bar', 0, '/', null, false, false, true)), $bag->getCookies()); + + $bag->set('set-cookie', 'foo2=bar2', false); + $this->assertEquals(array( + new Cookie('foo', 'bar', 0, '/', null, false, false, true), + new Cookie('foo2', 'bar2', 0, '/', null, false, false, true), + ), $bag->getCookies()); + + $bag->remove('set-cookie'); + $this->assertEquals(array(), $bag->getCookies()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetCookiesWithInvalidArgument() + { + $bag = new ResponseHeaderBag(); + + $bag->getCookies('invalid_argument'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testMakeDispositionInvalidDisposition() + { + $headers = new ResponseHeaderBag(); + + $headers->makeDisposition('invalid', 'foo.html'); + } + + /** + * @dataProvider provideMakeDisposition + */ + public function testMakeDisposition($disposition, $filename, $filenameFallback, $expected) + { + $headers = new ResponseHeaderBag(); + + $this->assertEquals($expected, $headers->makeDisposition($disposition, $filename, $filenameFallback)); + } + + public function testToStringDoesntMessUpHeaders() + { + $headers = new ResponseHeaderBag(); + + $headers->set('Location', 'http://www.symfony.com'); + $headers->set('Content-type', 'text/html'); + + (string) $headers; + + $allHeaders = $headers->allPreserveCase(); + $this->assertEquals(array('http://www.symfony.com'), $allHeaders['Location']); + $this->assertEquals(array('text/html'), $allHeaders['Content-type']); + } + + public function provideMakeDisposition() + { + return array( + array('attachment', 'foo.html', 'foo.html', 'attachment; filename="foo.html"'), + array('attachment', 'foo.html', '', 'attachment; filename="foo.html"'), + array('attachment', 'foo bar.html', '', 'attachment; filename="foo bar.html"'), + array('attachment', 'foo "bar".html', '', 'attachment; filename="foo \\"bar\\".html"'), + array('attachment', 'foo%20bar.html', 'foo bar.html', 'attachment; filename="foo bar.html"; filename*=utf-8\'\'foo%2520bar.html'), + array('attachment', 'föö.html', 'foo.html', 'attachment; filename="foo.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html'), + ); + } + + /** + * @dataProvider provideMakeDispositionFail + * @expectedException \InvalidArgumentException + */ + public function testMakeDispositionFail($disposition, $filename) + { + $headers = new ResponseHeaderBag(); + + $headers->makeDisposition($disposition, $filename); + } + + public function provideMakeDispositionFail() + { + return array( + array('attachment', 'foo%20bar.html'), + array('attachment', 'foo/bar.html'), + array('attachment', '/foo.html'), + array('attachment', 'foo\bar.html'), + array('attachment', '\foo.html'), + array('attachment', 'föö.html'), + ); + } + + private function assertSetCookieHeader($expected, ResponseHeaderBag $actual) + { + $this->assertRegExp('#^Set-Cookie:\s+'.preg_quote($expected, '#').'$#m', str_replace("\r\n", "\n", (string) $actual)); + } +} diff --git a/vendor/symfony/http-foundation/Tests/ResponseTest.php b/vendor/symfony/http-foundation/Tests/ResponseTest.php new file mode 100644 index 00000000..62b8c652 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ResponseTest.php @@ -0,0 +1,981 @@ + + * + * 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 Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @group time-sensitive + */ +class ResponseTest extends ResponseTestCase +{ + public function testCreate() + { + $response = Response::create('foo', 301, array('Foo' => 'bar')); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + $this->assertEquals(301, $response->getStatusCode()); + $this->assertEquals('bar', $response->headers->get('foo')); + } + + public function testToString() + { + $response = new Response(); + $response = explode("\r\n", $response); + $this->assertEquals('HTTP/1.0 200 OK', $response[0]); + $this->assertEquals('Cache-Control: no-cache, private', $response[1]); + } + + public function testClone() + { + $response = new Response(); + $responseClone = clone $response; + $this->assertEquals($response, $responseClone); + } + + public function testSendHeaders() + { + $response = new Response(); + $headers = $response->sendHeaders(); + $this->assertObjectHasAttribute('headers', $headers); + $this->assertObjectHasAttribute('content', $headers); + $this->assertObjectHasAttribute('version', $headers); + $this->assertObjectHasAttribute('statusCode', $headers); + $this->assertObjectHasAttribute('statusText', $headers); + $this->assertObjectHasAttribute('charset', $headers); + } + + public function testSend() + { + $response = new Response(); + $responseSend = $response->send(); + $this->assertObjectHasAttribute('headers', $responseSend); + $this->assertObjectHasAttribute('content', $responseSend); + $this->assertObjectHasAttribute('version', $responseSend); + $this->assertObjectHasAttribute('statusCode', $responseSend); + $this->assertObjectHasAttribute('statusText', $responseSend); + $this->assertObjectHasAttribute('charset', $responseSend); + } + + public function testGetCharset() + { + $response = new Response(); + $charsetOrigin = 'UTF-8'; + $response->setCharset($charsetOrigin); + $charset = $response->getCharset(); + $this->assertEquals($charsetOrigin, $charset); + } + + public function testIsCacheable() + { + $response = new Response(); + $this->assertFalse($response->isCacheable()); + } + + public function testIsCacheableWithErrorCode() + { + $response = new Response('', 500); + $this->assertFalse($response->isCacheable()); + } + + public function testIsCacheableWithNoStoreDirective() + { + $response = new Response(); + $response->headers->set('cache-control', 'private'); + $this->assertFalse($response->isCacheable()); + } + + public function testIsCacheableWithSetTtl() + { + $response = new Response(); + $response->setTtl(10); + $this->assertTrue($response->isCacheable()); + } + + public function testMustRevalidate() + { + $response = new Response(); + $this->assertFalse($response->mustRevalidate()); + } + + public function testMustRevalidateWithMustRevalidateCacheControlHeader() + { + $response = new Response(); + $response->headers->set('cache-control', 'must-revalidate'); + + $this->assertTrue($response->mustRevalidate()); + } + + public function testMustRevalidateWithProxyRevalidateCacheControlHeader() + { + $response = new Response(); + $response->headers->set('cache-control', 'proxy-revalidate'); + + $this->assertTrue($response->mustRevalidate()); + } + + public function testSetNotModified() + { + $response = new Response(); + $modified = $response->setNotModified(); + $this->assertObjectHasAttribute('headers', $modified); + $this->assertObjectHasAttribute('content', $modified); + $this->assertObjectHasAttribute('version', $modified); + $this->assertObjectHasAttribute('statusCode', $modified); + $this->assertObjectHasAttribute('statusText', $modified); + $this->assertObjectHasAttribute('charset', $modified); + $this->assertEquals(304, $modified->getStatusCode()); + } + + public function testIsSuccessful() + { + $response = new Response(); + $this->assertTrue($response->isSuccessful()); + } + + public function testIsNotModified() + { + $response = new Response(); + $modified = $response->isNotModified(new Request()); + $this->assertFalse($modified); + } + + public function testIsNotModifiedNotSafe() + { + $request = Request::create('/homepage', 'POST'); + + $response = new Response(); + $this->assertFalse($response->isNotModified($request)); + } + + public function testIsNotModifiedLastModified() + { + $before = 'Sun, 25 Aug 2013 18:32:31 GMT'; + $modified = 'Sun, 25 Aug 2013 18:33:31 GMT'; + $after = 'Sun, 25 Aug 2013 19:33:31 GMT'; + + $request = new Request(); + $request->headers->set('If-Modified-Since', $modified); + + $response = new Response(); + + $response->headers->set('Last-Modified', $modified); + $this->assertTrue($response->isNotModified($request)); + + $response->headers->set('Last-Modified', $before); + $this->assertTrue($response->isNotModified($request)); + + $response->headers->set('Last-Modified', $after); + $this->assertFalse($response->isNotModified($request)); + + $response->headers->set('Last-Modified', ''); + $this->assertFalse($response->isNotModified($request)); + } + + public function testIsNotModifiedEtag() + { + $etagOne = 'randomly_generated_etag'; + $etagTwo = 'randomly_generated_etag_2'; + + $request = new Request(); + $request->headers->set('if_none_match', sprintf('%s, %s, %s', $etagOne, $etagTwo, 'etagThree')); + + $response = new Response(); + + $response->headers->set('ETag', $etagOne); + $this->assertTrue($response->isNotModified($request)); + + $response->headers->set('ETag', $etagTwo); + $this->assertTrue($response->isNotModified($request)); + + $response->headers->set('ETag', ''); + $this->assertFalse($response->isNotModified($request)); + } + + public function testIsNotModifiedLastModifiedAndEtag() + { + $before = 'Sun, 25 Aug 2013 18:32:31 GMT'; + $modified = 'Sun, 25 Aug 2013 18:33:31 GMT'; + $after = 'Sun, 25 Aug 2013 19:33:31 GMT'; + $etag = 'randomly_generated_etag'; + + $request = new Request(); + $request->headers->set('if_none_match', sprintf('%s, %s', $etag, 'etagThree')); + $request->headers->set('If-Modified-Since', $modified); + + $response = new Response(); + + $response->headers->set('ETag', $etag); + $response->headers->set('Last-Modified', $after); + $this->assertFalse($response->isNotModified($request)); + + $response->headers->set('ETag', 'non-existent-etag'); + $response->headers->set('Last-Modified', $before); + $this->assertFalse($response->isNotModified($request)); + + $response->headers->set('ETag', $etag); + $response->headers->set('Last-Modified', $modified); + $this->assertTrue($response->isNotModified($request)); + } + + public function testIsNotModifiedIfModifiedSinceAndEtagWithoutLastModified() + { + $modified = 'Sun, 25 Aug 2013 18:33:31 GMT'; + $etag = 'randomly_generated_etag'; + + $request = new Request(); + $request->headers->set('if_none_match', sprintf('%s, %s', $etag, 'etagThree')); + $request->headers->set('If-Modified-Since', $modified); + + $response = new Response(); + + $response->headers->set('ETag', $etag); + $this->assertTrue($response->isNotModified($request)); + + $response->headers->set('ETag', 'non-existent-etag'); + $this->assertFalse($response->isNotModified($request)); + } + + public function testIsValidateable() + { + $response = new Response('', 200, array('Last-Modified' => $this->createDateTimeOneHourAgo()->format(DATE_RFC2822))); + $this->assertTrue($response->isValidateable(), '->isValidateable() returns true if Last-Modified is present'); + + $response = new Response('', 200, array('ETag' => '"12345"')); + $this->assertTrue($response->isValidateable(), '->isValidateable() returns true if ETag is present'); + + $response = new Response(); + $this->assertFalse($response->isValidateable(), '->isValidateable() returns false when no validator is present'); + } + + public function testGetDate() + { + $oneHourAgo = $this->createDateTimeOneHourAgo(); + $response = new Response('', 200, array('Date' => $oneHourAgo->format(DATE_RFC2822))); + $date = $response->getDate(); + $this->assertEquals($oneHourAgo->getTimestamp(), $date->getTimestamp(), '->getDate() returns the Date header if present'); + + $response = new Response(); + $date = $response->getDate(); + $this->assertEquals(time(), $date->getTimestamp(), '->getDate() returns the current Date if no Date header present'); + + $response = new Response('', 200, array('Date' => $this->createDateTimeOneHourAgo()->format(DATE_RFC2822))); + $now = $this->createDateTimeNow(); + $response->headers->set('Date', $now->format(DATE_RFC2822)); + $date = $response->getDate(); + $this->assertEquals($now->getTimestamp(), $date->getTimestamp(), '->getDate() returns the date when the header has been modified'); + + $response = new Response('', 200); + $now = $this->createDateTimeNow(); + $response->headers->remove('Date'); + $date = $response->getDate(); + $this->assertEquals($now->getTimestamp(), $date->getTimestamp(), '->getDate() returns the current Date when the header has previously been removed'); + } + + public function testGetMaxAge() + { + $response = new Response(); + $response->headers->set('Cache-Control', 's-maxage=600, max-age=0'); + $this->assertEquals(600, $response->getMaxAge(), '->getMaxAge() uses s-maxage cache control directive when present'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=600'); + $this->assertEquals(600, $response->getMaxAge(), '->getMaxAge() falls back to max-age when no s-maxage directive present'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'must-revalidate'); + $response->headers->set('Expires', $this->createDateTimeOneHourLater()->format(DATE_RFC2822)); + $this->assertEquals(3600, $response->getMaxAge(), '->getMaxAge() falls back to Expires when no max-age or s-maxage directive present'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'must-revalidate'); + $response->headers->set('Expires', -1); + $this->assertEquals('Sat, 01 Jan 00 00:00:00 +0000', $response->getExpires()->format(DATE_RFC822)); + + $response = new Response(); + $this->assertNull($response->getMaxAge(), '->getMaxAge() returns null if no freshness information available'); + } + + public function testSetSharedMaxAge() + { + $response = new Response(); + $response->setSharedMaxAge(20); + + $cacheControl = $response->headers->get('Cache-Control'); + $this->assertEquals('public, s-maxage=20', $cacheControl); + } + + public function testIsPrivate() + { + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=100'); + $response->setPrivate(); + $this->assertEquals(100, $response->headers->getCacheControlDirective('max-age'), '->isPrivate() adds the private Cache-Control directive when set to true'); + $this->assertTrue($response->headers->getCacheControlDirective('private'), '->isPrivate() adds the private Cache-Control directive when set to true'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'public, max-age=100'); + $response->setPrivate(); + $this->assertEquals(100, $response->headers->getCacheControlDirective('max-age'), '->isPrivate() adds the private Cache-Control directive when set to true'); + $this->assertTrue($response->headers->getCacheControlDirective('private'), '->isPrivate() adds the private Cache-Control directive when set to true'); + $this->assertFalse($response->headers->hasCacheControlDirective('public'), '->isPrivate() removes the public Cache-Control directive'); + } + + public function testExpire() + { + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=100'); + $response->expire(); + $this->assertEquals(100, $response->headers->get('Age'), '->expire() sets the Age to max-age when present'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=100, s-maxage=500'); + $response->expire(); + $this->assertEquals(500, $response->headers->get('Age'), '->expire() sets the Age to s-maxage when both max-age and s-maxage are present'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=5, s-maxage=500'); + $response->headers->set('Age', '1000'); + $response->expire(); + $this->assertEquals(1000, $response->headers->get('Age'), '->expire() does nothing when the response is already stale/expired'); + + $response = new Response(); + $response->expire(); + $this->assertFalse($response->headers->has('Age'), '->expire() does nothing when the response does not include freshness information'); + + $response = new Response(); + $response->headers->set('Expires', -1); + $response->expire(); + $this->assertNull($response->headers->get('Age'), '->expire() does not set the Age when the response is expired'); + } + + public function testGetTtl() + { + $response = new Response(); + $this->assertNull($response->getTtl(), '->getTtl() returns null when no Expires or Cache-Control headers are present'); + + $response = new Response(); + $response->headers->set('Expires', $this->createDateTimeOneHourLater()->format(DATE_RFC2822)); + $this->assertEquals(3600, $response->getTtl(), '->getTtl() uses the Expires header when no max-age is present'); + + $response = new Response(); + $response->headers->set('Expires', $this->createDateTimeOneHourAgo()->format(DATE_RFC2822)); + $this->assertLessThan(0, $response->getTtl(), '->getTtl() returns negative values when Expires is in past'); + + $response = new Response(); + $response->headers->set('Expires', $response->getDate()->format(DATE_RFC2822)); + $response->headers->set('Age', 0); + $this->assertSame(0, $response->getTtl(), '->getTtl() correctly handles zero'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=60'); + $this->assertEquals(60, $response->getTtl(), '->getTtl() uses Cache-Control max-age when present'); + } + + public function testSetClientTtl() + { + $response = new Response(); + $response->setClientTtl(10); + + $this->assertEquals($response->getMaxAge(), $response->getAge() + 10); + } + + public function testGetSetProtocolVersion() + { + $response = new Response(); + + $this->assertEquals('1.0', $response->getProtocolVersion()); + + $response->setProtocolVersion('1.1'); + + $this->assertEquals('1.1', $response->getProtocolVersion()); + } + + public function testGetVary() + { + $response = new Response(); + $this->assertEquals(array(), $response->getVary(), '->getVary() returns an empty array if no Vary header is present'); + + $response = new Response(); + $response->headers->set('Vary', 'Accept-Language'); + $this->assertEquals(array('Accept-Language'), $response->getVary(), '->getVary() parses a single header name value'); + + $response = new Response(); + $response->headers->set('Vary', 'Accept-Language User-Agent X-Foo'); + $this->assertEquals(array('Accept-Language', 'User-Agent', 'X-Foo'), $response->getVary(), '->getVary() parses multiple header name values separated by spaces'); + + $response = new Response(); + $response->headers->set('Vary', 'Accept-Language,User-Agent, X-Foo'); + $this->assertEquals(array('Accept-Language', 'User-Agent', 'X-Foo'), $response->getVary(), '->getVary() parses multiple header name values separated by commas'); + + $vary = array('Accept-Language', 'User-Agent', 'X-foo'); + + $response = new Response(); + $response->headers->set('Vary', $vary); + $this->assertEquals($vary, $response->getVary(), '->getVary() parses multiple header name values in arrays'); + + $response = new Response(); + $response->headers->set('Vary', 'Accept-Language, User-Agent, X-foo'); + $this->assertEquals($vary, $response->getVary(), '->getVary() parses multiple header name values in arrays'); + } + + public function testSetVary() + { + $response = new Response(); + $response->setVary('Accept-Language'); + $this->assertEquals(array('Accept-Language'), $response->getVary()); + + $response->setVary('Accept-Language, User-Agent'); + $this->assertEquals(array('Accept-Language', 'User-Agent'), $response->getVary(), '->setVary() replace the vary header by default'); + + $response->setVary('X-Foo', false); + $this->assertEquals(array('Accept-Language', 'User-Agent', 'X-Foo'), $response->getVary(), '->setVary() doesn\'t wipe out earlier Vary headers if replace is set to false'); + } + + public function testDefaultContentType() + { + $headerMock = $this->getMockBuilder('Symfony\Component\HttpFoundation\ResponseHeaderBag')->setMethods(array('set'))->getMock(); + $headerMock->expects($this->at(0)) + ->method('set') + ->with('Content-Type', 'text/html'); + $headerMock->expects($this->at(1)) + ->method('set') + ->with('Content-Type', 'text/html; charset=UTF-8'); + + $response = new Response('foo'); + $response->headers = $headerMock; + + $response->prepare(new Request()); + } + + public function testContentTypeCharset() + { + $response = new Response(); + $response->headers->set('Content-Type', 'text/css'); + + // force fixContentType() to be called + $response->prepare(new Request()); + + $this->assertEquals('text/css; charset=UTF-8', $response->headers->get('Content-Type')); + } + + public function testPrepareDoesNothingIfContentTypeIsSet() + { + $response = new Response('foo'); + $response->headers->set('Content-Type', 'text/plain'); + + $response->prepare(new Request()); + + $this->assertEquals('text/plain; charset=UTF-8', $response->headers->get('content-type')); + } + + public function testPrepareDoesNothingIfRequestFormatIsNotDefined() + { + $response = new Response('foo'); + + $response->prepare(new Request()); + + $this->assertEquals('text/html; charset=UTF-8', $response->headers->get('content-type')); + } + + public function testPrepareSetContentType() + { + $response = new Response('foo'); + $request = Request::create('/'); + $request->setRequestFormat('json'); + + $response->prepare($request); + + $this->assertEquals('application/json', $response->headers->get('content-type')); + } + + public function testPrepareRemovesContentForHeadRequests() + { + $response = new Response('foo'); + $request = Request::create('/', 'HEAD'); + + $length = 12345; + $response->headers->set('Content-Length', $length); + $response->prepare($request); + + $this->assertEquals('', $response->getContent()); + $this->assertEquals($length, $response->headers->get('Content-Length'), 'Content-Length should be as if it was GET; see RFC2616 14.13'); + } + + public function testPrepareRemovesContentForInformationalResponse() + { + $response = new Response('foo'); + $request = Request::create('/'); + + $response->setContent('content'); + $response->setStatusCode(101); + $response->prepare($request); + $this->assertEquals('', $response->getContent()); + $this->assertFalse($response->headers->has('Content-Type')); + $this->assertFalse($response->headers->has('Content-Type')); + + $response->setContent('content'); + $response->setStatusCode(304); + $response->prepare($request); + $this->assertEquals('', $response->getContent()); + $this->assertFalse($response->headers->has('Content-Type')); + $this->assertFalse($response->headers->has('Content-Length')); + } + + public function testPrepareRemovesContentLength() + { + $response = new Response('foo'); + $request = Request::create('/'); + + $response->headers->set('Content-Length', 12345); + $response->prepare($request); + $this->assertEquals(12345, $response->headers->get('Content-Length')); + + $response->headers->set('Transfer-Encoding', 'chunked'); + $response->prepare($request); + $this->assertFalse($response->headers->has('Content-Length')); + } + + public function testPrepareSetsPragmaOnHttp10Only() + { + $request = Request::create('/', 'GET'); + $request->server->set('SERVER_PROTOCOL', 'HTTP/1.0'); + + $response = new Response('foo'); + $response->prepare($request); + $this->assertEquals('no-cache', $response->headers->get('pragma')); + $this->assertEquals('-1', $response->headers->get('expires')); + + $request->server->set('SERVER_PROTOCOL', 'HTTP/1.1'); + $response = new Response('foo'); + $response->prepare($request); + $this->assertFalse($response->headers->has('pragma')); + $this->assertFalse($response->headers->has('expires')); + } + + public function testSetCache() + { + $response = new Response(); + //array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public') + try { + $response->setCache(array('wrong option' => 'value')); + $this->fail('->setCache() throws an InvalidArgumentException if an option is not supported'); + } catch (\Exception $e) { + $this->assertInstanceOf('InvalidArgumentException', $e, '->setCache() throws an InvalidArgumentException if an option is not supported'); + $this->assertContains('"wrong option"', $e->getMessage()); + } + + $options = array('etag' => '"whatever"'); + $response->setCache($options); + $this->assertEquals($response->getEtag(), '"whatever"'); + + $now = $this->createDateTimeNow(); + $options = array('last_modified' => $now); + $response->setCache($options); + $this->assertEquals($response->getLastModified()->getTimestamp(), $now->getTimestamp()); + + $options = array('max_age' => 100); + $response->setCache($options); + $this->assertEquals($response->getMaxAge(), 100); + + $options = array('s_maxage' => 200); + $response->setCache($options); + $this->assertEquals($response->getMaxAge(), 200); + + $this->assertTrue($response->headers->hasCacheControlDirective('public')); + $this->assertFalse($response->headers->hasCacheControlDirective('private')); + + $response->setCache(array('public' => true)); + $this->assertTrue($response->headers->hasCacheControlDirective('public')); + $this->assertFalse($response->headers->hasCacheControlDirective('private')); + + $response->setCache(array('public' => false)); + $this->assertFalse($response->headers->hasCacheControlDirective('public')); + $this->assertTrue($response->headers->hasCacheControlDirective('private')); + + $response->setCache(array('private' => true)); + $this->assertFalse($response->headers->hasCacheControlDirective('public')); + $this->assertTrue($response->headers->hasCacheControlDirective('private')); + + $response->setCache(array('private' => false)); + $this->assertTrue($response->headers->hasCacheControlDirective('public')); + $this->assertFalse($response->headers->hasCacheControlDirective('private')); + } + + public function testSendContent() + { + $response = new Response('test response rendering', 200); + + ob_start(); + $response->sendContent(); + $string = ob_get_clean(); + $this->assertContains('test response rendering', $string); + } + + public function testSetPublic() + { + $response = new Response(); + $response->setPublic(); + + $this->assertTrue($response->headers->hasCacheControlDirective('public')); + $this->assertFalse($response->headers->hasCacheControlDirective('private')); + } + + public function testSetExpires() + { + $response = new Response(); + $response->setExpires(null); + + $this->assertNull($response->getExpires(), '->setExpires() remove the header when passed null'); + + $now = $this->createDateTimeNow(); + $response->setExpires($now); + + $this->assertEquals($response->getExpires()->getTimestamp(), $now->getTimestamp()); + } + + public function testSetLastModified() + { + $response = new Response(); + $response->setLastModified($this->createDateTimeNow()); + $this->assertNotNull($response->getLastModified()); + + $response->setLastModified(null); + $this->assertNull($response->getLastModified()); + } + + public function testIsInvalid() + { + $response = new Response(); + + try { + $response->setStatusCode(99); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertTrue($response->isInvalid()); + } + + try { + $response->setStatusCode(650); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertTrue($response->isInvalid()); + } + + $response = new Response('', 200); + $this->assertFalse($response->isInvalid()); + } + + /** + * @dataProvider getStatusCodeFixtures + */ + public function testSetStatusCode($code, $text, $expectedText) + { + $response = new Response(); + + $response->setStatusCode($code, $text); + + $statusText = new \ReflectionProperty($response, 'statusText'); + $statusText->setAccessible(true); + + $this->assertEquals($expectedText, $statusText->getValue($response)); + } + + public function getStatusCodeFixtures() + { + return array( + array('200', null, 'OK'), + array('200', false, ''), + array('200', 'foo', 'foo'), + array('199', null, 'unknown status'), + array('199', false, ''), + array('199', 'foo', 'foo'), + ); + } + + public function testIsInformational() + { + $response = new Response('', 100); + $this->assertTrue($response->isInformational()); + + $response = new Response('', 200); + $this->assertFalse($response->isInformational()); + } + + public function testIsRedirectRedirection() + { + foreach (array(301, 302, 303, 307) as $code) { + $response = new Response('', $code); + $this->assertTrue($response->isRedirection()); + $this->assertTrue($response->isRedirect()); + } + + $response = new Response('', 304); + $this->assertTrue($response->isRedirection()); + $this->assertFalse($response->isRedirect()); + + $response = new Response('', 200); + $this->assertFalse($response->isRedirection()); + $this->assertFalse($response->isRedirect()); + + $response = new Response('', 404); + $this->assertFalse($response->isRedirection()); + $this->assertFalse($response->isRedirect()); + + $response = new Response('', 301, array('Location' => '/good-uri')); + $this->assertFalse($response->isRedirect('/bad-uri')); + $this->assertTrue($response->isRedirect('/good-uri')); + } + + public function testIsNotFound() + { + $response = new Response('', 404); + $this->assertTrue($response->isNotFound()); + + $response = new Response('', 200); + $this->assertFalse($response->isNotFound()); + } + + public function testIsEmpty() + { + foreach (array(204, 304) as $code) { + $response = new Response('', $code); + $this->assertTrue($response->isEmpty()); + } + + $response = new Response('', 200); + $this->assertFalse($response->isEmpty()); + } + + public function testIsForbidden() + { + $response = new Response('', 403); + $this->assertTrue($response->isForbidden()); + + $response = new Response('', 200); + $this->assertFalse($response->isForbidden()); + } + + public function testIsOk() + { + $response = new Response('', 200); + $this->assertTrue($response->isOk()); + + $response = new Response('', 404); + $this->assertFalse($response->isOk()); + } + + public function testIsServerOrClientError() + { + $response = new Response('', 404); + $this->assertTrue($response->isClientError()); + $this->assertFalse($response->isServerError()); + + $response = new Response('', 500); + $this->assertFalse($response->isClientError()); + $this->assertTrue($response->isServerError()); + } + + public function testHasVary() + { + $response = new Response(); + $this->assertFalse($response->hasVary()); + + $response->setVary('User-Agent'); + $this->assertTrue($response->hasVary()); + } + + public function testSetEtag() + { + $response = new Response('', 200, array('ETag' => '"12345"')); + $response->setEtag(); + + $this->assertNull($response->headers->get('Etag'), '->setEtag() removes Etags when call with null'); + } + + /** + * @dataProvider validContentProvider + */ + public function testSetContent($content) + { + $response = new Response(); + $response->setContent($content); + $this->assertEquals((string) $content, $response->getContent()); + } + + /** + * @expectedException \UnexpectedValueException + * @dataProvider invalidContentProvider + */ + public function testSetContentInvalid($content) + { + $response = new Response(); + $response->setContent($content); + } + + public function testSettersAreChainable() + { + $response = new Response(); + + $setters = array( + 'setProtocolVersion' => '1.0', + 'setCharset' => 'UTF-8', + 'setPublic' => null, + 'setPrivate' => null, + 'setDate' => $this->createDateTimeNow(), + 'expire' => null, + 'setMaxAge' => 1, + 'setSharedMaxAge' => 1, + 'setTtl' => 1, + 'setClientTtl' => 1, + ); + + foreach ($setters as $setter => $arg) { + $this->assertEquals($response, $response->{$setter}($arg)); + } + } + + public function testNoDeprecationsAreTriggered() + { + new DefaultResponse(); + $this->getMockBuilder(Response::class)->getMock(); + + // we just need to ensure that subclasses of Response can be created without any deprecations + // being triggered if the subclass does not override any final methods + $this->addToAssertionCount(1); + } + + public function validContentProvider() + { + return array( + 'obj' => array(new StringableObject()), + 'string' => array('Foo'), + 'int' => array(2), + ); + } + + public function invalidContentProvider() + { + return array( + 'obj' => array(new \stdClass()), + 'array' => array(array()), + 'bool' => array(true, '1'), + ); + } + + protected function createDateTimeOneHourAgo() + { + return $this->createDateTimeNow()->sub(new \DateInterval('PT1H')); + } + + protected function createDateTimeOneHourLater() + { + return $this->createDateTimeNow()->add(new \DateInterval('PT1H')); + } + + protected function createDateTimeNow() + { + $date = new \DateTime(); + + return $date->setTimestamp(time()); + } + + protected function provideResponse() + { + return new Response(); + } + + /** + * @see http://github.com/zendframework/zend-diactoros for the canonical source repository + * + * @author Fábio Pacheco + * @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com) + * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License + */ + public function ianaCodesReasonPhrasesProvider() + { + if (!in_array('https', stream_get_wrappers(), true)) { + $this->markTestSkipped('The "https" wrapper is not available'); + } + + $ianaHttpStatusCodes = new \DOMDocument(); + + libxml_set_streams_context(stream_context_create(array( + 'http' => array( + 'method' => 'GET', + 'timeout' => 30, + ), + ))); + + $ianaHttpStatusCodes->load('https://www.iana.org/assignments/http-status-codes/http-status-codes.xml'); + if (!$ianaHttpStatusCodes->relaxNGValidate(__DIR__.'/schema/http-status-codes.rng')) { + self::fail('Invalid IANA\'s HTTP status code list.'); + } + + $ianaCodesReasonPhrases = array(); + + $xpath = new \DomXPath($ianaHttpStatusCodes); + $xpath->registerNamespace('ns', 'http://www.iana.org/assignments'); + + $records = $xpath->query('//ns:record'); + foreach ($records as $record) { + $value = $xpath->query('.//ns:value', $record)->item(0)->nodeValue; + $description = $xpath->query('.//ns:description', $record)->item(0)->nodeValue; + + if (in_array($description, array('Unassigned', '(Unused)'), true)) { + continue; + } + + if (preg_match('/^([0-9]+)\s*\-\s*([0-9]+)$/', $value, $matches)) { + for ($value = $matches[1]; $value <= $matches[2]; ++$value) { + $ianaCodesReasonPhrases[] = array($value, $description); + } + } else { + $ianaCodesReasonPhrases[] = array($value, $description); + } + } + + return $ianaCodesReasonPhrases; + } + + /** + * @dataProvider ianaCodesReasonPhrasesProvider + */ + public function testReasonPhraseDefaultsAgainstIana($code, $reasonPhrase) + { + $this->assertEquals($reasonPhrase, Response::$statusTexts[$code]); + } +} + +class StringableObject +{ + public function __toString() + { + return 'Foo'; + } +} + +class DefaultResponse extends Response +{ +} + +class ExtendedResponse extends Response +{ + public function setLastModified(\DateTime $date = null) + { + } + + public function getDate() + { + } +} diff --git a/vendor/symfony/http-foundation/Tests/ResponseTestCase.php b/vendor/symfony/http-foundation/Tests/ResponseTestCase.php new file mode 100644 index 00000000..4ead34c1 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ResponseTestCase.php @@ -0,0 +1,89 @@ + + * + * 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; + +abstract class ResponseTestCase extends TestCase +{ + public function testNoCacheControlHeaderOnAttachmentUsingHTTPSAndMSIE() + { + // Check for HTTPS and IE 8 + $request = new Request(); + $request->server->set('HTTPS', true); + $request->server->set('HTTP_USER_AGENT', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)'); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertFalse($response->headers->has('Cache-Control')); + + // Check for IE 10 and HTTPS + $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)'); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + + // Check for IE 9 and HTTPS + $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)'); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + + // Check for IE 9 and HTTP + $request->server->set('HTTPS', false); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + + // Check for IE 8 and HTTP + $request->server->set('HTTP_USER_AGENT', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)'); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + + // Check for non-IE and HTTPS + $request->server->set('HTTPS', true); + $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17'); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + + // Check for non-IE and HTTP + $request->server->set('HTTPS', false); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + } + + abstract protected function provideResponse(); +} diff --git a/vendor/symfony/http-foundation/Tests/ServerBagTest.php b/vendor/symfony/http-foundation/Tests/ServerBagTest.php new file mode 100644 index 00000000..c1d9d12a --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ServerBagTest.php @@ -0,0 +1,170 @@ + + * + * 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\ServerBag; + +/** + * ServerBagTest. + * + * @author Bulat Shakirzyanov + */ +class ServerBagTest extends TestCase +{ + public function testShouldExtractHeadersFromServerArray() + { + $server = array( + 'SOME_SERVER_VARIABLE' => 'value', + 'SOME_SERVER_VARIABLE2' => 'value', + 'ROOT' => 'value', + 'HTTP_CONTENT_TYPE' => 'text/html', + 'HTTP_CONTENT_LENGTH' => '0', + 'HTTP_ETAG' => 'asdf', + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => 'bar', + ); + + $bag = new ServerBag($server); + + $this->assertEquals(array( + 'CONTENT_TYPE' => 'text/html', + 'CONTENT_LENGTH' => '0', + 'ETAG' => 'asdf', + 'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'), + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => 'bar', + ), $bag->getHeaders()); + } + + public function testHttpPasswordIsOptional() + { + $bag = new ServerBag(array('PHP_AUTH_USER' => 'foo')); + + $this->assertEquals(array( + 'AUTHORIZATION' => 'Basic '.base64_encode('foo:'), + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => '', + ), $bag->getHeaders()); + } + + public function testHttpBasicAuthWithPhpCgi() + { + $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'))); + + $this->assertEquals(array( + 'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'), + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => 'bar', + ), $bag->getHeaders()); + } + + public function testHttpBasicAuthWithPhpCgiBogus() + { + $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic_'.base64_encode('foo:bar'))); + + // Username and passwords should not be set as the header is bogus + $headers = $bag->getHeaders(); + $this->assertFalse(isset($headers['PHP_AUTH_USER'])); + $this->assertFalse(isset($headers['PHP_AUTH_PW'])); + } + + public function testHttpBasicAuthWithPhpCgiRedirect() + { + $bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => 'Basic '.base64_encode('username:pass:word'))); + + $this->assertEquals(array( + 'AUTHORIZATION' => 'Basic '.base64_encode('username:pass:word'), + 'PHP_AUTH_USER' => 'username', + 'PHP_AUTH_PW' => 'pass:word', + ), $bag->getHeaders()); + } + + public function testHttpBasicAuthWithPhpCgiEmptyPassword() + { + $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:'))); + + $this->assertEquals(array( + 'AUTHORIZATION' => 'Basic '.base64_encode('foo:'), + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => '', + ), $bag->getHeaders()); + } + + public function testHttpDigestAuthWithPhpCgi() + { + $digest = 'Digest username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"'; + $bag = new ServerBag(array('HTTP_AUTHORIZATION' => $digest)); + + $this->assertEquals(array( + 'AUTHORIZATION' => $digest, + 'PHP_AUTH_DIGEST' => $digest, + ), $bag->getHeaders()); + } + + public function testHttpDigestAuthWithPhpCgiBogus() + { + $digest = 'Digest_username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"'; + $bag = new ServerBag(array('HTTP_AUTHORIZATION' => $digest)); + + // Username and passwords should not be set as the header is bogus + $headers = $bag->getHeaders(); + $this->assertFalse(isset($headers['PHP_AUTH_USER'])); + $this->assertFalse(isset($headers['PHP_AUTH_PW'])); + } + + public function testHttpDigestAuthWithPhpCgiRedirect() + { + $digest = 'Digest username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"'; + $bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => $digest)); + + $this->assertEquals(array( + 'AUTHORIZATION' => $digest, + 'PHP_AUTH_DIGEST' => $digest, + ), $bag->getHeaders()); + } + + public function testOAuthBearerAuth() + { + $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo'; + $bag = new ServerBag(array('HTTP_AUTHORIZATION' => $headerContent)); + + $this->assertEquals(array( + 'AUTHORIZATION' => $headerContent, + ), $bag->getHeaders()); + } + + public function testOAuthBearerAuthWithRedirect() + { + $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo'; + $bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => $headerContent)); + + $this->assertEquals(array( + 'AUTHORIZATION' => $headerContent, + ), $bag->getHeaders()); + } + + /** + * @see https://github.com/symfony/symfony/issues/17345 + */ + public function testItDoesNotOverwriteTheAuthorizationHeaderIfItIsAlreadySet() + { + $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo'; + $bag = new ServerBag(array('PHP_AUTH_USER' => 'foo', 'HTTP_AUTHORIZATION' => $headerContent)); + + $this->assertEquals(array( + 'AUTHORIZATION' => $headerContent, + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => '', + ), $bag->getHeaders()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php new file mode 100644 index 00000000..8c148b58 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php @@ -0,0 +1,189 @@ + + * + * 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\Attribute; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; + +/** + * Tests AttributeBag. + * + * @author Drak + */ +class AttributeBagTest extends TestCase +{ + /** + * @var array + */ + private $array; + + /** + * @var AttributeBag + */ + private $bag; + + protected function setUp() + { + $this->array = array( + 'hello' => 'world', + 'always' => 'be happy', + 'user.login' => 'drak', + 'csrf.token' => array( + 'a' => '1234', + 'b' => '4321', + ), + 'category' => array( + 'fishing' => array( + 'first' => 'cod', + 'second' => 'sole', + ), + ), + ); + $this->bag = new AttributeBag('_sf2'); + $this->bag->initialize($this->array); + } + + protected function tearDown() + { + $this->bag = null; + $this->array = array(); + } + + public function testInitialize() + { + $bag = new AttributeBag(); + $bag->initialize($this->array); + $this->assertEquals($this->array, $bag->all()); + $array = array('should' => 'change'); + $bag->initialize($array); + $this->assertEquals($array, $bag->all()); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2', $this->bag->getStorageKey()); + $attributeBag = new AttributeBag('test'); + $this->assertEquals('test', $attributeBag->getStorageKey()); + } + + public function testGetSetName() + { + $this->assertEquals('attributes', $this->bag->getName()); + $this->bag->setName('foo'); + $this->assertEquals('foo', $this->bag->getName()); + } + + /** + * @dataProvider attributesProvider + */ + public function testHas($key, $value, $exists) + { + $this->assertEquals($exists, $this->bag->has($key)); + } + + /** + * @dataProvider attributesProvider + */ + public function testGet($key, $value, $expected) + { + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testGetDefaults() + { + $this->assertNull($this->bag->get('user2.login')); + $this->assertEquals('default', $this->bag->get('user2.login', 'default')); + } + + /** + * @dataProvider attributesProvider + */ + public function testSet($key, $value, $expected) + { + $this->bag->set($key, $value); + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testAll() + { + $this->assertEquals($this->array, $this->bag->all()); + + $this->bag->set('hello', 'fabien'); + $array = $this->array; + $array['hello'] = 'fabien'; + $this->assertEquals($array, $this->bag->all()); + } + + public function testReplace() + { + $array = array(); + $array['name'] = 'jack'; + $array['foo.bar'] = 'beep'; + $this->bag->replace($array); + $this->assertEquals($array, $this->bag->all()); + $this->assertNull($this->bag->get('hello')); + $this->assertNull($this->bag->get('always')); + $this->assertNull($this->bag->get('user.login')); + } + + public function testRemove() + { + $this->assertEquals('world', $this->bag->get('hello')); + $this->bag->remove('hello'); + $this->assertNull($this->bag->get('hello')); + + $this->assertEquals('be happy', $this->bag->get('always')); + $this->bag->remove('always'); + $this->assertNull($this->bag->get('always')); + + $this->assertEquals('drak', $this->bag->get('user.login')); + $this->bag->remove('user.login'); + $this->assertNull($this->bag->get('user.login')); + } + + public function testClear() + { + $this->bag->clear(); + $this->assertEquals(array(), $this->bag->all()); + } + + public function attributesProvider() + { + return array( + array('hello', 'world', true), + array('always', 'be happy', true), + array('user.login', 'drak', true), + array('csrf.token', array('a' => '1234', 'b' => '4321'), true), + array('category', array('fishing' => array('first' => 'cod', 'second' => 'sole')), true), + array('user2.login', null, false), + array('never', null, false), + array('bye', null, false), + array('bye/for/now', null, false), + ); + } + + public function testGetIterator() + { + $i = 0; + foreach ($this->bag as $key => $val) { + $this->assertEquals($this->array[$key], $val); + ++$i; + } + + $this->assertEquals(count($this->array), $i); + } + + public function testCount() + { + $this->assertEquals(count($this->array), count($this->bag)); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php new file mode 100644 index 00000000..d9d9eb7f --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php @@ -0,0 +1,185 @@ + + * + * 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\Attribute; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag; + +/** + * Tests NamespacedAttributeBag. + * + * @author Drak + */ +class NamespacedAttributeBagTest extends TestCase +{ + /** + * @var array + */ + private $array; + + /** + * @var NamespacedAttributeBag + */ + private $bag; + + protected function setUp() + { + $this->array = array( + 'hello' => 'world', + 'always' => 'be happy', + 'user.login' => 'drak', + 'csrf.token' => array( + 'a' => '1234', + 'b' => '4321', + ), + 'category' => array( + 'fishing' => array( + 'first' => 'cod', + 'second' => 'sole', + ), + ), + ); + $this->bag = new NamespacedAttributeBag('_sf2', '/'); + $this->bag->initialize($this->array); + } + + protected function tearDown() + { + $this->bag = null; + $this->array = array(); + } + + public function testInitialize() + { + $bag = new NamespacedAttributeBag(); + $bag->initialize($this->array); + $this->assertEquals($this->array, $this->bag->all()); + $array = array('should' => 'not stick'); + $bag->initialize($array); + + // should have remained the same + $this->assertEquals($this->array, $this->bag->all()); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2', $this->bag->getStorageKey()); + $attributeBag = new NamespacedAttributeBag('test'); + $this->assertEquals('test', $attributeBag->getStorageKey()); + } + + /** + * @dataProvider attributesProvider + */ + public function testHas($key, $value, $exists) + { + $this->assertEquals($exists, $this->bag->has($key)); + } + + /** + * @dataProvider attributesProvider + */ + public function testGet($key, $value, $expected) + { + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testGetDefaults() + { + $this->assertNull($this->bag->get('user2.login')); + $this->assertEquals('default', $this->bag->get('user2.login', 'default')); + } + + /** + * @dataProvider attributesProvider + */ + public function testSet($key, $value, $expected) + { + $this->bag->set($key, $value); + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testAll() + { + $this->assertEquals($this->array, $this->bag->all()); + + $this->bag->set('hello', 'fabien'); + $array = $this->array; + $array['hello'] = 'fabien'; + $this->assertEquals($array, $this->bag->all()); + } + + public function testReplace() + { + $array = array(); + $array['name'] = 'jack'; + $array['foo.bar'] = 'beep'; + $this->bag->replace($array); + $this->assertEquals($array, $this->bag->all()); + $this->assertNull($this->bag->get('hello')); + $this->assertNull($this->bag->get('always')); + $this->assertNull($this->bag->get('user.login')); + } + + public function testRemove() + { + $this->assertEquals('world', $this->bag->get('hello')); + $this->bag->remove('hello'); + $this->assertNull($this->bag->get('hello')); + + $this->assertEquals('be happy', $this->bag->get('always')); + $this->bag->remove('always'); + $this->assertNull($this->bag->get('always')); + + $this->assertEquals('drak', $this->bag->get('user.login')); + $this->bag->remove('user.login'); + $this->assertNull($this->bag->get('user.login')); + } + + public function testRemoveExistingNamespacedAttribute() + { + $this->assertSame('cod', $this->bag->remove('category/fishing/first')); + } + + public function testRemoveNonexistingNamespacedAttribute() + { + $this->assertNull($this->bag->remove('foo/bar/baz')); + } + + public function testClear() + { + $this->bag->clear(); + $this->assertEquals(array(), $this->bag->all()); + } + + public function attributesProvider() + { + return array( + array('hello', 'world', true), + array('always', 'be happy', true), + array('user.login', 'drak', true), + array('csrf.token', array('a' => '1234', 'b' => '4321'), true), + array('csrf.token/a', '1234', true), + array('csrf.token/b', '4321', true), + array('category', array('fishing' => array('first' => 'cod', 'second' => 'sole')), true), + array('category/fishing', array('first' => 'cod', 'second' => 'sole'), true), + array('category/fishing/missing/first', null, false), + array('category/fishing/first', 'cod', true), + array('category/fishing/second', 'sole', true), + array('category/fishing/missing/second', null, false), + array('user2.login', null, false), + array('never', null, false), + array('bye', null, false), + array('bye/for/now', null, false), + ); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php new file mode 100644 index 00000000..4eb200af --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php @@ -0,0 +1,156 @@ + + * + * 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\Flash; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag as FlashBag; + +/** + * AutoExpireFlashBagTest. + * + * @author Drak + */ +class AutoExpireFlashBagTest extends TestCase +{ + /** + * @var \Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag + */ + private $bag; + + /** + * @var array + */ + protected $array = array(); + + protected function setUp() + { + parent::setUp(); + $this->bag = new FlashBag(); + $this->array = array('new' => array('notice' => array('A previous flash message'))); + $this->bag->initialize($this->array); + } + + protected function tearDown() + { + $this->bag = null; + parent::tearDown(); + } + + public function testInitialize() + { + $bag = new FlashBag(); + $array = array('new' => array('notice' => array('A previous flash message'))); + $bag->initialize($array); + $this->assertEquals(array('A previous flash message'), $bag->peek('notice')); + $array = array('new' => array( + 'notice' => array('Something else'), + 'error' => array('a'), + )); + $bag->initialize($array); + $this->assertEquals(array('Something else'), $bag->peek('notice')); + $this->assertEquals(array('a'), $bag->peek('error')); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2_flashes', $this->bag->getStorageKey()); + $attributeBag = new FlashBag('test'); + $this->assertEquals('test', $attributeBag->getStorageKey()); + } + + public function testGetSetName() + { + $this->assertEquals('flashes', $this->bag->getName()); + $this->bag->setName('foo'); + $this->assertEquals('foo', $this->bag->getName()); + } + + public function testPeek() + { + $this->assertEquals(array(), $this->bag->peek('non_existing')); + $this->assertEquals(array('default'), $this->bag->peek('non_existing', array('default'))); + $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice')); + $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice')); + } + + public function testSet() + { + $this->bag->set('notice', 'Foo'); + $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice')); + } + + public function testHas() + { + $this->assertFalse($this->bag->has('nothing')); + $this->assertTrue($this->bag->has('notice')); + } + + public function testKeys() + { + $this->assertEquals(array('notice'), $this->bag->keys()); + } + + public function testPeekAll() + { + $array = array( + 'new' => array( + 'notice' => 'Foo', + 'error' => 'Bar', + ), + ); + + $this->bag->initialize($array); + $this->assertEquals(array( + 'notice' => 'Foo', + 'error' => 'Bar', + ), $this->bag->peekAll() + ); + + $this->assertEquals(array( + 'notice' => 'Foo', + 'error' => 'Bar', + ), $this->bag->peekAll() + ); + } + + public function testGet() + { + $this->assertEquals(array(), $this->bag->get('non_existing')); + $this->assertEquals(array('default'), $this->bag->get('non_existing', array('default'))); + $this->assertEquals(array('A previous flash message'), $this->bag->get('notice')); + $this->assertEquals(array(), $this->bag->get('notice')); + } + + public function testSetAll() + { + $this->bag->setAll(array('a' => 'first', 'b' => 'second')); + $this->assertFalse($this->bag->has('a')); + $this->assertFalse($this->bag->has('b')); + } + + public function testAll() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('error', 'Bar'); + $this->assertEquals(array( + 'notice' => array('A previous flash message'), + ), $this->bag->all() + ); + + $this->assertEquals(array(), $this->bag->all()); + } + + public function testClear() + { + $this->assertEquals(array('notice' => array('A previous flash message')), $this->bag->clear()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php new file mode 100644 index 00000000..f0aa6a61 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php @@ -0,0 +1,135 @@ + + * + * 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\Flash; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; + +/** + * FlashBagTest. + * + * @author Drak + */ +class FlashBagTest extends TestCase +{ + /** + * @var \Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface + */ + private $bag; + + /** + * @var array + */ + protected $array = array(); + + protected function setUp() + { + parent::setUp(); + $this->bag = new FlashBag(); + $this->array = array('notice' => array('A previous flash message')); + $this->bag->initialize($this->array); + } + + protected function tearDown() + { + $this->bag = null; + parent::tearDown(); + } + + public function testInitialize() + { + $bag = new FlashBag(); + $bag->initialize($this->array); + $this->assertEquals($this->array, $bag->peekAll()); + $array = array('should' => array('change')); + $bag->initialize($array); + $this->assertEquals($array, $bag->peekAll()); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2_flashes', $this->bag->getStorageKey()); + $attributeBag = new FlashBag('test'); + $this->assertEquals('test', $attributeBag->getStorageKey()); + } + + public function testGetSetName() + { + $this->assertEquals('flashes', $this->bag->getName()); + $this->bag->setName('foo'); + $this->assertEquals('foo', $this->bag->getName()); + } + + public function testPeek() + { + $this->assertEquals(array(), $this->bag->peek('non_existing')); + $this->assertEquals(array('default'), $this->bag->peek('not_existing', array('default'))); + $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice')); + $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice')); + } + + public function testGet() + { + $this->assertEquals(array(), $this->bag->get('non_existing')); + $this->assertEquals(array('default'), $this->bag->get('not_existing', array('default'))); + $this->assertEquals(array('A previous flash message'), $this->bag->get('notice')); + $this->assertEquals(array(), $this->bag->get('notice')); + } + + public function testAll() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('error', 'Bar'); + $this->assertEquals(array( + 'notice' => array('Foo'), + 'error' => array('Bar'), ), $this->bag->all() + ); + + $this->assertEquals(array(), $this->bag->all()); + } + + public function testSet() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('notice', 'Bar'); + $this->assertEquals(array('Bar'), $this->bag->peek('notice')); + } + + public function testHas() + { + $this->assertFalse($this->bag->has('nothing')); + $this->assertTrue($this->bag->has('notice')); + } + + public function testKeys() + { + $this->assertEquals(array('notice'), $this->bag->keys()); + } + + public function testPeekAll() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('error', 'Bar'); + $this->assertEquals(array( + 'notice' => array('Foo'), + 'error' => array('Bar'), + ), $this->bag->peekAll() + ); + $this->assertTrue($this->bag->has('notice')); + $this->assertTrue($this->bag->has('error')); + $this->assertEquals(array( + 'notice' => array('Foo'), + 'error' => array('Bar'), + ), $this->bag->peekAll() + ); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/SessionTest.php b/vendor/symfony/http-foundation/Tests/Session/SessionTest.php new file mode 100644 index 00000000..fa93507a --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/SessionTest.php @@ -0,0 +1,224 @@ + + * + * 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; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; + +/** + * SessionTest. + * + * @author Fabien Potencier + * @author Robert Schönthal + * @author Drak + */ +class SessionTest extends TestCase +{ + /** + * @var \Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface + */ + protected $storage; + + /** + * @var \Symfony\Component\HttpFoundation\Session\SessionInterface + */ + protected $session; + + protected function setUp() + { + $this->storage = new MockArraySessionStorage(); + $this->session = new Session($this->storage, new AttributeBag(), new FlashBag()); + } + + protected function tearDown() + { + $this->storage = null; + $this->session = null; + } + + public function testStart() + { + $this->assertEquals('', $this->session->getId()); + $this->assertTrue($this->session->start()); + $this->assertNotEquals('', $this->session->getId()); + } + + public function testIsStarted() + { + $this->assertFalse($this->session->isStarted()); + $this->session->start(); + $this->assertTrue($this->session->isStarted()); + } + + public function testSetId() + { + $this->assertEquals('', $this->session->getId()); + $this->session->setId('0123456789abcdef'); + $this->session->start(); + $this->assertEquals('0123456789abcdef', $this->session->getId()); + } + + public function testSetName() + { + $this->assertEquals('MOCKSESSID', $this->session->getName()); + $this->session->setName('session.test.com'); + $this->session->start(); + $this->assertEquals('session.test.com', $this->session->getName()); + } + + public function testGet() + { + // tests defaults + $this->assertNull($this->session->get('foo')); + $this->assertEquals(1, $this->session->get('foo', 1)); + } + + /** + * @dataProvider setProvider + */ + public function testSet($key, $value) + { + $this->session->set($key, $value); + $this->assertEquals($value, $this->session->get($key)); + } + + /** + * @dataProvider setProvider + */ + public function testHas($key, $value) + { + $this->session->set($key, $value); + $this->assertTrue($this->session->has($key)); + $this->assertFalse($this->session->has($key.'non_value')); + } + + public function testReplace() + { + $this->session->replace(array('happiness' => 'be good', 'symfony' => 'awesome')); + $this->assertEquals(array('happiness' => 'be good', 'symfony' => 'awesome'), $this->session->all()); + $this->session->replace(array()); + $this->assertEquals(array(), $this->session->all()); + } + + /** + * @dataProvider setProvider + */ + public function testAll($key, $value, $result) + { + $this->session->set($key, $value); + $this->assertEquals($result, $this->session->all()); + } + + /** + * @dataProvider setProvider + */ + public function testClear($key, $value) + { + $this->session->set('hi', 'fabien'); + $this->session->set($key, $value); + $this->session->clear(); + $this->assertEquals(array(), $this->session->all()); + } + + public function setProvider() + { + return array( + array('foo', 'bar', array('foo' => 'bar')), + array('foo.bar', 'too much beer', array('foo.bar' => 'too much beer')), + array('great', 'symfony is great', array('great' => 'symfony is great')), + ); + } + + /** + * @dataProvider setProvider + */ + public function testRemove($key, $value) + { + $this->session->set('hi.world', 'have a nice day'); + $this->session->set($key, $value); + $this->session->remove($key); + $this->assertEquals(array('hi.world' => 'have a nice day'), $this->session->all()); + } + + public function testInvalidate() + { + $this->session->set('invalidate', 123); + $this->session->invalidate(); + $this->assertEquals(array(), $this->session->all()); + } + + public function testMigrate() + { + $this->session->set('migrate', 321); + $this->session->migrate(); + $this->assertEquals(321, $this->session->get('migrate')); + } + + public function testMigrateDestroy() + { + $this->session->set('migrate', 333); + $this->session->migrate(true); + $this->assertEquals(333, $this->session->get('migrate')); + } + + public function testSave() + { + $this->session->start(); + $this->session->save(); + + $this->assertFalse($this->session->isStarted()); + } + + public function testGetId() + { + $this->assertEquals('', $this->session->getId()); + $this->session->start(); + $this->assertNotEquals('', $this->session->getId()); + } + + public function testGetFlashBag() + { + $this->assertInstanceOf('Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface', $this->session->getFlashBag()); + } + + public function testGetIterator() + { + $attributes = array('hello' => 'world', 'symfony' => 'rocks'); + foreach ($attributes as $key => $val) { + $this->session->set($key, $val); + } + + $i = 0; + foreach ($this->session as $key => $val) { + $this->assertEquals($attributes[$key], $val); + ++$i; + } + + $this->assertEquals(count($attributes), $i); + } + + public function testGetCount() + { + $this->session->set('hello', 'world'); + $this->session->set('symfony', 'rocks'); + + $this->assertCount(2, $this->session); + } + + public function testGetMeta() + { + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\MetadataBag', $this->session->getMetadataBag()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php new file mode 100644 index 00000000..06193c8b --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php @@ -0,0 +1,133 @@ + + * + * 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\MemcacheSessionHandler; + +/** + * @requires extension memcache + * @group time-sensitive + */ +class MemcacheSessionHandlerTest extends TestCase +{ + const PREFIX = 'prefix_'; + const TTL = 1000; + /** + * @var MemcacheSessionHandler + */ + protected $storage; + + protected $memcache; + + protected function setUp() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('PHPUnit_MockObject cannot mock the Memcache class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289'); + } + + parent::setUp(); + $this->memcache = $this->getMockBuilder('Memcache')->getMock(); + $this->storage = new MemcacheSessionHandler( + $this->memcache, + array('prefix' => self::PREFIX, 'expiretime' => self::TTL) + ); + } + + protected function tearDown() + { + $this->memcache = 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->memcache + ->expects($this->once()) + ->method('get') + ->with(self::PREFIX.'id') + ; + + $this->assertEquals('', $this->storage->read('id')); + } + + public function testWriteSession() + { + $this->memcache + ->expects($this->once()) + ->method('set') + ->with(self::PREFIX.'id', 'data', 0, $this->equalTo(time() + self::TTL, 2)) + ->will($this->returnValue(true)) + ; + + $this->assertTrue($this->storage->write('id', 'data')); + } + + public function testDestroySession() + { + $this->memcache + ->expects($this->once()) + ->method('delete') + ->with(self::PREFIX.'id') + ->will($this->returnValue(true)) + ; + + $this->assertTrue($this->storage->destroy('id')); + } + + public function testGcSession() + { + $this->assertTrue($this->storage->gc(123)); + } + + /** + * @dataProvider getOptionFixtures + */ + public function testSupportedOptions($options, $supported) + { + try { + new MemcacheSessionHandler($this->memcache, $options); + $this->assertTrue($supported); + } catch (\InvalidArgumentException $e) { + $this->assertFalse($supported); + } + } + + public function getOptionFixtures() + { + return array( + array(array('prefix' => 'session'), true), + array(array('expiretime' => 100), true), + array(array('prefix' => 'session', 'expiretime' => 200), true), + array(array('expiretime' => 100, 'foo' => 'bar'), false), + ); + } + + public function testGetConnection() + { + $method = new \ReflectionMethod($this->storage, 'getMemcache'); + $method->setAccessible(true); + + $this->assertInstanceOf('\Memcache', $method->invoke($this->storage)); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php new file mode 100644 index 00000000..2e7be359 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php @@ -0,0 +1,139 @@ + + * + * 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\MemcachedSessionHandler; + +/** + * @requires extension memcached + * @group time-sensitive + */ +class MemcachedSessionHandlerTest extends TestCase +{ + const PREFIX = 'prefix_'; + const TTL = 1000; + + /** + * @var MemcachedSessionHandler + */ + protected $storage; + + protected $memcached; + + protected function setUp() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('PHPUnit_MockObject cannot mock the Memcached class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289'); + } + + parent::setUp(); + + if (version_compare(phpversion('memcached'), '2.2.0', '>=') && version_compare(phpversion('memcached'), '3.0.0b1', '<')) { + $this->markTestSkipped('Tests can only be run with memcached extension 2.1.0 or lower, or 3.0.0b1 or higher'); + } + + $this->memcached = $this->getMockBuilder('Memcached')->getMock(); + $this->storage = new MemcachedSessionHandler( + $this->memcached, + array('prefix' => self::PREFIX, 'expiretime' => self::TTL) + ); + } + + protected function tearDown() + { + $this->memcached = 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->memcached + ->expects($this->once()) + ->method('get') + ->with(self::PREFIX.'id') + ; + + $this->assertEquals('', $this->storage->read('id')); + } + + public function testWriteSession() + { + $this->memcached + ->expects($this->once()) + ->method('set') + ->with(self::PREFIX.'id', 'data', $this->equalTo(time() + self::TTL, 2)) + ->will($this->returnValue(true)) + ; + + $this->assertTrue($this->storage->write('id', 'data')); + } + + public function testDestroySession() + { + $this->memcached + ->expects($this->once()) + ->method('delete') + ->with(self::PREFIX.'id') + ->will($this->returnValue(true)) + ; + + $this->assertTrue($this->storage->destroy('id')); + } + + public function testGcSession() + { + $this->assertTrue($this->storage->gc(123)); + } + + /** + * @dataProvider getOptionFixtures + */ + public function testSupportedOptions($options, $supported) + { + try { + new MemcachedSessionHandler($this->memcached, $options); + $this->assertTrue($supported); + } catch (\InvalidArgumentException $e) { + $this->assertFalse($supported); + } + } + + public function getOptionFixtures() + { + return array( + array(array('prefix' => 'session'), true), + array(array('expiretime' => 100), true), + array(array('prefix' => 'session', 'expiretime' => 200), true), + array(array('expiretime' => 100, 'foo' => 'bar'), false), + ); + } + + public function testGetConnection() + { + $method = new \ReflectionMethod($this->storage, 'getMemcached'); + $method->setAccessible(true); + + $this->assertInstanceOf('\Memcached', $method->invoke($this->storage)); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php new file mode 100644 index 00000000..74366863 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php @@ -0,0 +1,332 @@ + + * + * 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\MongoDbSessionHandler; + +/** + * @author Markus Bachmann + * @group time-sensitive + */ +class MongoDbSessionHandlerTest extends TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $mongo; + private $storage; + public $options; + + protected function setUp() + { + parent::setUp(); + + if (extension_loaded('mongodb')) { + if (!class_exists('MongoDB\Client')) { + $this->markTestSkipped('The mongodb/mongodb package is required.'); + } + } elseif (!extension_loaded('mongo')) { + $this->markTestSkipped('The Mongo or MongoDB extension is required.'); + } + + if (phpversion('mongodb')) { + $mongoClass = 'MongoDB\Client'; + } else { + $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient'; + } + + $this->mongo = $this->getMockBuilder($mongoClass) + ->disableOriginalConstructor() + ->getMock(); + + $this->options = array( + 'id_field' => '_id', + 'data_field' => 'data', + 'time_field' => 'time', + 'expiry_field' => 'expires_at', + 'database' => 'sf2-test', + 'collection' => 'session-test', + ); + + $this->storage = new MongoDbSessionHandler($this->mongo, $this->options); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForInvalidMongo() + { + new MongoDbSessionHandler(new \stdClass(), $this->options); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForMissingOptions() + { + new MongoDbSessionHandler($this->mongo, array()); + } + + public function testOpenMethodAlwaysReturnTrue() + { + $this->assertTrue($this->storage->open('test', 'test'), 'The "open" method should always return true'); + } + + public function testCloseMethodAlwaysReturnTrue() + { + $this->assertTrue($this->storage->close(), 'The "close" method should always return true'); + } + + public function testRead() + { + $collection = $this->createMongoCollectionMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + // defining the timeout before the actual method call + // allows to test for "greater than" values in the $criteria + $testTimeout = time() + 1; + + $collection->expects($this->once()) + ->method('findOne') + ->will($this->returnCallback(function ($criteria) use ($testTimeout) { + $this->assertArrayHasKey($this->options['id_field'], $criteria); + $this->assertEquals($criteria[$this->options['id_field']], 'foo'); + + $this->assertArrayHasKey($this->options['expiry_field'], $criteria); + $this->assertArrayHasKey('$gte', $criteria[$this->options['expiry_field']]); + + if (phpversion('mongodb')) { + $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$gte']); + $this->assertGreaterThanOrEqual(round((string) $criteria[$this->options['expiry_field']]['$gte'] / 1000), $testTimeout); + } else { + $this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$gte']); + $this->assertGreaterThanOrEqual($criteria[$this->options['expiry_field']]['$gte']->sec, $testTimeout); + } + + $fields = array( + $this->options['id_field'] => 'foo', + ); + + if (phpversion('mongodb')) { + $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary('bar', \MongoDB\BSON\Binary::TYPE_OLD_BINARY); + $fields[$this->options['id_field']] = new \MongoDB\BSON\UTCDateTime(time() * 1000); + } else { + $fields[$this->options['data_field']] = new \MongoBinData('bar', \MongoBinData::BYTE_ARRAY); + $fields[$this->options['id_field']] = new \MongoDate(); + } + + return $fields; + })); + + $this->assertEquals('bar', $this->storage->read('foo')); + } + + public function testWrite() + { + $collection = $this->createMongoCollectionMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $data = array(); + + $methodName = phpversion('mongodb') ? 'updateOne' : 'update'; + + $collection->expects($this->once()) + ->method($methodName) + ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) { + $this->assertEquals(array($this->options['id_field'] => 'foo'), $criteria); + + if (phpversion('mongodb')) { + $this->assertEquals(array('upsert' => true), $options); + } else { + $this->assertEquals(array('upsert' => true, 'multiple' => false), $options); + } + + $data = $updateData['$set']; + })); + + $expectedExpiry = time() + (int) ini_get('session.gc_maxlifetime'); + $this->assertTrue($this->storage->write('foo', 'bar')); + + if (phpversion('mongodb')) { + $this->assertEquals('bar', $data[$this->options['data_field']]->getData()); + $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]); + $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]); + $this->assertGreaterThanOrEqual($expectedExpiry, round((string) $data[$this->options['expiry_field']] / 1000)); + } else { + $this->assertEquals('bar', $data[$this->options['data_field']]->bin); + $this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]); + $this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]); + $this->assertGreaterThanOrEqual($expectedExpiry, $data[$this->options['expiry_field']]->sec); + } + } + + public function testWriteWhenUsingExpiresField() + { + $this->options = array( + 'id_field' => '_id', + 'data_field' => 'data', + 'time_field' => 'time', + 'database' => 'sf2-test', + 'collection' => 'session-test', + 'expiry_field' => 'expiresAt', + ); + + $this->storage = new MongoDbSessionHandler($this->mongo, $this->options); + + $collection = $this->createMongoCollectionMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $data = array(); + + $methodName = phpversion('mongodb') ? 'updateOne' : 'update'; + + $collection->expects($this->once()) + ->method($methodName) + ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) { + $this->assertEquals(array($this->options['id_field'] => 'foo'), $criteria); + + if (phpversion('mongodb')) { + $this->assertEquals(array('upsert' => true), $options); + } else { + $this->assertEquals(array('upsert' => true, 'multiple' => false), $options); + } + + $data = $updateData['$set']; + })); + + $this->assertTrue($this->storage->write('foo', 'bar')); + + if (phpversion('mongodb')) { + $this->assertEquals('bar', $data[$this->options['data_field']]->getData()); + $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]); + $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]); + } else { + $this->assertEquals('bar', $data[$this->options['data_field']]->bin); + $this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]); + $this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]); + } + } + + public function testReplaceSessionData() + { + $collection = $this->createMongoCollectionMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $data = array(); + + $methodName = phpversion('mongodb') ? 'updateOne' : 'update'; + + $collection->expects($this->exactly(2)) + ->method($methodName) + ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) { + $data = $updateData; + })); + + $this->storage->write('foo', 'bar'); + $this->storage->write('foo', 'foobar'); + + if (phpversion('mongodb')) { + $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->getData()); + } else { + $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->bin); + } + } + + public function testDestroy() + { + $collection = $this->createMongoCollectionMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $methodName = phpversion('mongodb') ? 'deleteOne' : 'remove'; + + $collection->expects($this->once()) + ->method($methodName) + ->with(array($this->options['id_field'] => 'foo')); + + $this->assertTrue($this->storage->destroy('foo')); + } + + public function testGc() + { + $collection = $this->createMongoCollectionMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $methodName = phpversion('mongodb') ? 'deleteOne' : 'remove'; + + $collection->expects($this->once()) + ->method($methodName) + ->will($this->returnCallback(function ($criteria) { + if (phpversion('mongodb')) { + $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$lt']); + $this->assertGreaterThanOrEqual(time() - 1, round((string) $criteria[$this->options['expiry_field']]['$lt'] / 1000)); + } else { + $this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$lt']); + $this->assertGreaterThanOrEqual(time() - 1, $criteria[$this->options['expiry_field']]['$lt']->sec); + } + })); + + $this->assertTrue($this->storage->gc(1)); + } + + public function testGetConnection() + { + $method = new \ReflectionMethod($this->storage, 'getMongo'); + $method->setAccessible(true); + + if (phpversion('mongodb')) { + $mongoClass = 'MongoDB\Client'; + } else { + $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient'; + } + + $this->assertInstanceOf($mongoClass, $method->invoke($this->storage)); + } + + private function createMongoCollectionMock() + { + $collectionClass = 'MongoCollection'; + if (phpversion('mongodb')) { + $collectionClass = 'MongoDB\Collection'; + } + + $collection = $this->getMockBuilder($collectionClass) + ->disableOriginalConstructor() + ->getMock(); + + return $collection; + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php new file mode 100644 index 00000000..a6264e51 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php @@ -0,0 +1,77 @@ + + * + * 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\NativeFileSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; + +/** + * Test class for NativeFileSessionHandler. + * + * @author Drak + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class NativeFileSessionHandlerTest extends TestCase +{ + public function testConstruct() + { + $storage = new NativeSessionStorage(array('name' => 'TESTING'), new NativeFileSessionHandler(sys_get_temp_dir())); + + $this->assertEquals('files', $storage->getSaveHandler()->getSaveHandlerName()); + $this->assertEquals('user', ini_get('session.save_handler')); + + $this->assertEquals(sys_get_temp_dir(), ini_get('session.save_path')); + $this->assertEquals('TESTING', ini_get('session.name')); + } + + /** + * @dataProvider savePathDataProvider + */ + public function testConstructSavePath($savePath, $expectedSavePath, $path) + { + $handler = new NativeFileSessionHandler($savePath); + $this->assertEquals($expectedSavePath, ini_get('session.save_path')); + $this->assertTrue(is_dir(realpath($path))); + + rmdir($path); + } + + public function savePathDataProvider() + { + $base = sys_get_temp_dir(); + + return array( + array("$base/foo", "$base/foo", "$base/foo"), + array("5;$base/foo", "5;$base/foo", "$base/foo"), + array("5;0600;$base/foo", "5;0600;$base/foo", "$base/foo"), + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testConstructException() + { + $handler = new NativeFileSessionHandler('something;invalid;with;too-many-args'); + } + + public function testConstructDefault() + { + $path = ini_get('session.save_path'); + $storage = new NativeSessionStorage(array('name' => 'TESTING'), new NativeFileSessionHandler()); + + $this->assertEquals($path, ini_get('session.save_path')); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php new file mode 100644 index 00000000..5486b2d6 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php @@ -0,0 +1,34 @@ + + * + * 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\NativeSessionHandler; + +/** + * Test class for NativeSessionHandler. + * + * @author Drak + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class NativeSessionHandlerTest extends TestCase +{ + public function testConstruct() + { + $handler = new NativeSessionHandler(); + + $this->assertTrue($handler instanceof \SessionHandler); + $this->assertTrue($handler instanceof NativeSessionHandler); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php new file mode 100644 index 00000000..718fd0f8 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php @@ -0,0 +1,59 @@ + + * + * 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\NullSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Session; + +/** + * Test class for NullSessionHandler. + * + * @author Drak + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class NullSessionHandlerTest extends TestCase +{ + public function testSaveHandlers() + { + $storage = $this->getStorage(); + $this->assertEquals('user', ini_get('session.save_handler')); + } + + public function testSession() + { + session_id('nullsessionstorage'); + $storage = $this->getStorage(); + $session = new Session($storage); + $this->assertNull($session->get('something')); + $session->set('something', 'unique'); + $this->assertEquals('unique', $session->get('something')); + } + + public function testNothingIsPersisted() + { + session_id('nullsessionstorage'); + $storage = $this->getStorage(); + $session = new Session($storage); + $session->start(); + $this->assertEquals('nullsessionstorage', $session->getId()); + $this->assertNull($session->get('something')); + } + + public function getStorage() + { + return new NativeSessionStorage(array(), new NullSessionHandler()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php new file mode 100644 index 00000000..a47120f1 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -0,0 +1,370 @@ + + * + * 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\PdoSessionHandler; + +/** + * @requires extension pdo_sqlite + * @group time-sensitive + */ +class PdoSessionHandlerTest extends TestCase +{ + private $dbFile; + + protected function tearDown() + { + // make sure the temporary database file is deleted when it has been created (even when a test fails) + if ($this->dbFile) { + @unlink($this->dbFile); + } + parent::tearDown(); + } + + protected function getPersistentSqliteDsn() + { + $this->dbFile = tempnam(sys_get_temp_dir(), 'sf2_sqlite_sessions'); + + return 'sqlite:'.$this->dbFile; + } + + protected function getMemorySqlitePdo() + { + $pdo = new \PDO('sqlite::memory:'); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $storage = new PdoSessionHandler($pdo); + $storage->createTable(); + + return $pdo; + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testWrongPdoErrMode() + { + $pdo = $this->getMemorySqlitePdo(); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_SILENT); + + $storage = new PdoSessionHandler($pdo); + } + + /** + * @expectedException \RuntimeException + */ + public function testInexistentTable() + { + $storage = new PdoSessionHandler($this->getMemorySqlitePdo(), array('db_table' => 'inexistent_table')); + $storage->open('', 'sid'); + $storage->read('id'); + $storage->write('id', 'data'); + $storage->close(); + } + + /** + * @expectedException \RuntimeException + */ + public function testCreateTableTwice() + { + $storage = new PdoSessionHandler($this->getMemorySqlitePdo()); + $storage->createTable(); + } + + public function testWithLazyDsnConnection() + { + $dsn = $this->getPersistentSqliteDsn(); + + $storage = new PdoSessionHandler($dsn); + $storage->createTable(); + $storage->open('', 'sid'); + $data = $storage->read('id'); + $storage->write('id', 'data'); + $storage->close(); + $this->assertSame('', $data, 'New session returns empty string data'); + + $storage->open('', 'sid'); + $data = $storage->read('id'); + $storage->close(); + $this->assertSame('data', $data, 'Written value can be read back correctly'); + } + + public function testWithLazySavePathConnection() + { + $dsn = $this->getPersistentSqliteDsn(); + + // Open is called with what ini_set('session.save_path', $dsn) would mean + $storage = new PdoSessionHandler(null); + $storage->open($dsn, 'sid'); + $storage->createTable(); + $data = $storage->read('id'); + $storage->write('id', 'data'); + $storage->close(); + $this->assertSame('', $data, 'New session returns empty string data'); + + $storage->open($dsn, 'sid'); + $data = $storage->read('id'); + $storage->close(); + $this->assertSame('data', $data, 'Written value can be read back correctly'); + } + + public function testReadWriteReadWithNullByte() + { + $sessionData = 'da'."\0".'ta'; + + $storage = new PdoSessionHandler($this->getMemorySqlitePdo()); + $storage->open('', 'sid'); + $readData = $storage->read('id'); + $storage->write('id', $sessionData); + $storage->close(); + $this->assertSame('', $readData, 'New session returns empty string data'); + + $storage->open('', 'sid'); + $readData = $storage->read('id'); + $storage->close(); + $this->assertSame($sessionData, $readData, 'Written value can be read back correctly'); + } + + public function testReadConvertsStreamToString() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289'); + } + + $pdo = new MockPdo('pgsql'); + $pdo->prepareResult = $this->getMockBuilder('PDOStatement')->getMock(); + + $content = 'foobar'; + $stream = $this->createStream($content); + + $pdo->prepareResult->expects($this->once())->method('fetchAll') + ->will($this->returnValue(array(array($stream, 42, time())))); + + $storage = new PdoSessionHandler($pdo); + $result = $storage->read('foo'); + + $this->assertSame($content, $result); + } + + public function testReadLockedConvertsStreamToString() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289'); + } + + $pdo = new MockPdo('pgsql'); + $selectStmt = $this->getMockBuilder('PDOStatement')->getMock(); + $insertStmt = $this->getMockBuilder('PDOStatement')->getMock(); + + $pdo->prepareResult = function ($statement) use ($selectStmt, $insertStmt) { + return 0 === strpos($statement, 'INSERT') ? $insertStmt : $selectStmt; + }; + + $content = 'foobar'; + $stream = $this->createStream($content); + $exception = null; + + $selectStmt->expects($this->atLeast(2))->method('fetchAll') + ->will($this->returnCallback(function () use (&$exception, $stream) { + return $exception ? array(array($stream, 42, time())) : array(); + })); + + $insertStmt->expects($this->once())->method('execute') + ->will($this->returnCallback(function () use (&$exception) { + throw $exception = new \PDOException('', '23'); + })); + + $storage = new PdoSessionHandler($pdo); + $result = $storage->read('foo'); + + $this->assertSame($content, $result); + } + + public function testReadingRequiresExactlySameId() + { + $storage = new PdoSessionHandler($this->getMemorySqlitePdo()); + $storage->open('', 'sid'); + $storage->write('id', 'data'); + $storage->write('test', 'data'); + $storage->write('space ', 'data'); + $storage->close(); + + $storage->open('', 'sid'); + $readDataCaseSensitive = $storage->read('ID'); + $readDataNoCharFolding = $storage->read('tést'); + $readDataKeepSpace = $storage->read('space '); + $readDataExtraSpace = $storage->read('space '); + $storage->close(); + + $this->assertSame('', $readDataCaseSensitive, 'Retrieval by ID should be case-sensitive (collation setting)'); + $this->assertSame('', $readDataNoCharFolding, 'Retrieval by ID should not do character folding (collation setting)'); + $this->assertSame('data', $readDataKeepSpace, 'Retrieval by ID requires spaces as-is'); + $this->assertSame('', $readDataExtraSpace, 'Retrieval by ID requires spaces as-is'); + } + + /** + * Simulates session_regenerate_id(true) which will require an INSERT or UPDATE (replace). + */ + public function testWriteDifferentSessionIdThanRead() + { + $storage = new PdoSessionHandler($this->getMemorySqlitePdo()); + $storage->open('', 'sid'); + $storage->read('id'); + $storage->destroy('id'); + $storage->write('new_id', 'data_of_new_session_id'); + $storage->close(); + + $storage->open('', 'sid'); + $data = $storage->read('new_id'); + $storage->close(); + + $this->assertSame('data_of_new_session_id', $data, 'Data of regenerated session id is available'); + } + + public function testWrongUsageStillWorks() + { + // wrong method sequence that should no happen, but still works + $storage = new PdoSessionHandler($this->getMemorySqlitePdo()); + $storage->write('id', 'data'); + $storage->write('other_id', 'other_data'); + $storage->destroy('inexistent'); + $storage->open('', 'sid'); + $data = $storage->read('id'); + $otherData = $storage->read('other_id'); + $storage->close(); + + $this->assertSame('data', $data); + $this->assertSame('other_data', $otherData); + } + + public function testSessionDestroy() + { + $pdo = $this->getMemorySqlitePdo(); + $storage = new PdoSessionHandler($pdo); + + $storage->open('', 'sid'); + $storage->read('id'); + $storage->write('id', 'data'); + $storage->close(); + $this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn()); + + $storage->open('', 'sid'); + $storage->read('id'); + $storage->destroy('id'); + $storage->close(); + $this->assertEquals(0, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn()); + + $storage->open('', 'sid'); + $data = $storage->read('id'); + $storage->close(); + $this->assertSame('', $data, 'Destroyed session returns empty string'); + } + + public function testSessionGC() + { + $previousLifeTime = ini_set('session.gc_maxlifetime', 1000); + $pdo = $this->getMemorySqlitePdo(); + $storage = new PdoSessionHandler($pdo); + + $storage->open('', 'sid'); + $storage->read('id'); + $storage->write('id', 'data'); + $storage->close(); + + $storage->open('', 'sid'); + $storage->read('gc_id'); + ini_set('session.gc_maxlifetime', -1); // test that you can set lifetime of a session after it has been read + $storage->write('gc_id', 'data'); + $storage->close(); + $this->assertEquals(2, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'No session pruned because gc not called'); + + $storage->open('', 'sid'); + $data = $storage->read('gc_id'); + $storage->gc(-1); + $storage->close(); + + ini_set('session.gc_maxlifetime', $previousLifeTime); + + $this->assertSame('', $data, 'Session already considered garbage, so not returning data even if it is not pruned yet'); + $this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'Expired session is pruned'); + } + + public function testGetConnection() + { + $storage = new PdoSessionHandler($this->getMemorySqlitePdo()); + + $method = new \ReflectionMethod($storage, 'getConnection'); + $method->setAccessible(true); + + $this->assertInstanceOf('\PDO', $method->invoke($storage)); + } + + public function testGetConnectionConnectsIfNeeded() + { + $storage = new PdoSessionHandler('sqlite::memory:'); + + $method = new \ReflectionMethod($storage, 'getConnection'); + $method->setAccessible(true); + + $this->assertInstanceOf('\PDO', $method->invoke($storage)); + } + + private function createStream($content) + { + $stream = tmpfile(); + fwrite($stream, $content); + fseek($stream, 0); + + return $stream; + } +} + +class MockPdo extends \PDO +{ + public $prepareResult; + private $driverName; + private $errorMode; + + public function __construct($driverName = null, $errorMode = null) + { + $this->driverName = $driverName; + $this->errorMode = null !== $errorMode ?: \PDO::ERRMODE_EXCEPTION; + } + + public function getAttribute($attribute) + { + if (\PDO::ATTR_ERRMODE === $attribute) { + return $this->errorMode; + } + + if (\PDO::ATTR_DRIVER_NAME === $attribute) { + return $this->driverName; + } + + return parent::getAttribute($attribute); + } + + public function prepare($statement, $driverOptions = array()) + { + return is_callable($this->prepareResult) + ? call_user_func($this->prepareResult, $statement, $driverOptions) + : $this->prepareResult; + } + + public function beginTransaction() + { + } + + public function rollBack() + { + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php new file mode 100644 index 00000000..5e41a474 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php @@ -0,0 +1,95 @@ + + * + * 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\WriteCheckSessionHandler; + +/** + * @author Adrien Brault + */ +class WriteCheckSessionHandlerTest extends TestCase +{ + public function test() + { + $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock); + + $wrappedSessionHandlerMock + ->expects($this->once()) + ->method('close') + ->with() + ->will($this->returnValue(true)) + ; + + $this->assertTrue($writeCheckSessionHandler->close()); + } + + public function testWrite() + { + $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock); + + $wrappedSessionHandlerMock + ->expects($this->once()) + ->method('write') + ->with('foo', 'bar') + ->will($this->returnValue(true)) + ; + + $this->assertTrue($writeCheckSessionHandler->write('foo', 'bar')); + } + + public function testSkippedWrite() + { + $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock); + + $wrappedSessionHandlerMock + ->expects($this->once()) + ->method('read') + ->with('foo') + ->will($this->returnValue('bar')) + ; + + $wrappedSessionHandlerMock + ->expects($this->never()) + ->method('write') + ; + + $this->assertEquals('bar', $writeCheckSessionHandler->read('foo')); + $this->assertTrue($writeCheckSessionHandler->write('foo', 'bar')); + } + + public function testNonSkippedWrite() + { + $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock); + + $wrappedSessionHandlerMock + ->expects($this->once()) + ->method('read') + ->with('foo') + ->will($this->returnValue('bar')) + ; + + $wrappedSessionHandlerMock + ->expects($this->once()) + ->method('write') + ->with('foo', 'baZZZ') + ->will($this->returnValue(true)) + ; + + $this->assertEquals('bar', $writeCheckSessionHandler->read('foo')); + $this->assertTrue($writeCheckSessionHandler->write('foo', 'baZZZ')); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php new file mode 100644 index 00000000..159e6211 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php @@ -0,0 +1,142 @@ + + * + * 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; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; + +/** + * Test class for MetadataBag. + * + * @group time-sensitive + */ +class MetadataBagTest extends TestCase +{ + /** + * @var MetadataBag + */ + protected $bag; + + /** + * @var array + */ + protected $array = array(); + + protected function setUp() + { + parent::setUp(); + $this->bag = new MetadataBag(); + $this->array = array(MetadataBag::CREATED => 1234567, MetadataBag::UPDATED => 12345678, MetadataBag::LIFETIME => 0); + $this->bag->initialize($this->array); + } + + protected function tearDown() + { + $this->array = array(); + $this->bag = null; + parent::tearDown(); + } + + public function testInitialize() + { + $sessionMetadata = array(); + + $bag1 = new MetadataBag(); + $bag1->initialize($sessionMetadata); + $this->assertGreaterThanOrEqual(time(), $bag1->getCreated()); + $this->assertEquals($bag1->getCreated(), $bag1->getLastUsed()); + + sleep(1); + $bag2 = new MetadataBag(); + $bag2->initialize($sessionMetadata); + $this->assertEquals($bag1->getCreated(), $bag2->getCreated()); + $this->assertEquals($bag1->getLastUsed(), $bag2->getLastUsed()); + $this->assertEquals($bag2->getCreated(), $bag2->getLastUsed()); + + sleep(1); + $bag3 = new MetadataBag(); + $bag3->initialize($sessionMetadata); + $this->assertEquals($bag1->getCreated(), $bag3->getCreated()); + $this->assertGreaterThan($bag2->getLastUsed(), $bag3->getLastUsed()); + $this->assertNotEquals($bag3->getCreated(), $bag3->getLastUsed()); + } + + public function testGetSetName() + { + $this->assertEquals('__metadata', $this->bag->getName()); + $this->bag->setName('foo'); + $this->assertEquals('foo', $this->bag->getName()); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2_meta', $this->bag->getStorageKey()); + } + + public function testGetLifetime() + { + $bag = new MetadataBag(); + $array = array(MetadataBag::CREATED => 1234567, MetadataBag::UPDATED => 12345678, MetadataBag::LIFETIME => 1000); + $bag->initialize($array); + $this->assertEquals(1000, $bag->getLifetime()); + } + + public function testGetCreated() + { + $this->assertEquals(1234567, $this->bag->getCreated()); + } + + public function testGetLastUsed() + { + $this->assertLessThanOrEqual(time(), $this->bag->getLastUsed()); + } + + public function testClear() + { + $this->bag->clear(); + + // the clear method has no side effects, we just want to ensure it doesn't trigger any exceptions + $this->addToAssertionCount(1); + } + + public function testSkipLastUsedUpdate() + { + $bag = new MetadataBag('', 30); + $timeStamp = time(); + + $created = $timeStamp - 15; + $sessionMetadata = array( + MetadataBag::CREATED => $created, + MetadataBag::UPDATED => $created, + MetadataBag::LIFETIME => 1000, + ); + $bag->initialize($sessionMetadata); + + $this->assertEquals($created, $sessionMetadata[MetadataBag::UPDATED]); + } + + public function testDoesNotSkipLastUsedUpdate() + { + $bag = new MetadataBag('', 30); + $timeStamp = time(); + + $created = $timeStamp - 45; + $sessionMetadata = array( + MetadataBag::CREATED => $created, + MetadataBag::UPDATED => $created, + MetadataBag::LIFETIME => 1000, + ); + $bag->initialize($sessionMetadata); + + $this->assertEquals($timeStamp, $sessionMetadata[MetadataBag::UPDATED]); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php new file mode 100644 index 00000000..82df5543 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php @@ -0,0 +1,131 @@ + + * + * 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; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; + +/** + * Test class for MockArraySessionStorage. + * + * @author Drak + */ +class MockArraySessionStorageTest extends TestCase +{ + /** + * @var MockArraySessionStorage + */ + private $storage; + + /** + * @var AttributeBag + */ + private $attributes; + + /** + * @var FlashBag + */ + private $flashes; + + private $data; + + protected function setUp() + { + $this->attributes = new AttributeBag(); + $this->flashes = new FlashBag(); + + $this->data = array( + $this->attributes->getStorageKey() => array('foo' => 'bar'), + $this->flashes->getStorageKey() => array('notice' => 'hello'), + ); + + $this->storage = new MockArraySessionStorage(); + $this->storage->registerBag($this->flashes); + $this->storage->registerBag($this->attributes); + $this->storage->setSessionData($this->data); + } + + protected function tearDown() + { + $this->data = null; + $this->flashes = null; + $this->attributes = null; + $this->storage = null; + } + + public function testStart() + { + $this->assertEquals('', $this->storage->getId()); + $this->storage->start(); + $id = $this->storage->getId(); + $this->assertNotEquals('', $id); + $this->storage->start(); + $this->assertEquals($id, $this->storage->getId()); + } + + public function testRegenerate() + { + $this->storage->start(); + $id = $this->storage->getId(); + $this->storage->regenerate(); + $this->assertNotEquals($id, $this->storage->getId()); + $this->assertEquals(array('foo' => 'bar'), $this->storage->getBag('attributes')->all()); + $this->assertEquals(array('notice' => 'hello'), $this->storage->getBag('flashes')->peekAll()); + + $id = $this->storage->getId(); + $this->storage->regenerate(true); + $this->assertNotEquals($id, $this->storage->getId()); + $this->assertEquals(array('foo' => 'bar'), $this->storage->getBag('attributes')->all()); + $this->assertEquals(array('notice' => 'hello'), $this->storage->getBag('flashes')->peekAll()); + } + + public function testGetId() + { + $this->assertEquals('', $this->storage->getId()); + $this->storage->start(); + $this->assertNotEquals('', $this->storage->getId()); + } + + public function testClearClearsBags() + { + $this->storage->clear(); + + $this->assertSame(array(), $this->storage->getBag('attributes')->all()); + $this->assertSame(array(), $this->storage->getBag('flashes')->peekAll()); + } + + public function testClearStartsSession() + { + $this->storage->clear(); + + $this->assertTrue($this->storage->isStarted()); + } + + public function testClearWithNoBagsStartsSession() + { + $storage = new MockArraySessionStorage(); + + $storage->clear(); + + $this->assertTrue($storage->isStarted()); + } + + /** + * @expectedException \RuntimeException + */ + public function testUnstartedSave() + { + $this->storage->save(); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php new file mode 100644 index 00000000..53accd38 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php @@ -0,0 +1,127 @@ + + * + * 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; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; + +/** + * Test class for MockFileSessionStorage. + * + * @author Drak + */ +class MockFileSessionStorageTest extends TestCase +{ + /** + * @var string + */ + private $sessionDir; + + /** + * @var MockFileSessionStorage + */ + protected $storage; + + protected function setUp() + { + $this->sessionDir = sys_get_temp_dir().'/sf2test'; + $this->storage = $this->getStorage(); + } + + protected function tearDown() + { + $this->sessionDir = null; + $this->storage = null; + array_map('unlink', glob($this->sessionDir.'/*.session')); + if (is_dir($this->sessionDir)) { + rmdir($this->sessionDir); + } + } + + public function testStart() + { + $this->assertEquals('', $this->storage->getId()); + $this->assertTrue($this->storage->start()); + $id = $this->storage->getId(); + $this->assertNotEquals('', $this->storage->getId()); + $this->assertTrue($this->storage->start()); + $this->assertEquals($id, $this->storage->getId()); + } + + public function testRegenerate() + { + $this->storage->start(); + $this->storage->getBag('attributes')->set('regenerate', 1234); + $this->storage->regenerate(); + $this->assertEquals(1234, $this->storage->getBag('attributes')->get('regenerate')); + $this->storage->regenerate(true); + $this->assertEquals(1234, $this->storage->getBag('attributes')->get('regenerate')); + } + + public function testGetId() + { + $this->assertEquals('', $this->storage->getId()); + $this->storage->start(); + $this->assertNotEquals('', $this->storage->getId()); + } + + public function testSave() + { + $this->storage->start(); + $id = $this->storage->getId(); + $this->assertNotEquals('108', $this->storage->getBag('attributes')->get('new')); + $this->assertFalse($this->storage->getBag('flashes')->has('newkey')); + $this->storage->getBag('attributes')->set('new', '108'); + $this->storage->getBag('flashes')->set('newkey', 'test'); + $this->storage->save(); + + $storage = $this->getStorage(); + $storage->setId($id); + $storage->start(); + $this->assertEquals('108', $storage->getBag('attributes')->get('new')); + $this->assertTrue($storage->getBag('flashes')->has('newkey')); + $this->assertEquals(array('test'), $storage->getBag('flashes')->peek('newkey')); + } + + public function testMultipleInstances() + { + $storage1 = $this->getStorage(); + $storage1->start(); + $storage1->getBag('attributes')->set('foo', 'bar'); + $storage1->save(); + + $storage2 = $this->getStorage(); + $storage2->setId($storage1->getId()); + $storage2->start(); + $this->assertEquals('bar', $storage2->getBag('attributes')->get('foo'), 'values persist between instances'); + } + + /** + * @expectedException \RuntimeException + */ + public function testSaveWithoutStart() + { + $storage1 = $this->getStorage(); + $storage1->save(); + } + + private function getStorage() + { + $storage = new MockFileSessionStorage($this->sessionDir); + $storage->registerBag(new FlashBag()); + $storage->registerBag(new AttributeBag()); + + return $storage; + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php new file mode 100644 index 00000000..818c63a9 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php @@ -0,0 +1,247 @@ + + * + * 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; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; + +/** + * Test class for NativeSessionStorage. + * + * @author Drak + * + * These tests require separate processes. + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class NativeSessionStorageTest extends TestCase +{ + private $savePath; + + protected function setUp() + { + $this->iniSet('session.save_handler', 'files'); + $this->iniSet('session.save_path', $this->savePath = sys_get_temp_dir().'/sf2test'); + if (!is_dir($this->savePath)) { + mkdir($this->savePath); + } + } + + protected function tearDown() + { + session_write_close(); + array_map('unlink', glob($this->savePath.'/*')); + if (is_dir($this->savePath)) { + rmdir($this->savePath); + } + + $this->savePath = null; + } + + /** + * @param array $options + * + * @return NativeSessionStorage + */ + protected function getStorage(array $options = array()) + { + $storage = new NativeSessionStorage($options); + $storage->registerBag(new AttributeBag()); + + return $storage; + } + + public function testBag() + { + $storage = $this->getStorage(); + $bag = new FlashBag(); + $storage->registerBag($bag); + $this->assertSame($bag, $storage->getBag($bag->getName())); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRegisterBagException() + { + $storage = $this->getStorage(); + $storage->getBag('non_existing'); + } + + /** + * @expectedException \LogicException + */ + public function testRegisterBagForAStartedSessionThrowsException() + { + $storage = $this->getStorage(); + $storage->start(); + $storage->registerBag(new AttributeBag()); + } + + public function testGetId() + { + $storage = $this->getStorage(); + $this->assertSame('', $storage->getId(), 'Empty ID before starting session'); + + $storage->start(); + $id = $storage->getId(); + $this->assertInternalType('string', $id); + $this->assertNotSame('', $id); + + $storage->save(); + $this->assertSame($id, $storage->getId(), 'ID stays after saving session'); + } + + public function testRegenerate() + { + $storage = $this->getStorage(); + $storage->start(); + $id = $storage->getId(); + $storage->getBag('attributes')->set('lucky', 7); + $storage->regenerate(); + $this->assertNotEquals($id, $storage->getId()); + $this->assertEquals(7, $storage->getBag('attributes')->get('lucky')); + } + + public function testRegenerateDestroy() + { + $storage = $this->getStorage(); + $storage->start(); + $id = $storage->getId(); + $storage->getBag('attributes')->set('legs', 11); + $storage->regenerate(true); + $this->assertNotEquals($id, $storage->getId()); + $this->assertEquals(11, $storage->getBag('attributes')->get('legs')); + } + + public function testSessionGlobalIsUpToDateAfterIdRegeneration() + { + $storage = $this->getStorage(); + $storage->start(); + $storage->getBag('attributes')->set('lucky', 7); + $storage->regenerate(); + $storage->getBag('attributes')->set('lucky', 42); + + $this->assertEquals(42, $_SESSION['_sf2_attributes']['lucky']); + } + + public function testRegenerationFailureDoesNotFlagStorageAsStarted() + { + $storage = $this->getStorage(); + $this->assertFalse($storage->regenerate()); + $this->assertFalse($storage->isStarted()); + } + + public function testDefaultSessionCacheLimiter() + { + $this->iniSet('session.cache_limiter', 'nocache'); + + $storage = new NativeSessionStorage(); + $this->assertEquals('', ini_get('session.cache_limiter')); + } + + public function testExplicitSessionCacheLimiter() + { + $this->iniSet('session.cache_limiter', 'nocache'); + + $storage = new NativeSessionStorage(array('cache_limiter' => 'public')); + $this->assertEquals('public', ini_get('session.cache_limiter')); + } + + public function testCookieOptions() + { + $options = array( + 'cookie_lifetime' => 123456, + 'cookie_path' => '/my/cookie/path', + 'cookie_domain' => 'symfony.example.com', + 'cookie_secure' => true, + 'cookie_httponly' => false, + ); + + $this->getStorage($options); + $temp = session_get_cookie_params(); + $gco = array(); + + foreach ($temp as $key => $value) { + $gco['cookie_'.$key] = $value; + } + + $this->assertEquals($options, $gco); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSetSaveHandlerException() + { + $storage = $this->getStorage(); + $storage->setSaveHandler(new \stdClass()); + } + + public function testSetSaveHandler() + { + $this->iniSet('session.save_handler', 'files'); + $storage = $this->getStorage(); + $storage->setSaveHandler(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(null); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new SessionHandlerProxy(new NativeSessionHandler())); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new NativeSessionHandler()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new SessionHandlerProxy(new NullSessionHandler())); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new NullSessionHandler()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + } + + /** + * @expectedException \RuntimeException + */ + public function testStarted() + { + $storage = $this->getStorage(); + + $this->assertFalse($storage->getSaveHandler()->isActive()); + $this->assertFalse($storage->isStarted()); + + session_start(); + $this->assertTrue(isset($_SESSION)); + $this->assertTrue($storage->getSaveHandler()->isActive()); + + // PHP session might have started, but the storage driver has not, so false is correct here + $this->assertFalse($storage->isStarted()); + + $key = $storage->getMetadataBag()->getStorageKey(); + $this->assertFalse(isset($_SESSION[$key])); + $storage->start(); + } + + public function testRestart() + { + $storage = $this->getStorage(); + $storage->start(); + $id = $storage->getId(); + $storage->getBag('attributes')->set('lucky', 7); + $storage->save(); + $storage->start(); + $this->assertSame($id, $storage->getId(), 'Same session ID after restarting'); + $this->assertSame(7, $storage->getBag('attributes')->get('lucky'), 'Data still available'); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php new file mode 100644 index 00000000..b8b98386 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php @@ -0,0 +1,96 @@ + + * + * 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; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; + +/** + * Test class for PhpSessionStorage. + * + * @author Drak + * + * These tests require separate processes. + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class PhpBridgeSessionStorageTest extends TestCase +{ + private $savePath; + + protected function setUp() + { + $this->iniSet('session.save_handler', 'files'); + $this->iniSet('session.save_path', $this->savePath = sys_get_temp_dir().'/sf2test'); + if (!is_dir($this->savePath)) { + mkdir($this->savePath); + } + } + + protected function tearDown() + { + session_write_close(); + array_map('unlink', glob($this->savePath.'/*')); + if (is_dir($this->savePath)) { + rmdir($this->savePath); + } + + $this->savePath = null; + } + + /** + * @return PhpBridgeSessionStorage + */ + protected function getStorage() + { + $storage = new PhpBridgeSessionStorage(); + $storage->registerBag(new AttributeBag()); + + return $storage; + } + + public function testPhpSession() + { + $storage = $this->getStorage(); + + $this->assertFalse($storage->getSaveHandler()->isActive()); + $this->assertFalse($storage->isStarted()); + + session_start(); + $this->assertTrue(isset($_SESSION)); + // in PHP 5.4 we can reliably detect a session started + $this->assertTrue($storage->getSaveHandler()->isActive()); + // PHP session might have started, but the storage driver has not, so false is correct here + $this->assertFalse($storage->isStarted()); + + $key = $storage->getMetadataBag()->getStorageKey(); + $this->assertFalse(isset($_SESSION[$key])); + $storage->start(); + $this->assertTrue(isset($_SESSION[$key])); + } + + public function testClear() + { + $storage = $this->getStorage(); + session_start(); + $_SESSION['drak'] = 'loves symfony'; + $storage->getBag('attributes')->set('symfony', 'greatness'); + $key = $storage->getBag('attributes')->getStorageKey(); + $this->assertEquals($_SESSION[$key], array('symfony' => 'greatness')); + $this->assertEquals($_SESSION['drak'], 'loves symfony'); + $storage->clear(); + $this->assertEquals($_SESSION[$key], array()); + $this->assertEquals($_SESSION['drak'], 'loves symfony'); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php new file mode 100644 index 00000000..ef1da130 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php @@ -0,0 +1,145 @@ + + * + * 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\Proxy; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; + +// Note until PHPUnit_Mock_Objects 1.2 is released you cannot mock abstracts due to +// https://github.com/sebastianbergmann/phpunit-mock-objects/issues/73 +class ConcreteProxy extends AbstractProxy +{ +} + +class ConcreteSessionHandlerInterfaceProxy extends AbstractProxy implements \SessionHandlerInterface +{ + public function open($savePath, $sessionName) + { + } + + public function close() + { + } + + public function read($id) + { + } + + public function write($id, $data) + { + } + + public function destroy($id) + { + } + + public function gc($maxlifetime) + { + } +} + +/** + * Test class for AbstractProxy. + * + * @author Drak + */ +class AbstractProxyTest extends TestCase +{ + /** + * @var AbstractProxy + */ + protected $proxy; + + protected function setUp() + { + $this->proxy = new ConcreteProxy(); + } + + protected function tearDown() + { + $this->proxy = null; + } + + public function testGetSaveHandlerName() + { + $this->assertNull($this->proxy->getSaveHandlerName()); + } + + public function testIsSessionHandlerInterface() + { + $this->assertFalse($this->proxy->isSessionHandlerInterface()); + $sh = new ConcreteSessionHandlerInterfaceProxy(); + $this->assertTrue($sh->isSessionHandlerInterface()); + } + + public function testIsWrapper() + { + $this->assertFalse($this->proxy->isWrapper()); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testIsActive() + { + $this->assertFalse($this->proxy->isActive()); + session_start(); + $this->assertTrue($this->proxy->isActive()); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testName() + { + $this->assertEquals(session_name(), $this->proxy->getName()); + $this->proxy->setName('foo'); + $this->assertEquals('foo', $this->proxy->getName()); + $this->assertEquals(session_name(), $this->proxy->getName()); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @expectedException \LogicException + */ + public function testNameException() + { + session_start(); + $this->proxy->setName('foo'); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testId() + { + $this->assertEquals(session_id(), $this->proxy->getId()); + $this->proxy->setId('foo'); + $this->assertEquals('foo', $this->proxy->getId()); + $this->assertEquals(session_id(), $this->proxy->getId()); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @expectedException \LogicException + */ + public function testIdException() + { + session_start(); + $this->proxy->setId('foo'); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php new file mode 100644 index 00000000..8ec30534 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php @@ -0,0 +1,36 @@ + + * + * 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\Proxy; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy; + +/** + * Test class for NativeProxy. + * + * @author Drak + */ +class NativeProxyTest extends TestCase +{ + public function testIsWrapper() + { + $proxy = new NativeProxy(); + $this->assertFalse($proxy->isWrapper()); + } + + public function testGetSaveHandlerName() + { + $name = ini_get('session.save_handler'); + $proxy = new NativeProxy(); + $this->assertEquals($name, $proxy->getSaveHandlerName()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php new file mode 100644 index 00000000..68282535 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php @@ -0,0 +1,124 @@ + + * + * 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\Proxy; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; + +/** + * Tests for SessionHandlerProxy class. + * + * @author Drak + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class SessionHandlerProxyTest extends TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_Matcher + */ + private $mock; + + /** + * @var SessionHandlerProxy + */ + private $proxy; + + protected function setUp() + { + $this->mock = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $this->proxy = new SessionHandlerProxy($this->mock); + } + + protected function tearDown() + { + $this->mock = null; + $this->proxy = null; + } + + public function testOpenTrue() + { + $this->mock->expects($this->once()) + ->method('open') + ->will($this->returnValue(true)); + + $this->assertFalse($this->proxy->isActive()); + $this->proxy->open('name', 'id'); + $this->assertFalse($this->proxy->isActive()); + } + + public function testOpenFalse() + { + $this->mock->expects($this->once()) + ->method('open') + ->will($this->returnValue(false)); + + $this->assertFalse($this->proxy->isActive()); + $this->proxy->open('name', 'id'); + $this->assertFalse($this->proxy->isActive()); + } + + public function testClose() + { + $this->mock->expects($this->once()) + ->method('close') + ->will($this->returnValue(true)); + + $this->assertFalse($this->proxy->isActive()); + $this->proxy->close(); + $this->assertFalse($this->proxy->isActive()); + } + + public function testCloseFalse() + { + $this->mock->expects($this->once()) + ->method('close') + ->will($this->returnValue(false)); + + $this->assertFalse($this->proxy->isActive()); + $this->proxy->close(); + $this->assertFalse($this->proxy->isActive()); + } + + public function testRead() + { + $this->mock->expects($this->once()) + ->method('read'); + + $this->proxy->read('id'); + } + + public function testWrite() + { + $this->mock->expects($this->once()) + ->method('write'); + + $this->proxy->write('id', 'data'); + } + + public function testDestroy() + { + $this->mock->expects($this->once()) + ->method('destroy'); + + $this->proxy->destroy('id'); + } + + public function testGc() + { + $this->mock->expects($this->once()) + ->method('gc'); + + $this->proxy->gc(86400); + } +} diff --git a/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php b/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php new file mode 100644 index 00000000..1e35eb88 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php @@ -0,0 +1,115 @@ + + * + * 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\StreamedResponse; + +class StreamedResponseTest extends TestCase +{ + public function testConstructor() + { + $response = new StreamedResponse(function () { echo 'foo'; }, 404, array('Content-Type' => 'text/plain')); + + $this->assertEquals(404, $response->getStatusCode()); + $this->assertEquals('text/plain', $response->headers->get('Content-Type')); + } + + public function testPrepareWith11Protocol() + { + $response = new StreamedResponse(function () { echo 'foo'; }); + $request = Request::create('/'); + $request->server->set('SERVER_PROTOCOL', 'HTTP/1.1'); + + $response->prepare($request); + + $this->assertEquals('1.1', $response->getProtocolVersion()); + $this->assertNotEquals('chunked', $response->headers->get('Transfer-Encoding'), 'Apache assumes responses with a Transfer-Encoding header set to chunked to already be encoded.'); + } + + public function testPrepareWith10Protocol() + { + $response = new StreamedResponse(function () { echo 'foo'; }); + $request = Request::create('/'); + $request->server->set('SERVER_PROTOCOL', 'HTTP/1.0'); + + $response->prepare($request); + + $this->assertEquals('1.0', $response->getProtocolVersion()); + $this->assertNull($response->headers->get('Transfer-Encoding')); + } + + public function testPrepareWithHeadRequest() + { + $response = new StreamedResponse(function () { echo 'foo'; }, 200, array('Content-Length' => '123')); + $request = Request::create('/', 'HEAD'); + + $response->prepare($request); + + $this->assertSame('123', $response->headers->get('Content-Length')); + } + + public function testPrepareWithCacheHeaders() + { + $response = new StreamedResponse(function () { echo 'foo'; }, 200, array('Cache-Control' => 'max-age=600, public')); + $request = Request::create('/', 'GET'); + + $response->prepare($request); + $this->assertEquals('max-age=600, public', $response->headers->get('Cache-Control')); + } + + public function testSendContent() + { + $called = 0; + + $response = new StreamedResponse(function () use (&$called) { ++$called; }); + + $response->sendContent(); + $this->assertEquals(1, $called); + + $response->sendContent(); + $this->assertEquals(1, $called); + } + + /** + * @expectedException \LogicException + */ + public function testSendContentWithNonCallable() + { + $response = new StreamedResponse(null); + $response->sendContent(); + } + + /** + * @expectedException \LogicException + */ + public function testSetContent() + { + $response = new StreamedResponse(function () { echo 'foo'; }); + $response->setContent('foo'); + } + + public function testGetContent() + { + $response = new StreamedResponse(function () { echo 'foo'; }); + $this->assertFalse($response->getContent()); + } + + public function testCreate() + { + $response = StreamedResponse::create(function () {}, 204); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response); + $this->assertEquals(204, $response->getStatusCode()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng b/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng new file mode 100644 index 00000000..73708ca6 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng b/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng new file mode 100644 index 00000000..b9c3ca9d --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + uri + + + + rfc + + + (rfc|bcp|std)\d+ + + + + + rfc-errata + + + + draft + + + (draft|RFC)(-[a-zA-Z0-9]+)+ + + + + + registry + + + + person + + + + text + + + note + + + + unicode + + + ucd\d+\.\d+\.\d+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (\d+|0x[\da-fA-F]+)(\s*-\s*(\d+|0x[\da-fA-F]+))? + + + + + + + + + + + + + 0x[0-9]{8} + + + + + + [0-1]+ + + + + + + + + + + + + + + + + + + + + + + legacy + mib + template + json + + + + + + + + + + diff --git a/vendor/symfony/http-foundation/composer.json b/vendor/symfony/http-foundation/composer.json new file mode 100644 index 00000000..a964975e --- /dev/null +++ b/vendor/symfony/http-foundation/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/http-foundation", + "type": "library", + "description": "Symfony HttpFoundation 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": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/vendor/symfony/http-foundation/phpunit.xml.dist b/vendor/symfony/http-foundation/phpunit.xml.dist new file mode 100644 index 00000000..c1d61f8b --- /dev/null +++ b/vendor/symfony/http-foundation/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/http-kernel/.gitignore b/vendor/symfony/http-kernel/.gitignore new file mode 100644 index 00000000..94a6a252 --- /dev/null +++ b/vendor/symfony/http-kernel/.gitignore @@ -0,0 +1,5 @@ +vendor/ +composer.lock +phpunit.xml +Tests/Fixtures/cache/ +Tests/Fixtures/logs/ diff --git a/vendor/symfony/http-kernel/Bundle/Bundle.php b/vendor/symfony/http-kernel/Bundle/Bundle.php new file mode 100644 index 00000000..0b0ea088 --- /dev/null +++ b/vendor/symfony/http-kernel/Bundle/Bundle.php @@ -0,0 +1,231 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Bundle; + +use Symfony\Component\DependencyInjection\ContainerAwareTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\Console\Application; +use Symfony\Component\Finder\Finder; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; + +/** + * An implementation of BundleInterface that adds a few conventions + * for DependencyInjection extensions and Console commands. + * + * @author Fabien Potencier + */ +abstract class Bundle implements BundleInterface +{ + use ContainerAwareTrait; + + protected $name; + protected $extension; + protected $path; + private $namespace; + + /** + * Boots the Bundle. + */ + public function boot() + { + } + + /** + * Shutdowns the Bundle. + */ + public function shutdown() + { + } + + /** + * Builds the bundle. + * + * It is only ever called once when the cache is empty. + * + * This method can be overridden to register compilation passes, + * other extensions, ... + * + * @param ContainerBuilder $container A ContainerBuilder instance + */ + public function build(ContainerBuilder $container) + { + } + + /** + * Returns the bundle's container extension. + * + * @return ExtensionInterface|null The container extension + * + * @throws \LogicException + */ + public function getContainerExtension() + { + if (null === $this->extension) { + $extension = $this->createContainerExtension(); + + if (null !== $extension) { + if (!$extension instanceof ExtensionInterface) { + throw new \LogicException(sprintf('Extension %s must implement Symfony\Component\DependencyInjection\Extension\ExtensionInterface.', get_class($extension))); + } + + // check naming convention + $basename = preg_replace('/Bundle$/', '', $this->getName()); + $expectedAlias = Container::underscore($basename); + + if ($expectedAlias != $extension->getAlias()) { + throw new \LogicException(sprintf( + 'Users will expect the alias of the default extension of a bundle to be the underscored version of the bundle name ("%s"). You can override "Bundle::getContainerExtension()" if you want to use "%s" or another alias.', + $expectedAlias, $extension->getAlias() + )); + } + + $this->extension = $extension; + } else { + $this->extension = false; + } + } + + if ($this->extension) { + return $this->extension; + } + } + + /** + * Gets the Bundle namespace. + * + * @return string The Bundle namespace + */ + public function getNamespace() + { + if (null === $this->namespace) { + $this->parseClassName(); + } + + return $this->namespace; + } + + /** + * Gets the Bundle directory path. + * + * @return string The Bundle absolute path + */ + public function getPath() + { + if (null === $this->path) { + $reflected = new \ReflectionObject($this); + $this->path = dirname($reflected->getFileName()); + } + + return $this->path; + } + + /** + * Returns the bundle parent name. + * + * @return string|null The Bundle parent name it overrides or null if no parent + */ + public function getParent() + { + } + + /** + * Returns the bundle name (the class short name). + * + * @return string The Bundle name + */ + final public function getName() + { + if (null === $this->name) { + $this->parseClassName(); + } + + return $this->name; + } + + /** + * Finds and registers Commands. + * + * Override this method if your bundle commands do not follow the conventions: + * + * * Commands are in the 'Command' sub-directory + * * Commands extend Symfony\Component\Console\Command\Command + * + * @param Application $application An Application instance + */ + public function registerCommands(Application $application) + { + if (!is_dir($dir = $this->getPath().'/Command')) { + return; + } + + if (!class_exists('Symfony\Component\Finder\Finder')) { + throw new \RuntimeException('You need the symfony/finder component to register bundle commands.'); + } + + $finder = new Finder(); + $finder->files()->name('*Command.php')->in($dir); + + $prefix = $this->getNamespace().'\\Command'; + foreach ($finder as $file) { + $ns = $prefix; + if ($relativePath = $file->getRelativePath()) { + $ns .= '\\'.str_replace('/', '\\', $relativePath); + } + $class = $ns.'\\'.$file->getBasename('.php'); + if ($this->container) { + $commandIds = $this->container->hasParameter('console.command.ids') ? $this->container->getParameter('console.command.ids') : array(); + $alias = 'console.command.'.strtolower(str_replace('\\', '_', $class)); + if (isset($commandIds[$alias]) || $this->container->has($alias)) { + continue; + } + } + $r = new \ReflectionClass($class); + if ($r->isSubclassOf('Symfony\\Component\\Console\\Command\\Command') && !$r->isAbstract() && !$r->getConstructor()->getNumberOfRequiredParameters()) { + $application->add($r->newInstance()); + } + } + } + + /** + * Returns the bundle's container extension class. + * + * @return string + */ + protected function getContainerExtensionClass() + { + $basename = preg_replace('/Bundle$/', '', $this->getName()); + + return $this->getNamespace().'\\DependencyInjection\\'.$basename.'Extension'; + } + + /** + * Creates the bundle's container extension. + * + * @return ExtensionInterface|null + */ + protected function createContainerExtension() + { + if (class_exists($class = $this->getContainerExtensionClass())) { + return new $class(); + } + } + + private function parseClassName() + { + $pos = strrpos(static::class, '\\'); + $this->namespace = false === $pos ? '' : substr(static::class, 0, $pos); + if (null === $this->name) { + $this->name = false === $pos ? static::class : substr(static::class, $pos + 1); + } + } +} diff --git a/vendor/symfony/http-kernel/Bundle/BundleInterface.php b/vendor/symfony/http-kernel/Bundle/BundleInterface.php new file mode 100644 index 00000000..25eea1d7 --- /dev/null +++ b/vendor/symfony/http-kernel/Bundle/BundleInterface.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Bundle; + +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; + +/** + * BundleInterface. + * + * @author Fabien Potencier + */ +interface BundleInterface extends ContainerAwareInterface +{ + /** + * Boots the Bundle. + */ + public function boot(); + + /** + * Shutdowns the Bundle. + */ + public function shutdown(); + + /** + * Builds the bundle. + * + * It is only ever called once when the cache is empty. + * + * @param ContainerBuilder $container A ContainerBuilder instance + */ + public function build(ContainerBuilder $container); + + /** + * Returns the container extension that should be implicitly loaded. + * + * @return ExtensionInterface|null The default extension or null if there is none + */ + public function getContainerExtension(); + + /** + * Returns the bundle name that this bundle overrides. + * + * Despite its name, this method does not imply any parent/child relationship + * between the bundles, just a way to extend and override an existing + * bundle. + * + * @return string The Bundle name it overrides or null if no parent + */ + public function getParent(); + + /** + * Returns the bundle name (the class short name). + * + * @return string The Bundle name + */ + public function getName(); + + /** + * Gets the Bundle namespace. + * + * @return string The Bundle namespace + */ + public function getNamespace(); + + /** + * Gets the Bundle directory path. + * + * The path should always be returned as a Unix path (with /). + * + * @return string The Bundle absolute path + */ + public function getPath(); +} diff --git a/vendor/symfony/http-kernel/CHANGELOG.md b/vendor/symfony/http-kernel/CHANGELOG.md new file mode 100644 index 00000000..061f61d1 --- /dev/null +++ b/vendor/symfony/http-kernel/CHANGELOG.md @@ -0,0 +1,134 @@ +CHANGELOG +========= + +3.3.0 +----- + + * added `kernel.project_dir` and `Kernel::getProjectDir()` + * deprecated `kernel.root_dir` and `Kernel::getRootDir()` + * deprecated `Kernel::getEnvParameters()` + * deprecated the special `SYMFONY__` environment variables + * added the possibility to change the query string parameter used by `UriSigner` + * deprecated `LazyLoadingFragmentHandler::addRendererService()` + * deprecated `Extension::addClassesToCompile()` and `Extension::getClassesToCompile()` + * deprecated `Psr6CacheClearer::addPool()` + +3.2.0 +----- + + * deprecated `DataCollector::varToString()`, use `cloneVar()` instead + * changed surrogate capability name in `AbstractSurrogate::addSurrogateCapability` to 'symfony' + * Added `ControllerArgumentValueResolverPass` + +3.1.0 +----- + * deprecated passing objects as URI attributes to the ESI and SSI renderers + * deprecated `ControllerResolver::getArguments()` + * added `Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface` + * added `Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface` as argument to `HttpKernel` + * added `Symfony\Component\HttpKernel\Controller\ArgumentResolver` + * added `Symfony\Component\HttpKernel\DataCollector\RequestDataCollector::getMethod()` + * added `Symfony\Component\HttpKernel\DataCollector\RequestDataCollector::getRedirect()` + * added the `kernel.controller_arguments` event, triggered after controller arguments have been resolved + +3.0.0 +----- + + * removed `Symfony\Component\HttpKernel\Kernel::init()` + * removed `Symfony\Component\HttpKernel\Kernel::isClassInActiveBundle()` and `Symfony\Component\HttpKernel\KernelInterface::isClassInActiveBundle()` + * removed `Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher::setProfiler()` + * removed `Symfony\Component\HttpKernel\EventListener\FragmentListener::getLocalIpAddresses()` + * removed `Symfony\Component\HttpKernel\EventListener\LocaleListener::setRequest()` + * removed `Symfony\Component\HttpKernel\EventListener\RouterListener::setRequest()` + * removed `Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelRequest()` + * removed `Symfony\Component\HttpKernel\Fragment\FragmentHandler::setRequest()` + * removed `Symfony\Component\HttpKernel\HttpCache\Esi::hasSurrogateEsiCapability()` + * removed `Symfony\Component\HttpKernel\HttpCache\Esi::addSurrogateEsiCapability()` + * removed `Symfony\Component\HttpKernel\HttpCache\Esi::needsEsiParsing()` + * removed `Symfony\Component\HttpKernel\HttpCache\HttpCache::getEsi()` + * removed `Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel` + * removed `Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass` + * removed `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener` + * removed `Symfony\Component\HttpKernel\EventListener\EsiListener` + * removed `Symfony\Component\HttpKernel\HttpCache\EsiResponseCacheStrategy` + * removed `Symfony\Component\HttpKernel\HttpCache\EsiResponseCacheStrategyInterface` + * removed `Symfony\Component\HttpKernel\Log\LoggerInterface` + * removed `Symfony\Component\HttpKernel\Log\NullLogger` + * removed `Symfony\Component\HttpKernel\Profiler::import()` + * removed `Symfony\Component\HttpKernel\Profiler::export()` + +2.8.0 +----- + + * deprecated `Profiler::import` and `Profiler::export` + +2.7.0 +----- + + * added the HTTP status code to profiles + +2.6.0 +----- + + * deprecated `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener`, use `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` instead + * deprecated unused method `Symfony\Component\HttpKernel\Kernel::isClassInActiveBundle` and `Symfony\Component\HttpKernel\KernelInterface::isClassInActiveBundle` + +2.5.0 +----- + + * deprecated `Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass`, use `Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass` instead + +2.4.0 +----- + + * added event listeners for the session + * added the KernelEvents::FINISH_REQUEST event + +2.3.0 +----- + + * [BC BREAK] renamed `Symfony\Component\HttpKernel\EventListener\DeprecationLoggerListener` to `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener` and changed its constructor + * deprecated `Symfony\Component\HttpKernel\Debug\ErrorHandler`, `Symfony\Component\HttpKernel\Debug\ExceptionHandler`, + `Symfony\Component\HttpKernel\Exception\FatalErrorException` and `Symfony\Component\HttpKernel\Exception\FlattenException` + * deprecated `Symfony\Component\HttpKernel\Kernel::init()` + * added the possibility to specify an id an extra attributes to hinclude tags + * added the collect of data if a controller is a Closure in the Request collector + * pass exceptions from the ExceptionListener to the logger using the logging context to allow for more + detailed messages + +2.2.0 +----- + + * [BC BREAK] the path info for sub-request is now always _fragment (or whatever you configured instead of the default) + * added Symfony\Component\HttpKernel\EventListener\FragmentListener + * added Symfony\Component\HttpKernel\UriSigner + * added Symfony\Component\HttpKernel\FragmentRenderer and rendering strategies (in Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface) + * added Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel + * added ControllerReference to create reference of Controllers (used in the FragmentRenderer class) + * [BC BREAK] renamed TimeDataCollector::getTotalTime() to + TimeDataCollector::getDuration() + * updated the MemoryDataCollector to include the memory used in the + kernel.terminate event listeners + * moved the Stopwatch classes to a new component + * added TraceableControllerResolver + * added TraceableEventDispatcher (removed ContainerAwareTraceableEventDispatcher) + * added support for WinCache opcode cache in ConfigDataCollector + +2.1.0 +----- + + * [BC BREAK] the charset is now configured via the Kernel::getCharset() method + * [BC BREAK] the current locale for the user is not stored anymore in the session + * added the HTTP method to the profiler storage + * updated all listeners to implement EventSubscriberInterface + * added TimeDataCollector + * added ContainerAwareTraceableEventDispatcher + * moved TraceableEventDispatcherInterface to the EventDispatcher component + * added RouterListener, LocaleListener, and StreamedResponseListener + * added CacheClearerInterface (and ChainCacheClearer) + * added a kernel.terminate event (via TerminableInterface and PostResponseEvent) + * added a Stopwatch class + * added WarmableInterface + * improved extensibility between bundles + * added profiler storages for Memcache(d), File-based, MongoDB, Redis + * moved Filesystem class to its own component diff --git a/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php b/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php new file mode 100644 index 00000000..675c5842 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheClearer; + +/** + * CacheClearerInterface. + * + * @author Dustin Dobervich + */ +interface CacheClearerInterface +{ + /** + * Clears any caches necessary. + * + * @param string $cacheDir The cache directory + */ + public function clear($cacheDir); +} diff --git a/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php b/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php new file mode 100644 index 00000000..c749c7c0 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheClearer; + +/** + * ChainCacheClearer. + * + * @author Dustin Dobervich + */ +class ChainCacheClearer implements CacheClearerInterface +{ + /** + * @var array + */ + protected $clearers; + + /** + * Constructs a new instance of ChainCacheClearer. + * + * @param array $clearers The initial clearers + */ + public function __construct(array $clearers = array()) + { + $this->clearers = $clearers; + } + + /** + * {@inheritdoc} + */ + public function clear($cacheDir) + { + foreach ($this->clearers as $clearer) { + $clearer->clear($cacheDir); + } + } + + /** + * Adds a cache clearer to the aggregate. + * + * @param CacheClearerInterface $clearer + */ + public function add(CacheClearerInterface $clearer) + { + $this->clearers[] = $clearer; + } +} diff --git a/vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php b/vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php new file mode 100644 index 00000000..2336b18a --- /dev/null +++ b/vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheClearer; + +use Psr\Cache\CacheItemPoolInterface; + +/** + * @author Nicolas Grekas + */ +class Psr6CacheClearer implements CacheClearerInterface +{ + private $pools = array(); + + public function __construct(array $pools = array()) + { + $this->pools = $pools; + } + + public function addPool(CacheItemPoolInterface $pool) + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Pass an array of pools indexed by name to the constructor instead.', __METHOD__), E_USER_DEPRECATED); + + $this->pools[] = $pool; + } + + public function hasPool($name) + { + return isset($this->pools[$name]); + } + + public function clearPool($name) + { + if (!isset($this->pools[$name])) { + throw new \InvalidArgumentException(sprintf('Cache pool not found: %s.', $name)); + } + + return $this->pools[$name]->clear(); + } + + /** + * {@inheritdoc} + */ + public function clear($cacheDir) + { + foreach ($this->pools as $pool) { + $pool->clear(); + } + } +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php new file mode 100644 index 00000000..dba35a63 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Abstract cache warmer that knows how to write a file to the cache. + * + * @author Fabien Potencier + */ +abstract class CacheWarmer implements CacheWarmerInterface +{ + protected function writeCacheFile($file, $content) + { + $tmpFile = @tempnam(dirname($file), basename($file)); + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) { + @chmod($file, 0666 & ~umask()); + + return; + } + + throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file)); + } +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php new file mode 100644 index 00000000..e5f4e4fa --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Aggregates several cache warmers into a single one. + * + * @author Fabien Potencier + */ +class CacheWarmerAggregate implements CacheWarmerInterface +{ + protected $warmers = array(); + protected $optionalsEnabled = false; + + public function __construct(array $warmers = array()) + { + foreach ($warmers as $warmer) { + $this->add($warmer); + } + } + + public function enableOptionalWarmers() + { + $this->optionalsEnabled = true; + } + + /** + * Warms up the cache. + * + * @param string $cacheDir The cache directory + */ + public function warmUp($cacheDir) + { + foreach ($this->warmers as $warmer) { + if (!$this->optionalsEnabled && $warmer->isOptional()) { + continue; + } + + $warmer->warmUp($cacheDir); + } + } + + /** + * Checks whether this warmer is optional or not. + * + * @return bool always false + */ + public function isOptional() + { + return false; + } + + public function setWarmers(array $warmers) + { + $this->warmers = array(); + foreach ($warmers as $warmer) { + $this->add($warmer); + } + } + + public function add(CacheWarmerInterface $warmer) + { + $this->warmers[] = $warmer; + } +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php new file mode 100644 index 00000000..8fece5e9 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Interface for classes able to warm up the cache. + * + * @author Fabien Potencier + */ +interface CacheWarmerInterface extends WarmableInterface +{ + /** + * Checks whether this warmer is optional or not. + * + * Optional warmers can be ignored on certain conditions. + * + * A warmer should return true if the cache can be + * generated incrementally and on-demand. + * + * @return bool true if the warmer is optional, false otherwise + */ + public function isOptional(); +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php b/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php new file mode 100644 index 00000000..25d8ee8f --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Interface for classes that support warming their cache. + * + * @author Fabien Potencier + */ +interface WarmableInterface +{ + /** + * Warms up the cache. + * + * @param string $cacheDir The cache directory + */ + public function warmUp($cacheDir); +} diff --git a/vendor/symfony/http-kernel/Client.php b/vendor/symfony/http-kernel/Client.php new file mode 100644 index 00000000..94f70cd6 --- /dev/null +++ b/vendor/symfony/http-kernel/Client.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\BrowserKit\Client as BaseClient; +use Symfony\Component\BrowserKit\Request as DomRequest; +use Symfony\Component\BrowserKit\Response as DomResponse; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Client simulates a browser and makes requests to a Kernel object. + * + * @author Fabien Potencier + * + * @method Request|null getRequest() A Request instance + * @method Response|null getResponse() A Response instance + */ +class Client extends BaseClient +{ + protected $kernel; + + /** + * Constructor. + * + * @param HttpKernelInterface $kernel An HttpKernel instance + * @param array $server The server parameters (equivalent of $_SERVER) + * @param History $history A History instance to store the browser history + * @param CookieJar $cookieJar A CookieJar instance to store the cookies + */ + public function __construct(HttpKernelInterface $kernel, array $server = array(), History $history = null, CookieJar $cookieJar = null) + { + // These class properties must be set before calling the parent constructor, as it may depend on it. + $this->kernel = $kernel; + $this->followRedirects = false; + + parent::__construct($server, $history, $cookieJar); + } + + /** + * Makes a request. + * + * @param Request $request A Request instance + * + * @return Response A Response instance + */ + protected function doRequest($request) + { + $response = $this->kernel->handle($request); + + if ($this->kernel instanceof TerminableInterface) { + $this->kernel->terminate($request, $response); + } + + return $response; + } + + /** + * Returns the script to execute when the request must be insulated. + * + * @param Request $request A Request instance + * + * @return string + */ + protected function getScript($request) + { + $kernel = str_replace("'", "\\'", serialize($this->kernel)); + $request = str_replace("'", "\\'", serialize($request)); + $errorReporting = error_reporting(); + + $requires = ''; + foreach (get_declared_classes() as $class) { + if (0 === strpos($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $file = dirname(dirname($r->getFileName())).'/autoload.php'; + if (file_exists($file)) { + $requires .= "require_once '".str_replace("'", "\\'", $file)."';\n"; + } + } + } + + if (!$requires) { + throw new \RuntimeException('Composer autoloader not found.'); + } + + $code = <<getHandleScript(); + } + + protected function getHandleScript() + { + return <<<'EOF' +$response = $kernel->handle($request); + +if ($kernel instanceof Symfony\Component\HttpKernel\TerminableInterface) { + $kernel->terminate($request, $response); +} + +echo serialize($response); +EOF; + } + + /** + * Converts the BrowserKit request to a HttpKernel request. + * + * @param DomRequest $request A DomRequest instance + * + * @return Request A Request instance + */ + protected function filterRequest(DomRequest $request) + { + $httpRequest = Request::create($request->getUri(), $request->getMethod(), $request->getParameters(), $request->getCookies(), $request->getFiles(), $request->getServer(), $request->getContent()); + + foreach ($this->filterFiles($httpRequest->files->all()) as $key => $value) { + $httpRequest->files->set($key, $value); + } + + return $httpRequest; + } + + /** + * Filters an array of files. + * + * This method created test instances of UploadedFile so that the move() + * method can be called on those instances. + * + * If the size of a file is greater than the allowed size (from php.ini) then + * an invalid UploadedFile is returned with an error set to UPLOAD_ERR_INI_SIZE. + * + * @see UploadedFile + * + * @param array $files An array of files + * + * @return array An array with all uploaded files marked as already moved + */ + protected function filterFiles(array $files) + { + $filtered = array(); + foreach ($files as $key => $value) { + if (is_array($value)) { + $filtered[$key] = $this->filterFiles($value); + } elseif ($value instanceof UploadedFile) { + if ($value->isValid() && $value->getSize() > UploadedFile::getMaxFilesize()) { + $filtered[$key] = new UploadedFile( + '', + $value->getClientOriginalName(), + $value->getClientMimeType(), + 0, + UPLOAD_ERR_INI_SIZE, + true + ); + } else { + $filtered[$key] = new UploadedFile( + $value->getPathname(), + $value->getClientOriginalName(), + $value->getClientMimeType(), + $value->getClientSize(), + $value->getError(), + true + ); + } + } + } + + return $filtered; + } + + /** + * Converts the HttpKernel response to a BrowserKit response. + * + * @param Response $response A Response instance + * + * @return DomResponse A DomResponse instance + */ + protected function filterResponse($response) + { + // this is needed to support StreamedResponse + ob_start(); + $response->sendContent(); + $content = ob_get_clean(); + + return new DomResponse($content, $response->getStatusCode(), $response->headers->all()); + } +} diff --git a/vendor/symfony/http-kernel/Config/EnvParametersResource.php b/vendor/symfony/http-kernel/Config/EnvParametersResource.php new file mode 100644 index 00000000..0fe6aca8 --- /dev/null +++ b/vendor/symfony/http-kernel/Config/EnvParametersResource.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Config; + +use Symfony\Component\Config\Resource\SelfCheckingResourceInterface; + +/** + * EnvParametersResource represents resources stored in prefixed environment variables. + * + * @author Chris Wilkinson + */ +class EnvParametersResource implements SelfCheckingResourceInterface, \Serializable +{ + /** + * @var string + */ + private $prefix; + + /** + * @var string + */ + private $variables; + + /** + * Constructor. + * + * @param string $prefix + */ + public function __construct($prefix) + { + $this->prefix = $prefix; + $this->variables = $this->findVariables(); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return serialize($this->getResource()); + } + + /** + * @return array An array with two keys: 'prefix' for the prefix used and 'variables' containing all the variables watched by this resource + */ + public function getResource() + { + return array('prefix' => $this->prefix, 'variables' => $this->variables); + } + + /** + * {@inheritdoc} + */ + public function isFresh($timestamp) + { + return $this->findVariables() === $this->variables; + } + + public function serialize() + { + return serialize(array('prefix' => $this->prefix, 'variables' => $this->variables)); + } + + public function unserialize($serialized) + { + if (\PHP_VERSION_ID >= 70000) { + $unserialized = unserialize($serialized, array('allowed_classes' => false)); + } else { + $unserialized = unserialize($serialized); + } + + $this->prefix = $unserialized['prefix']; + $this->variables = $unserialized['variables']; + } + + private function findVariables() + { + $variables = array(); + + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, $this->prefix)) { + $variables[$key] = $value; + } + } + + ksort($variables); + + return $variables; + } +} diff --git a/vendor/symfony/http-kernel/Config/FileLocator.php b/vendor/symfony/http-kernel/Config/FileLocator.php new file mode 100644 index 00000000..169c9ad6 --- /dev/null +++ b/vendor/symfony/http-kernel/Config/FileLocator.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Config; + +use Symfony\Component\Config\FileLocator as BaseFileLocator; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * FileLocator uses the KernelInterface to locate resources in bundles. + * + * @author Fabien Potencier + */ +class FileLocator extends BaseFileLocator +{ + private $kernel; + private $path; + + /** + * Constructor. + * + * @param KernelInterface $kernel A KernelInterface instance + * @param null|string $path The path the global resource directory + * @param array $paths An array of paths where to look for resources + */ + public function __construct(KernelInterface $kernel, $path = null, array $paths = array()) + { + $this->kernel = $kernel; + if (null !== $path) { + $this->path = $path; + $paths[] = $path; + } + + parent::__construct($paths); + } + + /** + * {@inheritdoc} + */ + public function locate($file, $currentPath = null, $first = true) + { + if (isset($file[0]) && '@' === $file[0]) { + return $this->kernel->locateResource($file, $this->path, $first); + } + + return parent::locate($file, $currentPath, $first); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver.php new file mode 100644 index 00000000..2c17125c --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface; + +/** + * Responsible for resolving the arguments passed to an action. + * + * @author Iltar van der Berg + */ +final class ArgumentResolver implements ArgumentResolverInterface +{ + private $argumentMetadataFactory; + + /** + * @var iterable|ArgumentValueResolverInterface[] + */ + private $argumentValueResolvers; + + public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, $argumentValueResolvers = array()) + { + $this->argumentMetadataFactory = $argumentMetadataFactory ?: new ArgumentMetadataFactory(); + $this->argumentValueResolvers = $argumentValueResolvers ?: self::getDefaultArgumentValueResolvers(); + } + + /** + * {@inheritdoc} + */ + public function getArguments(Request $request, $controller) + { + $arguments = array(); + + foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller) as $metadata) { + foreach ($this->argumentValueResolvers as $resolver) { + if (!$resolver->supports($request, $metadata)) { + continue; + } + + $resolved = $resolver->resolve($request, $metadata); + + if (!$resolved instanceof \Generator) { + throw new \InvalidArgumentException(sprintf('%s::resolve() must yield at least one value.', get_class($resolver))); + } + + foreach ($resolved as $append) { + $arguments[] = $append; + } + + // continue to the next controller argument + continue 2; + } + + $representative = $controller; + + if (is_array($representative)) { + $representative = sprintf('%s::%s()', get_class($representative[0]), $representative[1]); + } elseif (is_object($representative)) { + $representative = get_class($representative); + } + + throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.', $representative, $metadata->getName())); + } + + return $arguments; + } + + public static function getDefaultArgumentValueResolvers() + { + return array( + new RequestAttributeValueResolver(), + new RequestValueResolver(), + new SessionValueResolver(), + new DefaultValueResolver(), + new VariadicValueResolver(), + ); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php new file mode 100644 index 00000000..e58fd3ab --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields the default value defined in the action signature when no value has been given. + * + * @author Iltar van der Berg + */ +final class DefaultValueResolver implements ArgumentValueResolverInterface +{ + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return $argument->hasDefaultValue() || (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic()); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $argument->hasDefaultValue() ? $argument->getDefaultValue() : null; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php new file mode 100644 index 00000000..05be372d --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields a non-variadic argument's value from the request attributes. + * + * @author Iltar van der Berg + */ +final class RequestAttributeValueResolver implements ArgumentValueResolverInterface +{ + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return !$argument->isVariadic() && $request->attributes->has($argument->getName()); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $request->attributes->get($argument->getName()); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php new file mode 100644 index 00000000..2a5060a6 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields the same instance as the request object passed along. + * + * @author Iltar van der Berg + */ +final class RequestValueResolver implements ArgumentValueResolverInterface +{ + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $request; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php new file mode 100644 index 00000000..b1da6a9a --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields a service keyed by _controller and argument name. + * + * @author Nicolas Grekas + */ +final class ServiceValueResolver implements ArgumentValueResolverInterface +{ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return is_string($controller = $request->attributes->get('_controller')) && $this->container->has($controller) && $this->container->get($controller)->has($argument->getName()); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $this->container->get($request->attributes->get('_controller'))->get($argument->getName()); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php new file mode 100644 index 00000000..9e656d28 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields the Session. + * + * @author Iltar van der Berg + */ +final class SessionValueResolver implements ArgumentValueResolverInterface +{ + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + $type = $argument->getType(); + if (SessionInterface::class !== $type && !is_subclass_of($type, SessionInterface::class)) { + return false; + } + + return $request->getSession() instanceof $type; + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $request->getSession(); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php new file mode 100644 index 00000000..56ae5f19 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields a variadic argument's values from the request attributes. + * + * @author Iltar van der Berg + */ +final class VariadicValueResolver implements ArgumentValueResolverInterface +{ + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return $argument->isVariadic() && $request->attributes->has($argument->getName()); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + $values = $request->attributes->get($argument->getName()); + + if (!is_array($values)) { + throw new \InvalidArgumentException(sprintf('The action argument "...$%1$s" is required to be an array, the request attribute "%1$s" contains a type of "%2$s" instead.', $argument->getName(), gettype($values))); + } + + foreach ($values as $value) { + yield $value; + } + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php b/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php new file mode 100644 index 00000000..5c512309 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; + +/** + * An ArgumentResolverInterface instance knows how to determine the + * arguments for a specific action. + * + * @author Fabien Potencier + */ +interface ArgumentResolverInterface +{ + /** + * Returns the arguments to pass to the controller. + * + * @param Request $request + * @param callable $controller + * + * @return array An array of arguments to pass to the controller + * + * @throws \RuntimeException When no value could be provided for a required argument + */ + public function getArguments(Request $request, $controller); +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php b/vendor/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php new file mode 100644 index 00000000..fd7b09ec --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Responsible for resolving the value of an argument based on its metadata. + * + * @author Iltar van der Berg + */ +interface ArgumentValueResolverInterface +{ + /** + * Whether this resolver can resolve the value for the given ArgumentMetadata. + * + * @param Request $request + * @param ArgumentMetadata $argument + * + * @return bool + */ + public function supports(Request $request, ArgumentMetadata $argument); + + /** + * Returns the possible value(s). + * + * @param Request $request + * @param ArgumentMetadata $argument + * + * @return \Generator + */ + public function resolve(Request $request, ArgumentMetadata $argument); +} diff --git a/vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php b/vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php new file mode 100644 index 00000000..1a107c62 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; + +/** + * A controller resolver searching for a controller in a psr-11 container when using the "service:method" notation. + * + * @author Fabien Potencier + * @author Maxime Steinhausser + */ +class ContainerControllerResolver extends ControllerResolver +{ + protected $container; + + public function __construct(ContainerInterface $container, LoggerInterface $logger = null) + { + $this->container = $container; + + parent::__construct($logger); + } + + /** + * Returns a callable for the given controller. + * + * @param string $controller A Controller string + * + * @return mixed A PHP callable + * + * @throws \LogicException When the name could not be parsed + * @throws \InvalidArgumentException When the controller class does not exist + */ + protected function createController($controller) + { + if (false !== strpos($controller, '::')) { + return parent::createController($controller); + } + + if (1 == substr_count($controller, ':')) { + // controller in the "service:method" notation + list($service, $method) = explode(':', $controller, 2); + + return array($this->container->get($service), $method); + } + + if ($this->container->has($controller) && method_exists($service = $this->container->get($controller), '__invoke')) { + // invokable controller in the "service" notation + return $service; + } + + throw new \LogicException(sprintf('Unable to parse the controller name "%s".', $controller)); + } + + /** + * {@inheritdoc} + */ + protected function instantiateController($class) + { + if ($this->container->has($class)) { + return $this->container->get($class); + } + + return parent::instantiateController($class); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ControllerReference.php b/vendor/symfony/http-kernel/Controller/ControllerReference.php new file mode 100644 index 00000000..3d1592e8 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerReference.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; + +/** + * Acts as a marker and a data holder for a Controller. + * + * Some methods in Symfony accept both a URI (as a string) or a controller as + * an argument. In the latter case, instead of passing an array representing + * the controller, you can use an instance of this class. + * + * @author Fabien Potencier + * + * @see FragmentRendererInterface + */ +class ControllerReference +{ + public $controller; + public $attributes = array(); + public $query = array(); + + /** + * Constructor. + * + * @param string $controller The controller name + * @param array $attributes An array of parameters to add to the Request attributes + * @param array $query An array of parameters to add to the Request query string + */ + public function __construct($controller, array $attributes = array(), array $query = array()) + { + $this->controller = $controller; + $this->attributes = $attributes; + $this->query = $query; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ControllerResolver.php b/vendor/symfony/http-kernel/Controller/ControllerResolver.php new file mode 100644 index 00000000..f51a5a8e --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerResolver.php @@ -0,0 +1,265 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * ControllerResolver. + * + * This implementation uses the '_controller' request attribute to determine + * the controller to execute and uses the request attributes to determine + * the controller method arguments. + * + * @author Fabien Potencier + */ +class ControllerResolver implements ArgumentResolverInterface, ControllerResolverInterface +{ + private $logger; + + /** + * If the ...$arg functionality is available. + * + * Requires at least PHP 5.6.0 or HHVM 3.9.1 + * + * @var bool + */ + private $supportsVariadic; + + /** + * If scalar types exists. + * + * @var bool + */ + private $supportsScalarTypes; + + /** + * Constructor. + * + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(LoggerInterface $logger = null) + { + $this->logger = $logger; + + $this->supportsVariadic = method_exists('ReflectionParameter', 'isVariadic'); + $this->supportsScalarTypes = method_exists('ReflectionParameter', 'getType'); + } + + /** + * {@inheritdoc} + * + * This method looks for a '_controller' request attribute that represents + * the controller name (a string like ClassName::MethodName). + */ + public function getController(Request $request) + { + if (!$controller = $request->attributes->get('_controller')) { + if (null !== $this->logger) { + $this->logger->warning('Unable to look for the controller as the "_controller" parameter is missing.'); + } + + return false; + } + + if (is_array($controller)) { + return $controller; + } + + if (is_object($controller)) { + if (method_exists($controller, '__invoke')) { + return $controller; + } + + throw new \InvalidArgumentException(sprintf('Controller "%s" for URI "%s" is not callable.', get_class($controller), $request->getPathInfo())); + } + + if (false === strpos($controller, ':')) { + if (method_exists($controller, '__invoke')) { + return $this->instantiateController($controller); + } elseif (function_exists($controller)) { + return $controller; + } + } + + $callable = $this->createController($controller); + + if (!is_callable($callable)) { + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable. %s', $request->getPathInfo(), $this->getControllerError($callable))); + } + + return $callable; + } + + /** + * {@inheritdoc} + * + * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Implement the ArgumentResolverInterface and inject it in the HttpKernel instead. + */ + public function getArguments(Request $request, $controller) + { + @trigger_error(sprintf('%s is deprecated as of 3.1 and will be removed in 4.0. Implement the %s and inject it in the HttpKernel instead.', __METHOD__, ArgumentResolverInterface::class), E_USER_DEPRECATED); + + if (is_array($controller)) { + $r = new \ReflectionMethod($controller[0], $controller[1]); + } elseif (is_object($controller) && !$controller instanceof \Closure) { + $r = new \ReflectionObject($controller); + $r = $r->getMethod('__invoke'); + } else { + $r = new \ReflectionFunction($controller); + } + + return $this->doGetArguments($request, $controller, $r->getParameters()); + } + + /** + * @param Request $request + * @param callable $controller + * @param \ReflectionParameter[] $parameters + * + * @return array The arguments to use when calling the action + * + * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Implement the ArgumentResolverInterface and inject it in the HttpKernel instead. + */ + protected function doGetArguments(Request $request, $controller, array $parameters) + { + @trigger_error(sprintf('%s is deprecated as of 3.1 and will be removed in 4.0. Implement the %s and inject it in the HttpKernel instead.', __METHOD__, ArgumentResolverInterface::class), E_USER_DEPRECATED); + + $attributes = $request->attributes->all(); + $arguments = array(); + foreach ($parameters as $param) { + if (array_key_exists($param->name, $attributes)) { + if ($this->supportsVariadic && $param->isVariadic() && is_array($attributes[$param->name])) { + $arguments = array_merge($arguments, array_values($attributes[$param->name])); + } else { + $arguments[] = $attributes[$param->name]; + } + } elseif ($param->getClass() && $param->getClass()->isInstance($request)) { + $arguments[] = $request; + } elseif ($param->isDefaultValueAvailable()) { + $arguments[] = $param->getDefaultValue(); + } elseif ($this->supportsScalarTypes && $param->hasType() && $param->allowsNull()) { + $arguments[] = null; + } else { + if (is_array($controller)) { + $repr = sprintf('%s::%s()', get_class($controller[0]), $controller[1]); + } elseif (is_object($controller)) { + $repr = get_class($controller); + } else { + $repr = $controller; + } + + throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $repr, $param->name)); + } + } + + return $arguments; + } + + /** + * Returns a callable for the given controller. + * + * @param string $controller A Controller string + * + * @return callable A PHP callable + * + * @throws \InvalidArgumentException + */ + protected function createController($controller) + { + if (false === strpos($controller, '::')) { + throw new \InvalidArgumentException(sprintf('Unable to find controller "%s".', $controller)); + } + + list($class, $method) = explode('::', $controller, 2); + + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + } + + return array($this->instantiateController($class), $method); + } + + /** + * Returns an instantiated controller. + * + * @param string $class A class name + * + * @return object + */ + protected function instantiateController($class) + { + return new $class(); + } + + private function getControllerError($callable) + { + if (is_string($callable)) { + if (false !== strpos($callable, '::')) { + $callable = explode('::', $callable); + } + + if (class_exists($callable) && !method_exists($callable, '__invoke')) { + return sprintf('Class "%s" does not have a method "__invoke".', $callable); + } + + if (!function_exists($callable)) { + return sprintf('Function "%s" does not exist.', $callable); + } + } + + if (!is_array($callable)) { + return sprintf('Invalid type for controller given, expected string or array, got "%s".', gettype($callable)); + } + + if (2 !== count($callable)) { + return sprintf('Invalid format for controller, expected array(controller, method) or controller::method.'); + } + + list($controller, $method) = $callable; + + if (is_string($controller) && !class_exists($controller)) { + return sprintf('Class "%s" does not exist.', $controller); + } + + $className = is_object($controller) ? get_class($controller) : $controller; + + if (method_exists($controller, $method)) { + return sprintf('Method "%s" on class "%s" should be public and non-abstract.', $method, $className); + } + + $collection = get_class_methods($controller); + + $alternatives = array(); + + foreach ($collection as $item) { + $lev = levenshtein($method, $item); + + if ($lev <= strlen($method) / 3 || false !== strpos($item, $method)) { + $alternatives[] = $item; + } + } + + asort($alternatives); + + $message = sprintf('Expected method "%s" on class "%s"', $method, $className); + + if (count($alternatives) > 0) { + $message .= sprintf(', did you mean "%s"?', implode('", "', $alternatives)); + } else { + $message .= sprintf('. Available methods: "%s".', implode('", "', $collection)); + } + + return $message; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php b/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php new file mode 100644 index 00000000..0dd7cce9 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; + +/** + * A ControllerResolverInterface implementation knows how to determine the + * controller to execute based on a Request object. + * + * It can also determine the arguments to pass to the Controller. + * + * A Controller can be any valid PHP callable. + * + * @author Fabien Potencier + */ +interface ControllerResolverInterface +{ + /** + * Returns the Controller instance associated with a Request. + * + * As several resolvers can exist for a single application, a resolver must + * return false when it is not able to determine the controller. + * + * The resolver must only throw an exception when it should be able to load + * controller but cannot because of some errors made by the developer. + * + * @param Request $request A Request instance + * + * @return callable|false A PHP callable representing the Controller, + * or false if this resolver is not able to determine the controller + * + * @throws \LogicException If the controller can't be found + */ + public function getController(Request $request); + + /** + * Returns the arguments to pass to the controller. + * + * @param Request $request A Request instance + * @param callable $controller A PHP callable + * + * @return array An array of arguments to pass to the controller + * + * @throws \RuntimeException When value for argument given is not provided + * + * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Please use the {@see ArgumentResolverInterface} instead. + */ + public function getArguments(Request $request, $controller); +} diff --git a/vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php b/vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php new file mode 100644 index 00000000..6fb0fa66 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Fabien Potencier + */ +class TraceableArgumentResolver implements ArgumentResolverInterface +{ + private $resolver; + private $stopwatch; + + public function __construct(ArgumentResolverInterface $resolver, Stopwatch $stopwatch) + { + $this->resolver = $resolver; + $this->stopwatch = $stopwatch; + } + + /** + * {@inheritdoc} + */ + public function getArguments(Request $request, $controller) + { + $e = $this->stopwatch->start('controller.get_arguments'); + + $ret = $this->resolver->getArguments($request, $controller); + + $e->stop(); + + return $ret; + } +} diff --git a/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php b/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php new file mode 100644 index 00000000..ce291b1e --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\HttpFoundation\Request; + +/** + * TraceableControllerResolver. + * + * @author Fabien Potencier + */ +class TraceableControllerResolver implements ControllerResolverInterface, ArgumentResolverInterface +{ + private $resolver; + private $stopwatch; + private $argumentResolver; + + /** + * Constructor. + * + * @param ControllerResolverInterface $resolver A ControllerResolverInterface instance + * @param Stopwatch $stopwatch A Stopwatch instance + * @param ArgumentResolverInterface $argumentResolver Only required for BC + */ + public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch, ArgumentResolverInterface $argumentResolver = null) + { + $this->resolver = $resolver; + $this->stopwatch = $stopwatch; + $this->argumentResolver = $argumentResolver; + + // BC + if (null === $this->argumentResolver) { + $this->argumentResolver = $resolver; + } + + if (!$this->argumentResolver instanceof TraceableArgumentResolver) { + $this->argumentResolver = new TraceableArgumentResolver($this->argumentResolver, $this->stopwatch); + } + } + + /** + * {@inheritdoc} + */ + public function getController(Request $request) + { + $e = $this->stopwatch->start('controller.get_callable'); + + $ret = $this->resolver->getController($request); + + $e->stop(); + + return $ret; + } + + /** + * {@inheritdoc} + * + * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. + */ + public function getArguments(Request $request, $controller) + { + @trigger_error(sprintf('The %s method is deprecated as of 3.1 and will be removed in 4.0. Please use the %s instead.', __METHOD__, TraceableArgumentResolver::class), E_USER_DEPRECATED); + + $ret = $this->argumentResolver->getArguments($request, $controller); + + return $ret; + } +} diff --git a/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php new file mode 100644 index 00000000..32316a8d --- /dev/null +++ b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\ControllerMetadata; + +/** + * Responsible for storing metadata of an argument. + * + * @author Iltar van der Berg + */ +class ArgumentMetadata +{ + private $name; + private $type; + private $isVariadic; + private $hasDefaultValue; + private $defaultValue; + private $isNullable; + + /** + * @param string $name + * @param string $type + * @param bool $isVariadic + * @param bool $hasDefaultValue + * @param mixed $defaultValue + * @param bool $isNullable + */ + public function __construct($name, $type, $isVariadic, $hasDefaultValue, $defaultValue, $isNullable = false) + { + $this->name = $name; + $this->type = $type; + $this->isVariadic = $isVariadic; + $this->hasDefaultValue = $hasDefaultValue; + $this->defaultValue = $defaultValue; + $this->isNullable = $isNullable || null === $type || ($hasDefaultValue && null === $defaultValue); + } + + /** + * Returns the name as given in PHP, $foo would yield "foo". + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Returns the type of the argument. + * + * The type is the PHP class in 5.5+ and additionally the basic type in PHP 7.0+. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Returns whether the argument is defined as "...$variadic". + * + * @return bool + */ + public function isVariadic() + { + return $this->isVariadic; + } + + /** + * Returns whether the argument has a default value. + * + * Implies whether an argument is optional. + * + * @return bool + */ + public function hasDefaultValue() + { + return $this->hasDefaultValue; + } + + /** + * Returns whether the argument accepts null values. + * + * @return bool + */ + public function isNullable() + { + return $this->isNullable; + } + + /** + * Returns the default value of the argument. + * + * @throws \LogicException if no default value is present; {@see self::hasDefaultValue()} + * + * @return mixed + */ + public function getDefaultValue() + { + if (!$this->hasDefaultValue) { + throw new \LogicException(sprintf('Argument $%s does not have a default value. Use %s::hasDefaultValue() to avoid this exception.', $this->name, __CLASS__)); + } + + return $this->defaultValue; + } +} diff --git a/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php new file mode 100644 index 00000000..d1e7af20 --- /dev/null +++ b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\ControllerMetadata; + +/** + * Builds {@see ArgumentMetadata} objects based on the given Controller. + * + * @author Iltar van der Berg + */ +final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface +{ + /** + * If the ...$arg functionality is available. + * + * Requires at least PHP 5.6.0 or HHVM 3.9.1 + * + * @var bool + */ + private $supportsVariadic; + + /** + * If the reflection supports the getType() method to resolve types. + * + * Requires at least PHP 7.0.0 or HHVM 3.11.0 + * + * @var bool + */ + private $supportsParameterType; + + public function __construct() + { + $this->supportsVariadic = method_exists('ReflectionParameter', 'isVariadic'); + $this->supportsParameterType = method_exists('ReflectionParameter', 'getType'); + } + + /** + * {@inheritdoc} + */ + public function createArgumentMetadata($controller) + { + $arguments = array(); + + if (is_array($controller)) { + $reflection = new \ReflectionMethod($controller[0], $controller[1]); + } elseif (is_object($controller) && !$controller instanceof \Closure) { + $reflection = (new \ReflectionObject($controller))->getMethod('__invoke'); + } else { + $reflection = new \ReflectionFunction($controller); + } + + foreach ($reflection->getParameters() as $param) { + $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param), $this->isVariadic($param), $this->hasDefaultValue($param), $this->getDefaultValue($param), $param->allowsNull()); + } + + return $arguments; + } + + /** + * Returns whether an argument is variadic. + * + * @param \ReflectionParameter $parameter + * + * @return bool + */ + private function isVariadic(\ReflectionParameter $parameter) + { + return $this->supportsVariadic && $parameter->isVariadic(); + } + + /** + * Determines whether an argument has a default value. + * + * @param \ReflectionParameter $parameter + * + * @return bool + */ + private function hasDefaultValue(\ReflectionParameter $parameter) + { + return $parameter->isDefaultValueAvailable(); + } + + /** + * Returns a default value if available. + * + * @param \ReflectionParameter $parameter + * + * @return mixed|null + */ + private function getDefaultValue(\ReflectionParameter $parameter) + { + return $this->hasDefaultValue($parameter) ? $parameter->getDefaultValue() : null; + } + + /** + * Returns an associated type to the given parameter if available. + * + * @param \ReflectionParameter $parameter + * + * @return null|string + */ + private function getType(\ReflectionParameter $parameter) + { + if ($this->supportsParameterType) { + if (!$type = $parameter->getType()) { + return; + } + $typeName = $type instanceof \ReflectionNamedType ? $type->getName() : $type->__toString(); + if ('array' === $typeName && !$type->isBuiltin()) { + // Special case for HHVM with variadics + return; + } + + return $typeName; + } + + if (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $parameter, $info)) { + return $info[1]; + } + } +} diff --git a/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php new file mode 100644 index 00000000..6ea179d7 --- /dev/null +++ b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\ControllerMetadata; + +/** + * Builds method argument data. + * + * @author Iltar van der Berg + */ +interface ArgumentMetadataFactoryInterface +{ + /** + * @param mixed $controller The controller to resolve the arguments for + * + * @return ArgumentMetadata[] + */ + public function createArgumentMetadata($controller); +} diff --git a/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php b/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php new file mode 100644 index 00000000..b8405d59 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * AjaxDataCollector. + * + * @author Bart van den Burg + */ +class AjaxDataCollector extends DataCollector +{ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // all collecting is done client side + } + + public function getName() + { + return 'ajax'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php b/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php new file mode 100644 index 00000000..87c4c08e --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php @@ -0,0 +1,330 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\VarDumper\Caster\LinkStub; + +/** + * ConfigDataCollector. + * + * @author Fabien Potencier + */ +class ConfigDataCollector extends DataCollector implements LateDataCollectorInterface +{ + /** + * @var KernelInterface + */ + private $kernel; + private $name; + private $version; + private $hasVarDumper; + + /** + * Constructor. + * + * @param string $name The name of the application using the web profiler + * @param string $version The version of the application using the web profiler + */ + public function __construct($name = null, $version = null) + { + $this->name = $name; + $this->version = $version; + $this->hasVarDumper = class_exists(LinkStub::class); + } + + /** + * Sets the Kernel associated with this Request. + * + * @param KernelInterface $kernel A KernelInterface instance + */ + public function setKernel(KernelInterface $kernel = null) + { + $this->kernel = $kernel; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->data = array( + 'app_name' => $this->name, + 'app_version' => $this->version, + 'token' => $response->headers->get('X-Debug-Token'), + 'symfony_version' => Kernel::VERSION, + 'symfony_state' => 'unknown', + 'name' => isset($this->kernel) ? $this->kernel->getName() : 'n/a', + 'env' => isset($this->kernel) ? $this->kernel->getEnvironment() : 'n/a', + 'debug' => isset($this->kernel) ? $this->kernel->isDebug() : 'n/a', + 'php_version' => PHP_VERSION, + 'php_architecture' => PHP_INT_SIZE * 8, + 'php_intl_locale' => class_exists('Locale', false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a', + 'php_timezone' => date_default_timezone_get(), + 'xdebug_enabled' => extension_loaded('xdebug'), + 'apcu_enabled' => extension_loaded('apcu') && ini_get('apc.enabled'), + 'zend_opcache_enabled' => extension_loaded('Zend OPcache') && ini_get('opcache.enable'), + 'bundles' => array(), + 'sapi_name' => PHP_SAPI, + ); + + if (isset($this->kernel)) { + foreach ($this->kernel->getBundles() as $name => $bundle) { + $this->data['bundles'][$name] = $this->hasVarDumper ? new LinkStub($bundle->getPath()) : $bundle->getPath(); + } + + $this->data['symfony_state'] = $this->determineSymfonyState(); + $this->data['symfony_minor_version'] = sprintf('%s.%s', Kernel::MAJOR_VERSION, Kernel::MINOR_VERSION); + $eom = \DateTime::createFromFormat('m/Y', Kernel::END_OF_MAINTENANCE); + $eol = \DateTime::createFromFormat('m/Y', Kernel::END_OF_LIFE); + $this->data['symfony_eom'] = $eom->format('F Y'); + $this->data['symfony_eol'] = $eol->format('F Y'); + } + + if (preg_match('~^(\d+(?:\.\d+)*)(.+)?$~', $this->data['php_version'], $matches) && isset($matches[2])) { + $this->data['php_version'] = $matches[1]; + $this->data['php_version_extra'] = $matches[2]; + } + } + + public function lateCollect() + { + $this->data = $this->cloneVar($this->data); + } + + public function getApplicationName() + { + return $this->data['app_name']; + } + + public function getApplicationVersion() + { + return $this->data['app_version']; + } + + /** + * Gets the token. + * + * @return string The token + */ + public function getToken() + { + return $this->data['token']; + } + + /** + * Gets the Symfony version. + * + * @return string The Symfony version + */ + public function getSymfonyVersion() + { + return $this->data['symfony_version']; + } + + /** + * Returns the state of the current Symfony release. + * + * @return string One of: unknown, dev, stable, eom, eol + */ + public function getSymfonyState() + { + return $this->data['symfony_state']; + } + + /** + * Returns the minor Symfony version used (without patch numbers of extra + * suffix like "RC", "beta", etc.). + * + * @return string + */ + public function getSymfonyMinorVersion() + { + return $this->data['symfony_minor_version']; + } + + /** + * Returns the human redable date when this Symfony version ends its + * maintenance period. + * + * @return string + */ + public function getSymfonyEom() + { + return $this->data['symfony_eom']; + } + + /** + * Returns the human redable date when this Symfony version reaches its + * "end of life" and won't receive bugs or security fixes. + * + * @return string + */ + public function getSymfonyEol() + { + return $this->data['symfony_eol']; + } + + /** + * Gets the PHP version. + * + * @return string The PHP version + */ + public function getPhpVersion() + { + return $this->data['php_version']; + } + + /** + * Gets the PHP version extra part. + * + * @return string|null The extra part + */ + public function getPhpVersionExtra() + { + return isset($this->data['php_version_extra']) ? $this->data['php_version_extra'] : null; + } + + /** + * @return int The PHP architecture as number of bits (e.g. 32 or 64) + */ + public function getPhpArchitecture() + { + return $this->data['php_architecture']; + } + + /** + * @return string + */ + public function getPhpIntlLocale() + { + return $this->data['php_intl_locale']; + } + + /** + * @return string + */ + public function getPhpTimezone() + { + return $this->data['php_timezone']; + } + + /** + * Gets the application name. + * + * @return string The application name + */ + public function getAppName() + { + return $this->data['name']; + } + + /** + * Gets the environment. + * + * @return string The environment + */ + public function getEnv() + { + return $this->data['env']; + } + + /** + * Returns true if the debug is enabled. + * + * @return bool true if debug is enabled, false otherwise + */ + public function isDebug() + { + return $this->data['debug']; + } + + /** + * Returns true if the XDebug is enabled. + * + * @return bool true if XDebug is enabled, false otherwise + */ + public function hasXDebug() + { + return $this->data['xdebug_enabled']; + } + + /** + * Returns true if APCu is enabled. + * + * @return bool true if APCu is enabled, false otherwise + */ + public function hasApcu() + { + return $this->data['apcu_enabled']; + } + + /** + * Returns true if Zend OPcache is enabled. + * + * @return bool true if Zend OPcache is enabled, false otherwise + */ + public function hasZendOpcache() + { + return $this->data['zend_opcache_enabled']; + } + + public function getBundles() + { + return $this->data['bundles']; + } + + /** + * Gets the PHP SAPI name. + * + * @return string The environment + */ + public function getSapiName() + { + return $this->data['sapi_name']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'config'; + } + + /** + * Tries to retrieve information about the current Symfony version. + * + * @return string One of: dev, stable, eom, eol + */ + private function determineSymfonyState() + { + $now = new \DateTime(); + $eom = \DateTime::createFromFormat('m/Y', Kernel::END_OF_MAINTENANCE)->modify('last day of this month'); + $eol = \DateTime::createFromFormat('m/Y', Kernel::END_OF_LIFE)->modify('last day of this month'); + + if ($now > $eol) { + $versionState = 'eol'; + } elseif ($now > $eom) { + $versionState = 'eom'; + } elseif ('' !== Kernel::EXTRA_VERSION) { + $versionState = 'dev'; + } else { + $versionState = 'stable'; + } + + return $versionState; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/DataCollector.php b/vendor/symfony/http-kernel/DataCollector/DataCollector.php new file mode 100644 index 00000000..845c2c13 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DataCollector.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; +use Symfony\Component\VarDumper\Caster\CutStub; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +/** + * DataCollector. + * + * Children of this class must store the collected data in the data property. + * + * @author Fabien Potencier + * @author Bernhard Schussek + */ +abstract class DataCollector implements DataCollectorInterface, \Serializable +{ + protected $data = array(); + + /** + * @var ValueExporter + */ + private $valueExporter; + + /** + * @var ClonerInterface + */ + private $cloner; + + public function serialize() + { + return serialize($this->data); + } + + public function unserialize($data) + { + $this->data = unserialize($data); + } + + /** + * Converts the variable into a serializable Data instance. + * + * This array can be displayed in the template using + * the VarDumper component. + * + * @param mixed $var + * + * @return Data + */ + protected function cloneVar($var) + { + if ($var instanceof Data) { + return $var; + } + if (null === $this->cloner) { + if (class_exists(CutStub::class)) { + $this->cloner = new VarCloner(); + $this->cloner->setMaxItems(-1); + $this->cloner->addCasters($this->getCasters()); + } else { + @trigger_error(sprintf('Using the %s() method without the VarDumper component is deprecated since version 3.2 and won\'t be supported in 4.0. Install symfony/var-dumper version 3.2 or above.', __METHOD__), E_USER_DEPRECATED); + $this->cloner = false; + } + } + if (false === $this->cloner) { + if (null === $this->valueExporter) { + $this->valueExporter = new ValueExporter(); + } + + return $this->valueExporter->exportValue($var); + } + + return $this->cloner->cloneVar($var); + } + + /** + * Converts a PHP variable to a string. + * + * @param mixed $var A PHP variable + * + * @return string The string representation of the variable + * + * @deprecated since version 3.2, to be removed in 4.0. Use cloneVar() instead. + */ + protected function varToString($var) + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.2 and will be removed in 4.0. Use cloneVar() instead.', __METHOD__), E_USER_DEPRECATED); + + if (null === $this->valueExporter) { + $this->valueExporter = new ValueExporter(); + } + + return $this->valueExporter->exportValue($var); + } + + /** + * @return callable[] The casters to add to the cloner + */ + protected function getCasters() + { + return array( + '*' => function ($v, array $a, Stub $s, $isNested) { + if (!$v instanceof Stub) { + foreach ($a as $k => $v) { + if (is_object($v) && !$v instanceof \DateTimeInterface && !$v instanceof Stub) { + $a[$k] = new CutStub($v); + } + } + } + + return $a; + }, + ); + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php b/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php new file mode 100644 index 00000000..2820ad5b --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * DataCollectorInterface. + * + * @author Fabien Potencier + */ +interface DataCollectorInterface +{ + /** + * Collects data for the given Request and Response. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * @param \Exception $exception An Exception instance + */ + public function collect(Request $request, Response $response, \Exception $exception = null); + + /** + * Returns the name of the collector. + * + * @return string The collector name + */ + public function getName(); +} diff --git a/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php b/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php new file mode 100644 index 00000000..b50386cb --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php @@ -0,0 +1,303 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Dumper\DataDumperInterface; +use Twig\Template; + +/** + * @author Nicolas Grekas + */ +class DumpDataCollector extends DataCollector implements DataDumperInterface +{ + private $stopwatch; + private $fileLinkFormat; + private $dataCount = 0; + private $isCollected = true; + private $clonesCount = 0; + private $clonesIndex = 0; + private $rootRefs; + private $charset; + private $requestStack; + private $dumper; + private $dumperIsInjected; + + public function __construct(Stopwatch $stopwatch = null, $fileLinkFormat = null, $charset = null, RequestStack $requestStack = null, DataDumperInterface $dumper = null) + { + $this->stopwatch = $stopwatch; + $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + $this->charset = $charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8'; + $this->requestStack = $requestStack; + $this->dumper = $dumper; + $this->dumperIsInjected = null !== $dumper; + + // All clones share these properties by reference: + $this->rootRefs = array( + &$this->data, + &$this->dataCount, + &$this->isCollected, + &$this->clonesCount, + ); + } + + public function __clone() + { + $this->clonesIndex = ++$this->clonesCount; + } + + public function dump(Data $data) + { + if ($this->stopwatch) { + $this->stopwatch->start('dump'); + } + if ($this->isCollected) { + $this->isCollected = false; + } + + $trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 7); + + $file = $trace[0]['file']; + $line = $trace[0]['line']; + $name = false; + $fileExcerpt = false; + + for ($i = 1; $i < 7; ++$i) { + if (isset($trace[$i]['class'], $trace[$i]['function']) + && 'dump' === $trace[$i]['function'] + && 'Symfony\Component\VarDumper\VarDumper' === $trace[$i]['class'] + ) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + while (++$i < 7) { + if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && 0 !== strpos($trace[$i]['function'], 'call_user_func')) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + break; + } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) { + $template = $trace[$i]['object']; + $name = $template->getTemplateName(); + $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false); + $info = $template->getDebugInfo(); + if (isset($info[$trace[$i - 1]['line']])) { + $line = $info[$trace[$i - 1]['line']]; + $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null; + + if ($src) { + $src = explode("\n", $src); + $fileExcerpt = array(); + + for ($i = max($line - 3, 1), $max = min($line + 3, count($src)); $i <= $max; ++$i) { + $fileExcerpt[] = ''.$this->htmlEncode($src[$i - 1]).''; + } + + $fileExcerpt = '
    '.implode("\n", $fileExcerpt).'
'; + } + } + break; + } + } + break; + } + } + + if (false === $name) { + $name = str_replace('\\', '/', $file); + $name = substr($name, strrpos($name, '/') + 1); + } + + if ($this->dumper) { + $this->doDump($data, $name, $file, $line); + } + + $this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt'); + ++$this->dataCount; + + if ($this->stopwatch) { + $this->stopwatch->stop('dump'); + } + } + + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // Sub-requests and programmatic calls stay in the collected profile. + if ($this->dumper || ($this->requestStack && $this->requestStack->getMasterRequest() !== $request) || $request->isXmlHttpRequest() || $request->headers->has('Origin')) { + return; + } + + // In all other conditions that remove the web debug toolbar, dumps are written on the output. + if (!$this->requestStack + || !$response->headers->has('X-Debug-Token') + || $response->isRedirection() + || ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html')) + || 'html' !== $request->getRequestFormat() + || false === strripos($response->getContent(), '') + ) { + if ($response->headers->has('Content-Type') && false !== strpos($response->headers->get('Content-Type'), 'html')) { + $this->dumper = new HtmlDumper('php://output', $this->charset); + $this->dumper->setDisplayOptions(array('fileLinkFormat' => $this->fileLinkFormat)); + } else { + $this->dumper = new CliDumper('php://output', $this->charset); + } + + foreach ($this->data as $dump) { + $this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']); + } + } + } + + public function serialize() + { + if ($this->clonesCount !== $this->clonesIndex) { + return 'a:0:{}'; + } + + $this->data[] = $this->fileLinkFormat; + $this->data[] = $this->charset; + $ser = serialize($this->data); + $this->data = array(); + $this->dataCount = 0; + $this->isCollected = true; + if (!$this->dumperIsInjected) { + $this->dumper = null; + } + + return $ser; + } + + public function unserialize($data) + { + parent::unserialize($data); + $charset = array_pop($this->data); + $fileLinkFormat = array_pop($this->data); + $this->dataCount = count($this->data); + self::__construct($this->stopwatch, $fileLinkFormat, $charset); + } + + public function getDumpsCount() + { + return $this->dataCount; + } + + public function getDumps($format, $maxDepthLimit = -1, $maxItemsPerDepth = -1) + { + $data = fopen('php://memory', 'r+b'); + + if ('html' === $format) { + $dumper = new HtmlDumper($data, $this->charset); + $dumper->setDisplayOptions(array('fileLinkFormat' => $this->fileLinkFormat)); + } else { + throw new \InvalidArgumentException(sprintf('Invalid dump format: %s', $format)); + } + $dumps = array(); + + foreach ($this->data as $dump) { + $dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth)); + $dump['data'] = stream_get_contents($data, -1, 0); + ftruncate($data, 0); + rewind($data); + $dumps[] = $dump; + } + + return $dumps; + } + + public function getName() + { + return 'dump'; + } + + public function __destruct() + { + if (0 === $this->clonesCount-- && !$this->isCollected && $this->data) { + $this->clonesCount = 0; + $this->isCollected = true; + + $h = headers_list(); + $i = count($h); + array_unshift($h, 'Content-Type: '.ini_get('default_mimetype')); + while (0 !== stripos($h[$i], 'Content-Type:')) { + --$i; + } + + if ('cli' !== PHP_SAPI && stripos($h[$i], 'html')) { + $this->dumper = new HtmlDumper('php://output', $this->charset); + $this->dumper->setDisplayOptions(array('fileLinkFormat' => $this->fileLinkFormat)); + } else { + $this->dumper = new CliDumper('php://output', $this->charset); + } + + foreach ($this->data as $i => $dump) { + $this->data[$i] = null; + $this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']); + } + + $this->data = array(); + $this->dataCount = 0; + } + } + + private function doDump($data, $name, $file, $line) + { + if ($this->dumper instanceof CliDumper) { + $contextDumper = function ($name, $file, $line, $fmt) { + if ($this instanceof HtmlDumper) { + if ($file) { + $s = $this->style('meta', '%s'); + $f = strip_tags($this->style('', $file)); + $name = strip_tags($this->style('', $name)); + if ($fmt && $link = is_string($fmt) ? strtr($fmt, array('%f' => $file, '%l' => $line)) : $fmt->format($file, $line)) { + $name = sprintf(''.$s.'', strip_tags($this->style('', $link)), $f, $name); + } else { + $name = sprintf(''.$s.'', $f, $name); + } + } else { + $name = $this->style('meta', $name); + } + $this->line = $name.' on line '.$this->style('meta', $line).':'; + } else { + $this->line = $this->style('meta', $name).' on line '.$this->style('meta', $line).':'; + } + $this->dumpLine(0); + }; + $contextDumper = $contextDumper->bindTo($this->dumper, $this->dumper); + $contextDumper($name, $file, $line, $this->fileLinkFormat); + } else { + $cloner = new VarCloner(); + $this->dumper->dump($cloner->cloneVar($name.' on line '.$line.':')); + } + $this->dumper->dump($data); + } + + private function htmlEncode($s) + { + $html = ''; + + $dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + + $cloner = new VarCloner(); + $dumper->dump($cloner->cloneVar($s)); + + return substr(strip_tags($html), 1, -1); + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/EventDataCollector.php b/vendor/symfony/http-kernel/DataCollector/EventDataCollector.php new file mode 100644 index 00000000..3d75f322 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/EventDataCollector.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface; + +/** + * EventDataCollector. + * + * @author Fabien Potencier + */ +class EventDataCollector extends DataCollector implements LateDataCollectorInterface +{ + protected $dispatcher; + + public function __construct(EventDispatcherInterface $dispatcher = null) + { + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->data = array( + 'called_listeners' => array(), + 'not_called_listeners' => array(), + ); + } + + public function lateCollect() + { + if ($this->dispatcher instanceof TraceableEventDispatcherInterface) { + $this->setCalledListeners($this->dispatcher->getCalledListeners()); + $this->setNotCalledListeners($this->dispatcher->getNotCalledListeners()); + } + $this->data = $this->cloneVar($this->data); + } + + /** + * Sets the called listeners. + * + * @param array $listeners An array of called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function setCalledListeners(array $listeners) + { + $this->data['called_listeners'] = $listeners; + } + + /** + * Gets the called listeners. + * + * @return array An array of called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function getCalledListeners() + { + return $this->data['called_listeners']; + } + + /** + * Sets the not called listeners. + * + * @param array $listeners An array of not called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function setNotCalledListeners(array $listeners) + { + $this->data['not_called_listeners'] = $listeners; + } + + /** + * Gets the not called listeners. + * + * @return array An array of not called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function getNotCalledListeners() + { + return $this->data['not_called_listeners']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'events'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php b/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php new file mode 100644 index 00000000..9fe82644 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * ExceptionDataCollector. + * + * @author Fabien Potencier + */ +class ExceptionDataCollector extends DataCollector +{ + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if (null !== $exception) { + $this->data = array( + 'exception' => FlattenException::create($exception), + ); + } + } + + /** + * Checks if the exception is not null. + * + * @return bool true if the exception is not null, false otherwise + */ + public function hasException() + { + return isset($this->data['exception']); + } + + /** + * Gets the exception. + * + * @return \Exception The exception + */ + public function getException() + { + return $this->data['exception']; + } + + /** + * Gets the exception message. + * + * @return string The exception message + */ + public function getMessage() + { + return $this->data['exception']->getMessage(); + } + + /** + * Gets the exception code. + * + * @return int The exception code + */ + public function getCode() + { + return $this->data['exception']->getCode(); + } + + /** + * Gets the status code. + * + * @return int The status code + */ + public function getStatusCode() + { + return $this->data['exception']->getStatusCode(); + } + + /** + * Gets the exception trace. + * + * @return array The exception trace + */ + public function getTrace() + { + return $this->data['exception']->getTrace(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'exception'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php b/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php new file mode 100644 index 00000000..012332de --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +/** + * LateDataCollectorInterface. + * + * @author Fabien Potencier + */ +interface LateDataCollectorInterface +{ + /** + * Collects data as late as possible. + */ + public function lateCollect(); +} diff --git a/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php b/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php new file mode 100644 index 00000000..92fd0da7 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php @@ -0,0 +1,262 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\Debug\Exception\SilencedErrorContext; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; + +/** + * LogDataCollector. + * + * @author Fabien Potencier + */ +class LoggerDataCollector extends DataCollector implements LateDataCollectorInterface +{ + private $logger; + private $containerPathPrefix; + + public function __construct($logger = null, $containerPathPrefix = null) + { + if (null !== $logger && $logger instanceof DebugLoggerInterface) { + $this->logger = $logger; + } + + $this->containerPathPrefix = $containerPathPrefix; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // everything is done as late as possible + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + if (null !== $this->logger) { + $containerDeprecationLogs = $this->getContainerDeprecationLogs(); + $this->data = $this->computeErrorsCount($containerDeprecationLogs); + $this->data['compiler_logs'] = $this->getContainerCompilerLogs(); + $this->data['logs'] = $this->sanitizeLogs(array_merge($this->logger->getLogs(), $containerDeprecationLogs)); + $this->data = $this->cloneVar($this->data); + } + } + + /** + * Gets the logs. + * + * @return array An array of logs + */ + public function getLogs() + { + return isset($this->data['logs']) ? $this->data['logs'] : array(); + } + + public function getPriorities() + { + return isset($this->data['priorities']) ? $this->data['priorities'] : array(); + } + + public function countErrors() + { + return isset($this->data['error_count']) ? $this->data['error_count'] : 0; + } + + public function countDeprecations() + { + return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0; + } + + public function countWarnings() + { + return isset($this->data['warning_count']) ? $this->data['warning_count'] : 0; + } + + public function countScreams() + { + return isset($this->data['scream_count']) ? $this->data['scream_count'] : 0; + } + + public function getCompilerLogs() + { + return isset($this->data['compiler_logs']) ? $this->data['compiler_logs'] : array(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'logger'; + } + + private function getContainerDeprecationLogs() + { + if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Deprecations.log')) { + return array(); + } + + $bootTime = filemtime($file); + $logs = array(); + foreach (unserialize(file_get_contents($file)) as $log) { + $log['context'] = array('exception' => new SilencedErrorContext($log['type'], $log['file'], $log['line'], $log['trace'], $log['count'])); + $log['timestamp'] = $bootTime; + $log['priority'] = 100; + $log['priorityName'] = 'DEBUG'; + $log['channel'] = '-'; + $log['scream'] = false; + unset($log['type'], $log['file'], $log['line'], $log['trace'], $log['trace'], $log['count']); + $logs[] = $log; + } + + return $logs; + } + + private function getContainerCompilerLogs() + { + if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Compiler.log')) { + return array(); + } + + $logs = array(); + foreach (file($file, FILE_IGNORE_NEW_LINES) as $log) { + $log = explode(': ', $log, 2); + if (!isset($log[1]) || !preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)++$/', $log[0])) { + $log = array('Unknown Compiler Pass', implode(': ', $log)); + } + + $logs[$log[0]][] = array('message' => $log[1]); + } + + return $logs; + } + + private function sanitizeLogs($logs) + { + $sanitizedLogs = array(); + + foreach ($logs as $log) { + if (!$this->isSilencedOrDeprecationErrorLog($log)) { + $sanitizedLogs[] = $log; + + continue; + } + + $message = $log['message']; + $exception = $log['context']['exception']; + + if ($exception instanceof SilencedErrorContext) { + if (isset($silencedLogs[$h = spl_object_hash($exception)])) { + continue; + } + $silencedLogs[$h] = true; + + if (!isset($sanitizedLogs[$message])) { + $sanitizedLogs[$message] = $log + array( + 'errorCount' => 0, + 'scream' => true, + ); + } + $sanitizedLogs[$message]['errorCount'] += $exception->count; + + continue; + } + + $errorId = md5("{$exception->getSeverity()}/{$exception->getLine()}/{$exception->getFile()}\0{$message}", true); + + if (isset($sanitizedLogs[$errorId])) { + ++$sanitizedLogs[$errorId]['errorCount']; + } else { + $log += array( + 'errorCount' => 1, + 'scream' => false, + ); + + $sanitizedLogs[$errorId] = $log; + } + } + + return array_values($sanitizedLogs); + } + + private function isSilencedOrDeprecationErrorLog(array $log) + { + if (!isset($log['context']['exception'])) { + return false; + } + + $exception = $log['context']['exception']; + + if ($exception instanceof SilencedErrorContext) { + return true; + } + + if ($exception instanceof \ErrorException && in_array($exception->getSeverity(), array(E_DEPRECATED, E_USER_DEPRECATED), true)) { + return true; + } + + return false; + } + + private function computeErrorsCount(array $containerDeprecationLogs) + { + $silencedLogs = array(); + $count = array( + 'error_count' => $this->logger->countErrors(), + 'deprecation_count' => 0, + 'warning_count' => 0, + 'scream_count' => 0, + 'priorities' => array(), + ); + + foreach ($this->logger->getLogs() as $log) { + if (isset($count['priorities'][$log['priority']])) { + ++$count['priorities'][$log['priority']]['count']; + } else { + $count['priorities'][$log['priority']] = array( + 'count' => 1, + 'name' => $log['priorityName'], + ); + } + if ('WARNING' === $log['priorityName']) { + ++$count['warning_count']; + } + + if ($this->isSilencedOrDeprecationErrorLog($log)) { + $exception = $log['context']['exception']; + if ($exception instanceof SilencedErrorContext) { + if (isset($silencedLogs[$h = spl_object_hash($exception)])) { + continue; + } + $silencedLogs[$h] = true; + $count['scream_count'] += $exception->count; + } else { + ++$count['deprecation_count']; + } + } + } + + foreach ($containerDeprecationLogs as $deprecationLog) { + $count['deprecation_count'] += $deprecationLog['context']['exception']->count; + } + + ksort($count['priorities']); + + return $count; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php b/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php new file mode 100644 index 00000000..93850108 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * MemoryDataCollector. + * + * @author Fabien Potencier + */ +class MemoryDataCollector extends DataCollector implements LateDataCollectorInterface +{ + public function __construct() + { + $this->data = array( + 'memory' => 0, + 'memory_limit' => $this->convertToBytes(ini_get('memory_limit')), + ); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->updateMemoryUsage(); + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + $this->updateMemoryUsage(); + } + + /** + * Gets the memory. + * + * @return int The memory + */ + public function getMemory() + { + return $this->data['memory']; + } + + /** + * Gets the PHP memory limit. + * + * @return int The memory limit + */ + public function getMemoryLimit() + { + return $this->data['memory_limit']; + } + + /** + * Updates the memory usage data. + */ + public function updateMemoryUsage() + { + $this->data['memory'] = memory_get_peak_usage(true); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'memory'; + } + + private function convertToBytes($memoryLimit) + { + if ('-1' === $memoryLimit) { + return -1; + } + + $memoryLimit = strtolower($memoryLimit); + $max = strtolower(ltrim($memoryLimit, '+')); + if (0 === strpos($max, '0x')) { + $max = intval($max, 16); + } elseif (0 === strpos($max, '0')) { + $max = intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($memoryLimit, -1)) { + case 't': $max *= 1024; + case 'g': $max *= 1024; + case 'm': $max *= 1024; + case 'k': $max *= 1024; + } + + return $max; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php b/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php new file mode 100644 index 00000000..7049844f --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php @@ -0,0 +1,397 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * RequestDataCollector. + * + * @author Fabien Potencier + */ +class RequestDataCollector extends DataCollector implements EventSubscriberInterface, LateDataCollectorInterface +{ + /** @var \SplObjectStorage */ + protected $controllers; + + public function __construct() + { + $this->controllers = new \SplObjectStorage(); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // attributes are serialized and as they can be anything, they need to be converted to strings. + $attributes = array(); + $route = ''; + foreach ($request->attributes->all() as $key => $value) { + if ('_route' === $key) { + $route = is_object($value) ? $value->getPath() : $value; + $attributes[$key] = $route; + } else { + $attributes[$key] = $value; + } + } + + $content = null; + try { + $content = $request->getContent(); + } catch (\LogicException $e) { + // the user already got the request content as a resource + $content = false; + } + + $sessionMetadata = array(); + $sessionAttributes = array(); + $session = null; + $flashes = array(); + if ($request->hasSession()) { + $session = $request->getSession(); + if ($session->isStarted()) { + $sessionMetadata['Created'] = date(DATE_RFC822, $session->getMetadataBag()->getCreated()); + $sessionMetadata['Last used'] = date(DATE_RFC822, $session->getMetadataBag()->getLastUsed()); + $sessionMetadata['Lifetime'] = $session->getMetadataBag()->getLifetime(); + $sessionAttributes = $session->all(); + $flashes = $session->getFlashBag()->peekAll(); + } + } + + $statusCode = $response->getStatusCode(); + + $responseCookies = array(); + foreach ($response->headers->getCookies() as $cookie) { + $responseCookies[$cookie->getName()] = $cookie; + } + + $this->data = array( + 'method' => $request->getMethod(), + 'format' => $request->getRequestFormat(), + 'content' => $content, + 'content_type' => $response->headers->get('Content-Type', 'text/html'), + 'status_text' => isset(Response::$statusTexts[$statusCode]) ? Response::$statusTexts[$statusCode] : '', + 'status_code' => $statusCode, + 'request_query' => $request->query->all(), + 'request_request' => $request->request->all(), + 'request_headers' => $request->headers->all(), + 'request_server' => $request->server->all(), + 'request_cookies' => $request->cookies->all(), + 'request_attributes' => $attributes, + 'route' => $route, + 'response_headers' => $response->headers->all(), + 'response_cookies' => $responseCookies, + 'session_metadata' => $sessionMetadata, + 'session_attributes' => $sessionAttributes, + 'flashes' => $flashes, + 'path_info' => $request->getPathInfo(), + 'controller' => 'n/a', + 'locale' => $request->getLocale(), + ); + + if (isset($this->data['request_headers']['php-auth-pw'])) { + $this->data['request_headers']['php-auth-pw'] = '******'; + } + + if (isset($this->data['request_server']['PHP_AUTH_PW'])) { + $this->data['request_server']['PHP_AUTH_PW'] = '******'; + } + + if (isset($this->data['request_request']['_password'])) { + $this->data['request_request']['_password'] = '******'; + } + + foreach ($this->data as $key => $value) { + if (!is_array($value)) { + continue; + } + if ('request_headers' === $key || 'response_headers' === $key) { + $this->data[$key] = array_map(function ($v) { return isset($v[0]) && !isset($v[1]) ? $v[0] : $v; }, $value); + } + } + + if (isset($this->controllers[$request])) { + $this->data['controller'] = $this->parseController($this->controllers[$request]); + unset($this->controllers[$request]); + } + + if (null !== $session && $session->isStarted()) { + if ($request->attributes->has('_redirected')) { + $this->data['redirect'] = $session->remove('sf_redirect'); + } + + if ($response->isRedirect()) { + $session->set('sf_redirect', array( + 'token' => $response->headers->get('x-debug-token'), + 'route' => $request->attributes->get('_route', 'n/a'), + 'method' => $request->getMethod(), + 'controller' => $this->parseController($request->attributes->get('_controller')), + 'status_code' => $statusCode, + 'status_text' => Response::$statusTexts[(int) $statusCode], + )); + } + } + + $this->data['identifier'] = $this->data['route'] ?: (is_array($this->data['controller']) ? $this->data['controller']['class'].'::'.$this->data['controller']['method'].'()' : $this->data['controller']); + } + + public function lateCollect() + { + $this->data = $this->cloneVar($this->data); + } + + public function getMethod() + { + return $this->data['method']; + } + + public function getPathInfo() + { + return $this->data['path_info']; + } + + public function getRequestRequest() + { + return new ParameterBag($this->data['request_request']->getValue()); + } + + public function getRequestQuery() + { + return new ParameterBag($this->data['request_query']->getValue()); + } + + public function getRequestHeaders() + { + return new ParameterBag($this->data['request_headers']->getValue()); + } + + public function getRequestServer($raw = false) + { + return new ParameterBag($this->data['request_server']->getValue($raw)); + } + + public function getRequestCookies($raw = false) + { + return new ParameterBag($this->data['request_cookies']->getValue($raw)); + } + + public function getRequestAttributes() + { + return new ParameterBag($this->data['request_attributes']->getValue()); + } + + public function getResponseHeaders() + { + return new ParameterBag($this->data['response_headers']->getValue()); + } + + public function getResponseCookies() + { + return new ParameterBag($this->data['response_cookies']->getValue()); + } + + public function getSessionMetadata() + { + return $this->data['session_metadata']->getValue(); + } + + public function getSessionAttributes() + { + return $this->data['session_attributes']->getValue(); + } + + public function getFlashes() + { + return $this->data['flashes']->getValue(); + } + + public function getContent() + { + return $this->data['content']; + } + + public function getContentType() + { + return $this->data['content_type']; + } + + public function getStatusText() + { + return $this->data['status_text']; + } + + public function getStatusCode() + { + return $this->data['status_code']; + } + + public function getFormat() + { + return $this->data['format']; + } + + public function getLocale() + { + return $this->data['locale']; + } + + /** + * Gets the route name. + * + * The _route request attributes is automatically set by the Router Matcher. + * + * @return string The route + */ + public function getRoute() + { + return $this->data['route']; + } + + public function getIdentifier() + { + return $this->data['identifier']; + } + + /** + * Gets the route parameters. + * + * The _route_params request attributes is automatically set by the RouterListener. + * + * @return array The parameters + */ + public function getRouteParams() + { + return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params']->getValue() : array(); + } + + /** + * Gets the parsed controller. + * + * @return array|string The controller as a string or array of data + * with keys 'class', 'method', 'file' and 'line' + */ + public function getController() + { + return $this->data['controller']; + } + + /** + * Gets the previous request attributes. + * + * @return array|bool A legacy array of data from the previous redirection response + * or false otherwise + */ + public function getRedirect() + { + return isset($this->data['redirect']) ? $this->data['redirect'] : false; + } + + public function onKernelController(FilterControllerEvent $event) + { + $this->controllers[$event->getRequest()] = $event->getController(); + } + + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest() || !$event->getRequest()->hasSession() || !$event->getRequest()->getSession()->isStarted()) { + return; + } + + if ($event->getRequest()->getSession()->has('sf_redirect')) { + $event->getRequest()->attributes->set('_redirected', true); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::CONTROLLER => 'onKernelController', + KernelEvents::RESPONSE => 'onKernelResponse', + ); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'request'; + } + + /** + * Parse a controller. + * + * @param mixed $controller The controller to parse + * + * @return array|string An array of controller data or a simple string + */ + protected function parseController($controller) + { + if (is_string($controller) && false !== strpos($controller, '::')) { + $controller = explode('::', $controller); + } + + if (is_array($controller)) { + try { + $r = new \ReflectionMethod($controller[0], $controller[1]); + + return array( + 'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0], + 'method' => $controller[1], + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ); + } catch (\ReflectionException $e) { + if (is_callable($controller)) { + // using __call or __callStatic + return array( + 'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0], + 'method' => $controller[1], + 'file' => 'n/a', + 'line' => 'n/a', + ); + } + } + } + + if ($controller instanceof \Closure) { + $r = new \ReflectionFunction($controller); + + return array( + 'class' => $r->getName(), + 'method' => null, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ); + } + + if (is_object($controller)) { + $r = new \ReflectionClass($controller); + + return array( + 'class' => $r->getName(), + 'method' => null, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ); + } + + return is_string($controller) ? $controller : 'n/a'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php b/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php new file mode 100644 index 00000000..76d96234 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; + +/** + * RouterDataCollector. + * + * @author Fabien Potencier + */ +class RouterDataCollector extends DataCollector +{ + protected $controllers; + + public function __construct() + { + $this->controllers = new \SplObjectStorage(); + + $this->data = array( + 'redirect' => false, + 'url' => null, + 'route' => null, + ); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if ($response instanceof RedirectResponse) { + $this->data['redirect'] = true; + $this->data['url'] = $response->getTargetUrl(); + + if ($this->controllers->contains($request)) { + $this->data['route'] = $this->guessRoute($request, $this->controllers[$request]); + } + } + + unset($this->controllers[$request]); + } + + protected function guessRoute(Request $request, $controller) + { + return 'n/a'; + } + + /** + * Remembers the controller associated to each request. + * + * @param FilterControllerEvent $event The filter controller event + */ + public function onKernelController(FilterControllerEvent $event) + { + $this->controllers[$event->getRequest()] = $event->getController(); + } + + /** + * @return bool Whether this request will result in a redirect + */ + public function getRedirect() + { + return $this->data['redirect']; + } + + /** + * @return string|null The target URL + */ + public function getTargetUrl() + { + return $this->data['url']; + } + + /** + * @return string|null The target route + */ + public function getTargetRoute() + { + return $this->data['route']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'router'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php b/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php new file mode 100644 index 00000000..2d39156e --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * TimeDataCollector. + * + * @author Fabien Potencier + */ +class TimeDataCollector extends DataCollector implements LateDataCollectorInterface +{ + protected $kernel; + protected $stopwatch; + + public function __construct(KernelInterface $kernel = null, $stopwatch = null) + { + $this->kernel = $kernel; + $this->stopwatch = $stopwatch; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if (null !== $this->kernel) { + $startTime = $this->kernel->getStartTime(); + } else { + $startTime = $request->server->get('REQUEST_TIME_FLOAT'); + } + + $this->data = array( + 'token' => $response->headers->get('X-Debug-Token'), + 'start_time' => $startTime * 1000, + 'events' => array(), + ); + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + if (null !== $this->stopwatch && isset($this->data['token'])) { + $this->setEvents($this->stopwatch->getSectionEvents($this->data['token'])); + } + unset($this->data['token']); + } + + /** + * Sets the request events. + * + * @param array $events The request events + */ + public function setEvents(array $events) + { + foreach ($events as $event) { + $event->ensureStopped(); + } + + $this->data['events'] = $events; + } + + /** + * Gets the request events. + * + * @return array The request events + */ + public function getEvents() + { + return $this->data['events']; + } + + /** + * Gets the request elapsed time. + * + * @return float The elapsed time + */ + public function getDuration() + { + if (!isset($this->data['events']['__section__'])) { + return 0; + } + + $lastEvent = $this->data['events']['__section__']; + + return $lastEvent->getOrigin() + $lastEvent->getDuration() - $this->getStartTime(); + } + + /** + * Gets the initialization time. + * + * This is the time spent until the beginning of the request handling. + * + * @return float The elapsed time + */ + public function getInitTime() + { + if (!isset($this->data['events']['__section__'])) { + return 0; + } + + return $this->data['events']['__section__']->getOrigin() - $this->getStartTime(); + } + + /** + * Gets the request time. + * + * @return int The time + */ + public function getStartTime() + { + return $this->data['start_time']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'time'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.php b/vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.php new file mode 100644 index 00000000..f1e48311 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector\Util; + +@trigger_error('The '.__NAMESPACE__.'\ValueExporter class is deprecated since version 3.2 and will be removed in 4.0. Use the VarDumper component instead.', E_USER_DEPRECATED); + +/** + * @author Bernhard Schussek + * + * @deprecated since version 3.2, to be removed in 4.0. Use the VarDumper component instead. + */ +class ValueExporter +{ + /** + * Converts a PHP value to a string. + * + * @param mixed $value The PHP value + * @param int $depth only for internal usage + * @param bool $deep only for internal usage + * + * @return string The string representation of the given value + */ + public function exportValue($value, $depth = 1, $deep = false) + { + if ($value instanceof \__PHP_Incomplete_Class) { + return sprintf('__PHP_Incomplete_Class(%s)', $this->getClassNameFromIncomplete($value)); + } + + if (is_object($value)) { + if ($value instanceof \DateTimeInterface) { + return sprintf('Object(%s) - %s', get_class($value), $value->format(\DateTime::ATOM)); + } + + return sprintf('Object(%s)', get_class($value)); + } + + if (is_array($value)) { + if (empty($value)) { + return '[]'; + } + + $indent = str_repeat(' ', $depth); + + $a = array(); + foreach ($value as $k => $v) { + if (is_array($v)) { + $deep = true; + } + $a[] = sprintf('%s => %s', $k, $this->exportValue($v, $depth + 1, $deep)); + } + + if ($deep) { + return sprintf("[\n%s%s\n%s]", $indent, implode(sprintf(", \n%s", $indent), $a), str_repeat(' ', $depth - 1)); + } + + $s = sprintf('[%s]', implode(', ', $a)); + + if (80 > strlen($s)) { + return $s; + } + + return sprintf("[\n%s%s\n]", $indent, implode(sprintf(",\n%s", $indent), $a)); + } + + if (is_resource($value)) { + return sprintf('Resource(%s#%d)', get_resource_type($value), $value); + } + + if (null === $value) { + return 'null'; + } + + if (false === $value) { + return 'false'; + } + + if (true === $value) { + return 'true'; + } + + return (string) $value; + } + + private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) + { + $array = new \ArrayObject($value); + + return $array['__PHP_Incomplete_Class_Name']; + } +} diff --git a/vendor/symfony/http-kernel/Debug/FileLinkFormatter.php b/vendor/symfony/http-kernel/Debug/FileLinkFormatter.php new file mode 100644 index 00000000..c340d9b6 --- /dev/null +++ b/vendor/symfony/http-kernel/Debug/FileLinkFormatter.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * Formats debug file links. + * + * @author Jérémy Romey + */ +class FileLinkFormatter implements \Serializable +{ + private $fileLinkFormat; + private $requestStack; + private $baseDir; + private $urlFormat; + + public function __construct($fileLinkFormat = null, RequestStack $requestStack = null, $baseDir = null, $urlFormat = null) + { + $fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + if ($fileLinkFormat && !is_array($fileLinkFormat)) { + $i = strpos($f = $fileLinkFormat, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: strlen($f); + $fileLinkFormat = array(substr($f, 0, $i)) + preg_split('/&([^>]++)>/', substr($f, $i), -1, PREG_SPLIT_DELIM_CAPTURE); + } + + $this->fileLinkFormat = $fileLinkFormat; + $this->requestStack = $requestStack; + $this->baseDir = $baseDir; + $this->urlFormat = $urlFormat; + } + + public function format($file, $line) + { + if ($fmt = $this->getFileLinkFormat()) { + for ($i = 1; isset($fmt[$i]); ++$i) { + if (0 === strpos($file, $k = $fmt[$i++])) { + $file = substr_replace($file, $fmt[$i], 0, strlen($k)); + break; + } + } + + return strtr($fmt[0], array('%f' => $file, '%l' => $line)); + } + + return false; + } + + public function serialize() + { + return serialize($this->getFileLinkFormat()); + } + + public function unserialize($serialized) + { + if (\PHP_VERSION_ID >= 70000) { + $this->fileLinkFormat = unserialize($serialized, array('allowed_classes' => false)); + } else { + $this->fileLinkFormat = unserialize($serialized); + } + } + + private function getFileLinkFormat() + { + if ($this->fileLinkFormat) { + return $this->fileLinkFormat; + } + if ($this->requestStack && $this->baseDir && $this->urlFormat) { + $request = $this->requestStack->getMasterRequest(); + if ($request instanceof Request) { + return array( + $request->getSchemeAndHttpHost().$request->getBaseUrl().$this->urlFormat, + $this->baseDir.DIRECTORY_SEPARATOR, '', + ); + } + } + } +} diff --git a/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php b/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php new file mode 100644 index 00000000..fbc49dff --- /dev/null +++ b/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher as BaseTraceableEventDispatcher; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\Event; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier + */ +class TraceableEventDispatcher extends BaseTraceableEventDispatcher +{ + /** + * {@inheritdoc} + */ + protected function preDispatch($eventName, Event $event) + { + switch ($eventName) { + case KernelEvents::REQUEST: + $this->stopwatch->openSection(); + break; + case KernelEvents::VIEW: + case KernelEvents::RESPONSE: + // stop only if a controller has been executed + if ($this->stopwatch->isStarted('controller')) { + $this->stopwatch->stop('controller'); + } + break; + case KernelEvents::TERMINATE: + $token = $event->getResponse()->headers->get('X-Debug-Token'); + // There is a very special case when using built-in AppCache class as kernel wrapper, in the case + // of an ESI request leading to a `stale` response [B] inside a `fresh` cached response [A]. + // In this case, `$token` contains the [B] debug token, but the open `stopwatch` section ID + // is equal to the [A] debug token. Trying to reopen section with the [B] token throws an exception + // which must be caught. + try { + $this->stopwatch->openSection($token); + } catch (\LogicException $e) { + } + break; + } + } + + /** + * {@inheritdoc} + */ + protected function postDispatch($eventName, Event $event) + { + switch ($eventName) { + case KernelEvents::CONTROLLER_ARGUMENTS: + $this->stopwatch->start('controller', 'section'); + break; + case KernelEvents::RESPONSE: + $token = $event->getResponse()->headers->get('X-Debug-Token'); + $this->stopwatch->stopSection($token); + break; + case KernelEvents::TERMINATE: + // In the special case described in the `preDispatch` method above, the `$token` section + // does not exist, then closing it throws an exception which must be caught. + $token = $event->getResponse()->headers->get('X-Debug-Token'); + try { + $this->stopwatch->stopSection($token); + } catch (\LogicException $e) { + } + break; + } + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php b/vendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php new file mode 100644 index 00000000..9235ae4e --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Composer\Autoload\ClassLoader; +use Symfony\Component\Debug\DebugClassLoader; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\HttpKernel\Kernel; + +/** + * Sets the classes to compile in the cache for the container. + * + * @author Fabien Potencier + */ +class AddAnnotatedClassesToCachePass implements CompilerPassInterface +{ + private $kernel; + + public function __construct(Kernel $kernel) + { + $this->kernel = $kernel; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $classes = array(); + $annotatedClasses = array(); + foreach ($container->getExtensions() as $extension) { + if ($extension instanceof Extension) { + if (\PHP_VERSION_ID < 70000) { + $classes = array_merge($classes, $extension->getClassesToCompile()); + } + $annotatedClasses = array_merge($annotatedClasses, $extension->getAnnotatedClassesToCompile()); + } + } + + $existingClasses = $this->getClassesInComposerClassMaps(); + + if (\PHP_VERSION_ID < 70000) { + $classes = $container->getParameterBag()->resolveValue($classes); + $this->kernel->setClassCache($this->expandClasses($classes, $existingClasses)); + } + $annotatedClasses = $container->getParameterBag()->resolveValue($annotatedClasses); + $this->kernel->setAnnotatedClassCache($this->expandClasses($annotatedClasses, $existingClasses)); + } + + /** + * Expands the given class patterns using a list of existing classes. + * + * @param array $patterns The class patterns to expand + * @param array $classes The existing classes to match against the patterns + * + * @return array A list of classes derivated from the patterns + */ + private function expandClasses(array $patterns, array $classes) + { + $expanded = array(); + + // Explicit classes declared in the patterns are returned directly + foreach ($patterns as $key => $pattern) { + if (substr($pattern, -1) !== '\\' && false === strpos($pattern, '*')) { + unset($patterns[$key]); + $expanded[] = ltrim($pattern, '\\'); + } + } + + // Match patterns with the classes list + $regexps = $this->patternsToRegexps($patterns); + + foreach ($classes as $class) { + $class = ltrim($class, '\\'); + + if ($this->matchAnyRegexps($class, $regexps)) { + $expanded[] = $class; + } + } + + return array_unique($expanded); + } + + private function getClassesInComposerClassMaps() + { + $classes = array(); + + foreach (spl_autoload_functions() as $function) { + if (!is_array($function)) { + continue; + } + + if ($function[0] instanceof DebugClassLoader) { + $function = $function[0]->getClassLoader(); + } + + if (is_array($function) && $function[0] instanceof ClassLoader) { + $classes += array_filter($function[0]->getClassMap()); + } + } + + return array_keys($classes); + } + + private function patternsToRegexps($patterns) + { + $regexps = array(); + + foreach ($patterns as $pattern) { + // Escape user input + $regex = preg_quote(ltrim($pattern, '\\')); + + // Wildcards * and ** + $regex = strtr($regex, array('\\*\\*' => '.*?', '\\*' => '[^\\\\]*?')); + + // If this class does not end by a slash, anchor the end + if (substr($regex, -1) !== '\\') { + $regex .= '$'; + } + + $regexps[] = '{^\\\\'.$regex.'}'; + } + + return $regexps; + } + + private function matchAnyRegexps($class, $regexps) + { + $blacklisted = false !== strpos($class, 'Test'); + + foreach ($regexps as $regex) { + if ($blacklisted && false === strpos($regex, 'Test')) { + continue; + } + + if (preg_match($regex, '\\'.$class)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php b/vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php new file mode 100644 index 00000000..4ffa1751 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +@trigger_error('The '.__NAMESPACE__.'\AddClassesToCachePass class is deprecated since version 3.3 and will be removed in 4.0.', E_USER_DEPRECATED); + +/** + * Sets the classes to compile in the cache for the container. + * + * @author Fabien Potencier + * + * @deprecated since version 3.3, to be removed in 4.0. + */ +class AddClassesToCachePass extends AddAnnotatedClassesToCachePass +{ +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php b/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php new file mode 100644 index 00000000..c7eca306 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This extension sub-class provides first-class integration with the + * Config/Definition Component. + * + * You can use this as base class if + * + * a) you use the Config/Definition component for configuration, + * b) your configuration class is named "Configuration", and + * c) the configuration class resides in the DependencyInjection sub-folder. + * + * @author Johannes M. Schmitt + */ +abstract class ConfigurableExtension extends Extension +{ + /** + * {@inheritdoc} + */ + final public function load(array $configs, ContainerBuilder $container) + { + $this->loadInternal($this->processConfiguration($this->getConfiguration($configs, $container), $configs), $container); + } + + /** + * Configures the passed container according to the merged configuration. + * + * @param array $mergedConfig + * @param ContainerBuilder $container + */ + abstract protected function loadInternal(array $mergedConfig, ContainerBuilder $container); +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php b/vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php new file mode 100644 index 00000000..343e217b --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Gathers and configures the argument value resolvers. + * + * @author Iltar van der Berg + */ +class ControllerArgumentValueResolverPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + private $argumentResolverService; + private $argumentValueResolverTag; + + public function __construct($argumentResolverService = 'argument_resolver', $argumentValueResolverTag = 'controller.argument_value_resolver') + { + $this->argumentResolverService = $argumentResolverService; + $this->argumentValueResolverTag = $argumentValueResolverTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->argumentResolverService)) { + return; + } + + $container + ->getDefinition($this->argumentResolverService) + ->replaceArgument(1, new IteratorArgument($this->findAndSortTaggedServices($this->argumentValueResolverTag, $container))) + ; + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/Extension.php b/vendor/symfony/http-kernel/DependencyInjection/Extension.php new file mode 100644 index 00000000..f6449f4e --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/Extension.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Extension\Extension as BaseExtension; + +/** + * Allow adding classes to the class cache. + * + * @author Fabien Potencier + */ +abstract class Extension extends BaseExtension +{ + private $classes = array(); + private $annotatedClasses = array(); + + /** + * Gets the classes to cache. + * + * @return array An array of classes + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function getClassesToCompile() + { + if (\PHP_VERSION_ID >= 70000) { + @trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED); + } + + return $this->classes; + } + + /** + * Gets the annotated classes to cache. + * + * @return array An array of classes + */ + public function getAnnotatedClassesToCompile() + { + return $this->annotatedClasses; + } + + /** + * Adds classes to the class cache. + * + * @param array $classes An array of class patterns + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function addClassesToCompile(array $classes) + { + if (\PHP_VERSION_ID >= 70000) { + @trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED); + } + + $this->classes = array_merge($this->classes, $classes); + } + + /** + * Adds annotated classes to the class cache. + * + * @param array $annotatedClasses An array of class patterns + */ + public function addAnnotatedClassesToCompile(array $annotatedClasses) + { + $this->annotatedClasses = array_merge($this->annotatedClasses, $annotatedClasses); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php b/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php new file mode 100644 index 00000000..ac52c873 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; + +/** + * Adds services tagged kernel.fragment_renderer as HTTP content rendering strategies. + * + * @author Fabien Potencier + */ +class FragmentRendererPass implements CompilerPassInterface +{ + private $handlerService; + private $rendererTag; + + /** + * @param string $handlerService Service name of the fragment handler in the container + * @param string $rendererTag Tag name used for fragments + */ + public function __construct($handlerService = 'fragment.handler', $rendererTag = 'kernel.fragment_renderer') + { + $this->handlerService = $handlerService; + $this->rendererTag = $rendererTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->handlerService)) { + return; + } + + $definition = $container->getDefinition($this->handlerService); + $renderers = array(); + foreach ($container->findTaggedServiceIds($this->rendererTag, true) as $id => $tags) { + $def = $container->getDefinition($id); + $class = $container->getParameterBag()->resolveValue($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(FragmentRendererInterface::class)) { + throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, FragmentRendererInterface::class)); + } + + foreach ($tags as $tag) { + $renderers[$tag['alias']] = new Reference($id); + } + } + + $definition->replaceArgument(0, ServiceLocatorTagPass::register($container, $renderers)); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php b/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php new file mode 100644 index 00000000..d6f4dab1 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; + +/** + * Lazily loads fragment renderers from the dependency injection container. + * + * @author Fabien Potencier + */ +class LazyLoadingFragmentHandler extends FragmentHandler +{ + private $container; + /** + * @deprecated since version 3.3, to be removed in 4.0 + */ + private $rendererIds = array(); + private $initialized = array(); + + /** + * Constructor. + * + * @param ContainerInterface $container A container + * @param RequestStack $requestStack The Request stack that controls the lifecycle of requests + * @param bool $debug Whether the debug mode is enabled or not + */ + public function __construct(ContainerInterface $container, RequestStack $requestStack, $debug = false) + { + $this->container = $container; + + parent::__construct($requestStack, array(), $debug); + } + + /** + * Adds a service as a fragment renderer. + * + * @param string $name The service name + * @param string $renderer The render service id + * + * @deprecated since version 3.3, to be removed in 4.0 + */ + public function addRendererService($name, $renderer) + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); + + $this->rendererIds[$name] = $renderer; + } + + /** + * {@inheritdoc} + */ + public function render($uri, $renderer = 'inline', array $options = array()) + { + // BC 3.x, to be removed in 4.0 + if (isset($this->rendererIds[$renderer])) { + $this->addRenderer($this->container->get($this->rendererIds[$renderer])); + unset($this->rendererIds[$renderer]); + + return parent::render($uri, $renderer, $options); + } + + if (!isset($this->initialized[$renderer]) && $this->container->has($renderer)) { + $this->addRenderer($this->container->get($renderer)); + $this->initialized[$renderer] = true; + } + + return parent::render($uri, $renderer, $options); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php b/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php new file mode 100644 index 00000000..dcd73828 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass as BaseMergeExtensionConfigurationPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Ensures certain extensions are always loaded. + * + * @author Kris Wallsmith + */ +class MergeExtensionConfigurationPass extends BaseMergeExtensionConfigurationPass +{ + private $extensions; + + public function __construct(array $extensions) + { + $this->extensions = $extensions; + } + + public function process(ContainerBuilder $container) + { + foreach ($this->extensions as $extension) { + if (!count($container->getExtensionConfig($extension))) { + $container->loadFromExtension($extension, array()); + } + } + + parent::process($container); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php new file mode 100644 index 00000000..9c00f99b --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; + +/** + * Creates the service-locators required by ServiceValueResolver. + * + * @author Nicolas Grekas + */ +class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface +{ + private $resolverServiceId; + private $controllerTag; + + public function __construct($resolverServiceId = 'argument_resolver.service', $controllerTag = 'controller.service_arguments') + { + $this->resolverServiceId = $resolverServiceId; + $this->controllerTag = $controllerTag; + } + + public function process(ContainerBuilder $container) + { + if (false === $container->hasDefinition($this->resolverServiceId)) { + return; + } + + $parameterBag = $container->getParameterBag(); + $controllers = array(); + + foreach ($container->findTaggedServiceIds($this->controllerTag, true) as $id => $tags) { + $def = $container->getDefinition($id); + $def->setPublic(true); + $class = $def->getClass(); + $autowire = $def->isAutowired(); + + // resolve service class, taking parent definitions into account + while (!$class && $def instanceof ChildDefinition) { + $def = $container->findDefinition($def->getParent()); + $class = $def->getClass(); + } + $class = $parameterBag->resolveValue($class); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + $isContainerAware = $r->implementsInterface(ContainerAwareInterface::class) || is_subclass_of($class, AbstractController::class); + + // get regular public methods + $methods = array(); + $arguments = array(); + foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $r) { + if ('setContainer' === $r->name && $isContainerAware) { + continue; + } + if (!$r->isConstructor() && !$r->isDestructor() && !$r->isAbstract()) { + $methods[strtolower($r->name)] = array($r, $r->getParameters()); + } + } + + // validate and collect explicit per-actions and per-arguments service references + foreach ($tags as $attributes) { + if (!isset($attributes['action']) && !isset($attributes['argument']) && !isset($attributes['id'])) { + $autowire = true; + continue; + } + foreach (array('action', 'argument', 'id') as $k) { + if (!isset($attributes[$k][0])) { + throw new InvalidArgumentException(sprintf('Missing "%s" attribute on tag "%s" %s for service "%s".', $k, $this->controllerTag, json_encode($attributes, JSON_UNESCAPED_UNICODE), $id)); + } + } + if (!isset($methods[$action = strtolower($attributes['action'])])) { + throw new InvalidArgumentException(sprintf('Invalid "action" attribute on tag "%s" for service "%s": no public "%s()" method found on class "%s".', $this->controllerTag, $id, $attributes['action'], $class)); + } + list($r, $parameters) = $methods[$action]; + $found = false; + + foreach ($parameters as $p) { + if ($attributes['argument'] === $p->name) { + if (!isset($arguments[$r->name][$p->name])) { + $arguments[$r->name][$p->name] = $attributes['id']; + } + $found = true; + break; + } + } + + if (!$found) { + throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": method "%s()" has no "%s" argument on class "%s".', $this->controllerTag, $id, $r->name, $attributes['argument'], $class)); + } + } + + foreach ($methods as list($r, $parameters)) { + /** @var \ReflectionMethod $r */ + + // create a per-method map of argument-names to service/type-references + $args = array(); + foreach ($parameters as $p) { + /** @var \ReflectionParameter $p */ + $type = $target = ProxyHelper::getTypeHint($r, $p, true); + $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + + if (isset($arguments[$r->name][$p->name])) { + $target = $arguments[$r->name][$p->name]; + if ('?' !== $target[0]) { + $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + } elseif ('' === $target = (string) substr($target, 1)) { + throw new InvalidArgumentException(sprintf('A "%s" tag must have non-empty "id" attributes for service "%s".', $this->controllerTag, $id)); + } elseif ($p->allowsNull() && !$p->isOptional()) { + $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; + } + } elseif (!$type || !$autowire) { + continue; + } + + if ($type && !$p->isOptional() && !$p->allowsNull() && !class_exists($type) && !interface_exists($type, false)) { + $message = sprintf('Cannot determine controller argument for "%s::%s()": the $%s argument is type-hinted with the non-existent class or interface: "%s".', $class, $r->name, $p->name, $type); + + // see if the type-hint lives in the same namespace as the controller + if (0 === strncmp($type, $class, strrpos($class, '\\'))) { + $message .= ' Did you forget to add a use statement?'; + } + + throw new InvalidArgumentException($message); + } + + $args[$p->name] = $type ? new TypedReference($target, $type, $r->class, $invalidBehavior) : new Reference($target, $invalidBehavior); + } + // register the maps as a per-method service-locators + if ($args) { + $controllers[$id.':'.$r->name] = ServiceLocatorTagPass::register($container, $args); + } + } + } + + $container->getDefinition($this->resolverServiceId) + ->replaceArgument(0, ServiceLocatorTagPass::register($container, $controllers)); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php b/vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php new file mode 100644 index 00000000..440b0d00 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Removes empty service-locators registered for ServiceValueResolver. + * + * @author Nicolas Grekas + */ +class RemoveEmptyControllerArgumentLocatorsPass implements CompilerPassInterface +{ + private $resolverServiceId; + + public function __construct($resolverServiceId = 'argument_resolver.service') + { + $this->resolverServiceId = $resolverServiceId; + } + + public function process(ContainerBuilder $container) + { + if (false === $container->hasDefinition($this->resolverServiceId)) { + return; + } + + $serviceResolver = $container->getDefinition($this->resolverServiceId); + $controllerLocator = $container->getDefinition((string) $serviceResolver->getArgument(0)); + $controllers = $controllerLocator->getArgument(0); + + foreach ($controllers as $controller => $argumentRef) { + $argumentLocator = $container->getDefinition((string) $argumentRef->getValues()[0]); + + if (!$argumentLocator->getArgument(0)) { + // remove empty argument locators + $reason = sprintf('Removing service-argument resolver for controller "%s": no corresponding services exist for the referenced types.', $controller); + } else { + // any methods listed for call-at-instantiation cannot be actions + $reason = false; + $action = substr(strrchr($controller, ':'), 1); + $id = substr($controller, 0, -1 - strlen($action)); + $controllerDef = $container->getDefinition($id); + foreach ($controllerDef->getMethodCalls() as list($method, $args)) { + if (0 === strcasecmp($action, $method)) { + $reason = sprintf('Removing method "%s" of service "%s" from controller candidates: the method is called at instantiation, thus cannot be an action.', $action, $id); + break; + } + } + if (!$reason) { + if ($controllerDef->getClass() === $id) { + $controllers[$id.'::'.$action] = $argumentRef; + } + if ('__invoke' === $action) { + $controllers[$id] = $argumentRef; + } + continue; + } + } + + unset($controllers[$controller]); + $container->log($this, $reason); + } + + $controllerLocator->replaceArgument(0, $controllers); + } +} diff --git a/vendor/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php b/vendor/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php new file mode 100644 index 00000000..1dc784ed --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Allows filtering of controller arguments. + * + * You can call getController() to retrieve the controller and getArguments + * to retrieve the current arguments. With setArguments() you can replace + * arguments that are used to call the controller. + * + * Arguments set in the event must be compatible with the signature of the + * controller. + * + * @author Christophe Coevoet + */ +class FilterControllerArgumentsEvent extends FilterControllerEvent +{ + private $arguments; + + public function __construct(HttpKernelInterface $kernel, callable $controller, array $arguments, Request $request, $requestType) + { + parent::__construct($kernel, $controller, $request, $requestType); + + $this->arguments = $arguments; + } + + /** + * @return array + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * @param array $arguments + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + } +} diff --git a/vendor/symfony/http-kernel/Event/FilterControllerEvent.php b/vendor/symfony/http-kernel/Event/FilterControllerEvent.php new file mode 100644 index 00000000..e0d46aa4 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FilterControllerEvent.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Allows filtering of a controller callable. + * + * You can call getController() to retrieve the current controller. With + * setController() you can set a new controller that is used in the processing + * of the request. + * + * Controllers should be callables. + * + * @author Bernhard Schussek + */ +class FilterControllerEvent extends KernelEvent +{ + /** + * The current controller. + */ + private $controller; + + public function __construct(HttpKernelInterface $kernel, callable $controller, Request $request, $requestType) + { + parent::__construct($kernel, $request, $requestType); + + $this->setController($controller); + } + + /** + * Returns the current controller. + * + * @return callable + */ + public function getController() + { + return $this->controller; + } + + /** + * Sets a new controller. + * + * @param callable $controller + */ + public function setController(callable $controller) + { + $this->controller = $controller; + } +} diff --git a/vendor/symfony/http-kernel/Event/FilterResponseEvent.php b/vendor/symfony/http-kernel/Event/FilterResponseEvent.php new file mode 100644 index 00000000..ed816a9d --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FilterResponseEvent.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Allows to filter a Response object. + * + * You can call getResponse() to retrieve the current response. With + * setResponse() you can set a new response that will be returned to the + * browser. + * + * @author Bernhard Schussek + */ +class FilterResponseEvent extends KernelEvent +{ + /** + * The current response object. + * + * @var Response + */ + private $response; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, Response $response) + { + parent::__construct($kernel, $request, $requestType); + + $this->setResponse($response); + } + + /** + * Returns the current response object. + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } + + /** + * Sets a new response object. + * + * @param Response $response + */ + public function setResponse(Response $response) + { + $this->response = $response; + } +} diff --git a/vendor/symfony/http-kernel/Event/FinishRequestEvent.php b/vendor/symfony/http-kernel/Event/FinishRequestEvent.php new file mode 100644 index 00000000..ee724843 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FinishRequestEvent.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +/** + * Triggered whenever a request is fully processed. + * + * @author Benjamin Eberlei + */ +class FinishRequestEvent extends KernelEvent +{ +} diff --git a/vendor/symfony/http-kernel/Event/GetResponseEvent.php b/vendor/symfony/http-kernel/Event/GetResponseEvent.php new file mode 100644 index 00000000..4c96a4b7 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/GetResponseEvent.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Response; + +/** + * Allows to create a response for a request. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * @author Bernhard Schussek + */ +class GetResponseEvent extends KernelEvent +{ + /** + * The response object. + * + * @var Response + */ + private $response; + + /** + * Returns the response object. + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } + + /** + * Sets a response and stops event propagation. + * + * @param Response $response + */ + public function setResponse(Response $response) + { + $this->response = $response; + + $this->stopPropagation(); + } + + /** + * Returns whether a response was set. + * + * @return bool Whether a response was set + */ + public function hasResponse() + { + return null !== $this->response; + } +} diff --git a/vendor/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php b/vendor/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php new file mode 100644 index 00000000..f70ce09e --- /dev/null +++ b/vendor/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Allows to create a response for the return value of a controller. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * @author Bernhard Schussek + */ +class GetResponseForControllerResultEvent extends GetResponseEvent +{ + /** + * The return value of the controller. + * + * @var mixed + */ + private $controllerResult; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, $controllerResult) + { + parent::__construct($kernel, $request, $requestType); + + $this->controllerResult = $controllerResult; + } + + /** + * Returns the return value of the controller. + * + * @return mixed The controller return value + */ + public function getControllerResult() + { + return $this->controllerResult; + } + + /** + * Assigns the return value of the controller. + * + * @param mixed $controllerResult The controller return value + */ + public function setControllerResult($controllerResult) + { + $this->controllerResult = $controllerResult; + } +} diff --git a/vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.php b/vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.php new file mode 100644 index 00000000..751b7451 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Allows to create a response for a thrown exception. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * You can also call setException() to replace the thrown exception. This + * exception will be thrown if no response is set during processing of this + * event. + * + * @author Bernhard Schussek + */ +class GetResponseForExceptionEvent extends GetResponseEvent +{ + /** + * The exception object. + * + * @var \Exception + */ + private $exception; + + /** + * @var bool + */ + private $allowCustomResponseCode = false; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, \Exception $e) + { + parent::__construct($kernel, $request, $requestType); + + $this->setException($e); + } + + /** + * Returns the thrown exception. + * + * @return \Exception The thrown exception + */ + public function getException() + { + return $this->exception; + } + + /** + * Replaces the thrown exception. + * + * This exception will be thrown if no response is set in the event. + * + * @param \Exception $exception The thrown exception + */ + public function setException(\Exception $exception) + { + $this->exception = $exception; + } + + /** + * Mark the event as allowing a custom response code. + */ + public function allowCustomResponseCode() + { + $this->allowCustomResponseCode = true; + } + + /** + * Returns true if the event allows a custom response code. + * + * @return bool + */ + public function isAllowingCustomResponseCode() + { + return $this->allowCustomResponseCode; + } +} diff --git a/vendor/symfony/http-kernel/Event/KernelEvent.php b/vendor/symfony/http-kernel/Event/KernelEvent.php new file mode 100644 index 00000000..2043a017 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/KernelEvent.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\EventDispatcher\Event; + +/** + * Base class for events thrown in the HttpKernel component. + * + * @author Bernhard Schussek + */ +class KernelEvent extends Event +{ + /** + * The kernel in which this event was thrown. + * + * @var HttpKernelInterface + */ + private $kernel; + + /** + * The request the kernel is currently processing. + * + * @var Request + */ + private $request; + + /** + * The request type the kernel is currently processing. One of + * HttpKernelInterface::MASTER_REQUEST and HttpKernelInterface::SUB_REQUEST. + * + * @var int + */ + private $requestType; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType) + { + $this->kernel = $kernel; + $this->request = $request; + $this->requestType = $requestType; + } + + /** + * Returns the kernel in which this event was thrown. + * + * @return HttpKernelInterface + */ + public function getKernel() + { + return $this->kernel; + } + + /** + * Returns the request the kernel is currently processing. + * + * @return Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * Returns the request type the kernel is currently processing. + * + * @return int One of HttpKernelInterface::MASTER_REQUEST and + * HttpKernelInterface::SUB_REQUEST + */ + public function getRequestType() + { + return $this->requestType; + } + + /** + * Checks if this is a master request. + * + * @return bool True if the request is a master request + */ + public function isMasterRequest() + { + return HttpKernelInterface::MASTER_REQUEST === $this->requestType; + } +} diff --git a/vendor/symfony/http-kernel/Event/PostResponseEvent.php b/vendor/symfony/http-kernel/Event/PostResponseEvent.php new file mode 100644 index 00000000..2406fddb --- /dev/null +++ b/vendor/symfony/http-kernel/Event/PostResponseEvent.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Allows to execute logic after a response was sent. + * + * Since it's only triggered on master requests, the `getRequestType()` method + * will always return the value of `HttpKernelInterface::MASTER_REQUEST`. + * + * @author Jordi Boggiano + */ +class PostResponseEvent extends KernelEvent +{ + private $response; + + public function __construct(HttpKernelInterface $kernel, Request $request, Response $response) + { + parent::__construct($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $this->response = $response; + } + + /** + * Returns the response for which this event was thrown. + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php b/vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php new file mode 100644 index 00000000..7a6c2073 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Sets the session in the request. + * + * @author Johannes M. Schmitt + */ +abstract class AbstractSessionListener implements EventSubscriberInterface +{ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $request = $event->getRequest(); + $session = $this->getSession(); + if (null === $session || $request->hasSession()) { + return; + } + + $request->setSession($session); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array('onKernelRequest', 128), + ); + } + + /** + * Gets the session object. + * + * @return SessionInterface|null A SessionInterface instance or null if no session is available + */ + abstract protected function getSession(); +} diff --git a/vendor/symfony/http-kernel/EventListener/AbstractTestSessionListener.php b/vendor/symfony/http-kernel/EventListener/AbstractTestSessionListener.php new file mode 100644 index 00000000..eb6e66c0 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/AbstractTestSessionListener.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * TestSessionListener. + * + * Saves session in test environment. + * + * @author Bulat Shakirzyanov + * @author Fabien Potencier + */ +abstract class AbstractTestSessionListener implements EventSubscriberInterface +{ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + // bootstrap the session + $session = $this->getSession(); + if (!$session) { + return; + } + + $cookies = $event->getRequest()->cookies; + + if ($cookies->has($session->getName())) { + $session->setId($cookies->get($session->getName())); + } + } + + /** + * Checks if session was initialized and saves if current request is master + * Runs on 'kernel.response' in test environment. + * + * @param FilterResponseEvent $event + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $session = $event->getRequest()->getSession(); + if ($session && $session->isStarted()) { + $session->save(); + $params = session_get_cookie_params(); + $event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly'])); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array('onKernelRequest', 192), + KernelEvents::RESPONSE => array('onKernelResponse', -128), + ); + } + + /** + * Gets the session object. + * + * @return SessionInterface|null A SessionInterface instance or null if no session is available + */ + abstract protected function getSession(); +} diff --git a/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php b/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php new file mode 100644 index 00000000..280844c1 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; + +/** + * Adds configured formats to each request. + * + * @author Gildas Quemener + */ +class AddRequestFormatsListener implements EventSubscriberInterface +{ + /** + * @var array + */ + protected $formats; + + /** + * @param array $formats + */ + public function __construct(array $formats) + { + $this->formats = $formats; + } + + /** + * Adds request formats. + * + * @param GetResponseEvent $event + */ + public function onKernelRequest(GetResponseEvent $event) + { + foreach ($this->formats as $format => $mimeTypes) { + $event->getRequest()->setFormat($format, $mimeTypes); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array(KernelEvents::REQUEST => array('onKernelRequest', 1)); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php b/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php new file mode 100644 index 00000000..29e1725a --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Debug\ErrorHandler; +use Symfony\Component\Debug\ExceptionHandler; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\KernelEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleEvent; +use Symfony\Component\Console\Output\ConsoleOutputInterface; + +/** + * Configures errors and exceptions handlers. + * + * @author Nicolas Grekas + */ +class DebugHandlersListener implements EventSubscriberInterface +{ + private $exceptionHandler; + private $logger; + private $levels; + private $throwAt; + private $scream; + private $fileLinkFormat; + private $scope; + private $firstCall = true; + + /** + * @param callable|null $exceptionHandler A handler that will be called on Exception + * @param LoggerInterface|null $logger A PSR-3 logger + * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value + * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged + * @param string|array $fileLinkFormat The format for links to source files + * @param bool $scope Enables/disables scoping mode + */ + public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, $throwAt = E_ALL, $scream = true, $fileLinkFormat = null, $scope = true) + { + $this->exceptionHandler = $exceptionHandler; + $this->logger = $logger; + $this->levels = null === $levels ? E_ALL : $levels; + $this->throwAt = is_numeric($throwAt) ? (int) $throwAt : (null === $throwAt ? null : ($throwAt ? E_ALL : null)); + $this->scream = (bool) $scream; + $this->fileLinkFormat = $fileLinkFormat; + $this->scope = (bool) $scope; + } + + /** + * Configures the error handler. + * + * @param Event|null $event The triggering event + */ + public function configure(Event $event = null) + { + if (!$this->firstCall) { + return; + } + $this->firstCall = false; + if ($this->logger || null !== $this->throwAt) { + $handler = set_error_handler('var_dump'); + $handler = is_array($handler) ? $handler[0] : null; + restore_error_handler(); + if ($handler instanceof ErrorHandler) { + if ($this->logger) { + $handler->setDefaultLogger($this->logger, $this->levels); + if (is_array($this->levels)) { + $levels = 0; + foreach ($this->levels as $type => $log) { + $levels |= $type; + } + } else { + $levels = $this->levels; + } + if ($this->scream) { + $handler->screamAt($levels); + } + if ($this->scope) { + $handler->scopeAt($this->levels); + } else { + $handler->scopeAt(0, true); + } + $this->logger = $this->levels = null; + } + if (null !== $this->throwAt) { + $handler->throwAt($this->throwAt, true); + } + } + } + if (!$this->exceptionHandler) { + if ($event instanceof KernelEvent) { + if (method_exists($event->getKernel(), 'terminateWithException')) { + $this->exceptionHandler = array($event->getKernel(), 'terminateWithException'); + } + } elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) { + $output = $event->getOutput(); + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + $this->exceptionHandler = function ($e) use ($app, $output) { + $app->renderException($e, $output); + }; + } + } + if ($this->exceptionHandler) { + $handler = set_exception_handler('var_dump'); + $handler = is_array($handler) ? $handler[0] : null; + restore_exception_handler(); + if ($handler instanceof ErrorHandler) { + $h = $handler->setExceptionHandler('var_dump') ?: $this->exceptionHandler; + $handler->setExceptionHandler($h); + $handler = is_array($h) ? $h[0] : null; + } + if ($handler instanceof ExceptionHandler) { + $handler->setHandler($this->exceptionHandler); + if (null !== $this->fileLinkFormat) { + $handler->setFileLinkFormat($this->fileLinkFormat); + } + } + $this->exceptionHandler = null; + } + } + + public static function getSubscribedEvents() + { + $events = array(KernelEvents::REQUEST => array('configure', 2048)); + + if ('cli' === PHP_SAPI && defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) { + $events[ConsoleEvents::COMMAND] = array('configure', 2048); + } + + return $events; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/DumpListener.php b/vendor/symfony/http-kernel/EventListener/DumpListener.php new file mode 100644 index 00000000..88acef31 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/DumpListener.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Dumper\DataDumperInterface; +use Symfony\Component\VarDumper\VarDumper; + +/** + * Configures dump() handler. + * + * @author Nicolas Grekas + */ +class DumpListener implements EventSubscriberInterface +{ + private $cloner; + private $dumper; + + /** + * @param ClonerInterface $cloner Cloner service + * @param DataDumperInterface $dumper Dumper service + */ + public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper) + { + $this->cloner = $cloner; + $this->dumper = $dumper; + } + + public function configure() + { + $cloner = $this->cloner; + $dumper = $this->dumper; + + VarDumper::setHandler(function ($var) use ($cloner, $dumper) { + $dumper->dump($cloner->cloneVar($var)); + }); + } + + public static function getSubscribedEvents() + { + if (!class_exists(ConsoleEvents::class)) { + return array(); + } + + // Register early to have a working dump() as early as possible + return array(ConsoleEvents::COMMAND => array('configure', 1024)); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ExceptionListener.php b/vendor/symfony/http-kernel/EventListener/ExceptionListener.php new file mode 100644 index 00000000..cf3a2f0a --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ExceptionListener.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * ExceptionListener. + * + * @author Fabien Potencier + */ +class ExceptionListener implements EventSubscriberInterface +{ + protected $controller; + protected $logger; + + public function __construct($controller, LoggerInterface $logger = null) + { + $this->controller = $controller; + $this->logger = $logger; + } + + public function onKernelException(GetResponseForExceptionEvent $event) + { + $exception = $event->getException(); + $request = $event->getRequest(); + + $this->logException($exception, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine())); + + $request = $this->duplicateRequest($exception, $request); + + try { + $response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, false); + } catch (\Exception $e) { + $this->logException($e, sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine())); + + $wrapper = $e; + + while ($prev = $wrapper->getPrevious()) { + if ($exception === $wrapper = $prev) { + throw $e; + } + } + + $prev = new \ReflectionProperty('Exception', 'previous'); + $prev->setAccessible(true); + $prev->setValue($wrapper, $exception); + + throw $e; + } + + $event->setResponse($response); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::EXCEPTION => array('onKernelException', -128), + ); + } + + /** + * Logs an exception. + * + * @param \Exception $exception The \Exception instance + * @param string $message The error message to log + */ + protected function logException(\Exception $exception, $message) + { + if (null !== $this->logger) { + if (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) { + $this->logger->critical($message, array('exception' => $exception)); + } else { + $this->logger->error($message, array('exception' => $exception)); + } + } + } + + /** + * Clones the request for the exception. + * + * @param \Exception $exception The thrown exception + * @param Request $request The original request + * + * @return Request $request The cloned request + */ + protected function duplicateRequest(\Exception $exception, Request $request) + { + $attributes = array( + '_controller' => $this->controller, + 'exception' => FlattenException::create($exception), + 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, + ); + $request = $request->duplicate(null, null, $attributes); + $request->setMethod('GET'); + + return $request; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/FragmentListener.php b/vendor/symfony/http-kernel/EventListener/FragmentListener.php new file mode 100644 index 00000000..37bf15c3 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/FragmentListener.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\UriSigner; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Handles content fragments represented by special URIs. + * + * All URL paths starting with /_fragment are handled as + * content fragments by this listener. + * + * If throws an AccessDeniedHttpException exception if the request + * is not signed or if it is not an internal sub-request. + * + * @author Fabien Potencier + */ +class FragmentListener implements EventSubscriberInterface +{ + private $signer; + private $fragmentPath; + + /** + * Constructor. + * + * @param UriSigner $signer A UriSigner instance + * @param string $fragmentPath The path that triggers this listener + */ + public function __construct(UriSigner $signer, $fragmentPath = '/_fragment') + { + $this->signer = $signer; + $this->fragmentPath = $fragmentPath; + } + + /** + * Fixes request attributes when the path is '/_fragment'. + * + * @param GetResponseEvent $event A GetResponseEvent instance + * + * @throws AccessDeniedHttpException if the request does not come from a trusted IP. + */ + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + + if ($this->fragmentPath !== rawurldecode($request->getPathInfo())) { + return; + } + + if ($request->attributes->has('_controller')) { + // Is a sub-request: no need to parse _path but it should still be removed from query parameters as below. + $request->query->remove('_path'); + + return; + } + + if ($event->isMasterRequest()) { + $this->validateRequest($request); + } + + parse_str($request->query->get('_path', ''), $attributes); + $request->attributes->add($attributes); + $request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params', array()), $attributes)); + $request->query->remove('_path'); + } + + protected function validateRequest(Request $request) + { + // is the Request safe? + if (!$request->isMethodSafe(false)) { + throw new AccessDeniedHttpException(); + } + + // is the Request signed? + // we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering) + if ($this->signer->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().(null !== ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : ''))) { + return; + } + + throw new AccessDeniedHttpException(); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array(array('onKernelRequest', 48)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/LocaleListener.php b/vendor/symfony/http-kernel/EventListener/LocaleListener.php new file mode 100644 index 00000000..99fc7867 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/LocaleListener.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\RequestContextAwareInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Initializes the locale based on the current request. + * + * @author Fabien Potencier + */ +class LocaleListener implements EventSubscriberInterface +{ + private $router; + private $defaultLocale; + private $requestStack; + + /** + * Constructor. + * + * @param RequestStack $requestStack A RequestStack instance + * @param string $defaultLocale The default locale + * @param RequestContextAwareInterface|null $router The router + */ + public function __construct(RequestStack $requestStack, $defaultLocale = 'en', RequestContextAwareInterface $router = null) + { + $this->defaultLocale = $defaultLocale; + $this->requestStack = $requestStack; + $this->router = $router; + } + + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + $request->setDefaultLocale($this->defaultLocale); + + $this->setLocale($request); + $this->setRouterContext($request); + } + + public function onKernelFinishRequest(FinishRequestEvent $event) + { + if (null !== $parentRequest = $this->requestStack->getParentRequest()) { + $this->setRouterContext($parentRequest); + } + } + + private function setLocale(Request $request) + { + if ($locale = $request->attributes->get('_locale')) { + $request->setLocale($locale); + } + } + + private function setRouterContext(Request $request) + { + if (null !== $this->router) { + $this->router->getContext()->setParameter('_locale', $request->getLocale()); + } + } + + public static function getSubscribedEvents() + { + return array( + // must be registered after the Router to have access to the _locale + KernelEvents::REQUEST => array(array('onKernelRequest', 16)), + KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ProfilerListener.php b/vendor/symfony/http-kernel/EventListener/ProfilerListener.php new file mode 100644 index 00000000..4fea1168 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ProfilerListener.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Profiler\Profiler; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * ProfilerListener collects data for the current request by listening to the kernel events. + * + * @author Fabien Potencier + */ +class ProfilerListener implements EventSubscriberInterface +{ + protected $profiler; + protected $matcher; + protected $onlyException; + protected $onlyMasterRequests; + protected $exception; + protected $profiles; + protected $requestStack; + protected $parents; + + /** + * Constructor. + * + * @param Profiler $profiler A Profiler instance + * @param RequestStack $requestStack A RequestStack instance + * @param RequestMatcherInterface|null $matcher A RequestMatcher instance + * @param bool $onlyException true if the profiler only collects data when an exception occurs, false otherwise + * @param bool $onlyMasterRequests true if the profiler only collects data when the request is a master request, false otherwise + */ + public function __construct(Profiler $profiler, RequestStack $requestStack, RequestMatcherInterface $matcher = null, $onlyException = false, $onlyMasterRequests = false) + { + $this->profiler = $profiler; + $this->matcher = $matcher; + $this->onlyException = (bool) $onlyException; + $this->onlyMasterRequests = (bool) $onlyMasterRequests; + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + $this->requestStack = $requestStack; + } + + /** + * Handles the onKernelException event. + * + * @param GetResponseForExceptionEvent $event A GetResponseForExceptionEvent instance + */ + public function onKernelException(GetResponseForExceptionEvent $event) + { + if ($this->onlyMasterRequests && !$event->isMasterRequest()) { + return; + } + + $this->exception = $event->getException(); + } + + /** + * Handles the onKernelResponse event. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + $master = $event->isMasterRequest(); + if ($this->onlyMasterRequests && !$master) { + return; + } + + if ($this->onlyException && null === $this->exception) { + return; + } + + $request = $event->getRequest(); + $exception = $this->exception; + $this->exception = null; + + if (null !== $this->matcher && !$this->matcher->matches($request)) { + return; + } + + if (!$profile = $this->profiler->collect($request, $event->getResponse(), $exception)) { + return; + } + + $this->profiles[$request] = $profile; + + $this->parents[$request] = $this->requestStack->getParentRequest(); + } + + public function onKernelTerminate(PostResponseEvent $event) + { + // attach children to parents + foreach ($this->profiles as $request) { + if (null !== $parentRequest = $this->parents[$request]) { + if (isset($this->profiles[$parentRequest])) { + $this->profiles[$parentRequest]->addChild($this->profiles[$request]); + } + } + } + + // save profiles + foreach ($this->profiles as $request) { + $this->profiler->saveProfile($this->profiles[$request]); + } + + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => array('onKernelResponse', -100), + KernelEvents::EXCEPTION => 'onKernelException', + KernelEvents::TERMINATE => array('onKernelTerminate', -1024), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ResponseListener.php b/vendor/symfony/http-kernel/EventListener/ResponseListener.php new file mode 100644 index 00000000..eeb2b0fc --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ResponseListener.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * ResponseListener fixes the Response headers based on the Request. + * + * @author Fabien Potencier + */ +class ResponseListener implements EventSubscriberInterface +{ + private $charset; + + public function __construct($charset) + { + $this->charset = $charset; + } + + /** + * Filters the Response. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $response = $event->getResponse(); + + if (null === $response->getCharset()) { + $response->setCharset($this->charset); + } + + $response->prepare($event->getRequest()); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => 'onKernelResponse', + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/RouterListener.php b/vendor/symfony/http-kernel/EventListener/RouterListener.php new file mode 100644 index 00000000..3c46be86 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/RouterListener.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RequestContextAwareInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Initializes the context from the request and sets request attributes based on a matching route. + * + * @author Fabien Potencier + */ +class RouterListener implements EventSubscriberInterface +{ + private $matcher; + private $context; + private $logger; + private $requestStack; + + /** + * Constructor. + * + * @param UrlMatcherInterface|RequestMatcherInterface $matcher The Url or Request matcher + * @param RequestStack $requestStack A RequestStack instance + * @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface) + * @param LoggerInterface|null $logger The logger + * + * @throws \InvalidArgumentException + */ + public function __construct($matcher, RequestStack $requestStack, RequestContext $context = null, LoggerInterface $logger = null) + { + if (!$matcher instanceof UrlMatcherInterface && !$matcher instanceof RequestMatcherInterface) { + throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.'); + } + + if (null === $context && !$matcher instanceof RequestContextAwareInterface) { + throw new \InvalidArgumentException('You must either pass a RequestContext or the matcher must implement RequestContextAwareInterface.'); + } + + $this->matcher = $matcher; + $this->context = $context ?: $matcher->getContext(); + $this->requestStack = $requestStack; + $this->logger = $logger; + } + + private function setCurrentRequest(Request $request = null) + { + if (null !== $request) { + $this->context->fromRequest($request); + } + } + + /** + * After a sub-request is done, we need to reset the routing context to the parent request so that the URL generator + * operates on the correct context again. + * + * @param FinishRequestEvent $event + */ + public function onKernelFinishRequest(FinishRequestEvent $event) + { + $this->setCurrentRequest($this->requestStack->getParentRequest()); + } + + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + + $this->setCurrentRequest($request); + + if ($request->attributes->has('_controller')) { + // routing is already done + return; + } + + // add attributes based on the request (routing) + try { + // matching a request is more powerful than matching a URL path + context, so try that first + if ($this->matcher instanceof RequestMatcherInterface) { + $parameters = $this->matcher->matchRequest($request); + } else { + $parameters = $this->matcher->match($request->getPathInfo()); + } + + if (null !== $this->logger) { + $this->logger->info('Matched route "{route}".', array( + 'route' => isset($parameters['_route']) ? $parameters['_route'] : 'n/a', + 'route_parameters' => $parameters, + 'request_uri' => $request->getUri(), + 'method' => $request->getMethod(), + )); + } + + $request->attributes->add($parameters); + unset($parameters['_route'], $parameters['_controller']); + $request->attributes->set('_route_params', $parameters); + } catch (ResourceNotFoundException $e) { + $message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo()); + + if ($referer = $request->headers->get('referer')) { + $message .= sprintf(' (from "%s")', $referer); + } + + throw new NotFoundHttpException($message, $e); + } catch (MethodNotAllowedException $e) { + $message = sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)', $request->getMethod(), $request->getPathInfo(), implode(', ', $e->getAllowedMethods())); + + throw new MethodNotAllowedHttpException($e->getAllowedMethods(), $message, $e); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array(array('onKernelRequest', 32)), + KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/SaveSessionListener.php b/vendor/symfony/http-kernel/EventListener/SaveSessionListener.php new file mode 100644 index 00000000..36809b59 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/SaveSessionListener.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Saves the session, in case it is still open, before sending the response/headers. + * + * This ensures several things in case the developer did not save the session explicitly: + * + * * If a session save handler without locking is used, it ensures the data is available + * on the next request, e.g. after a redirect. PHPs auto-save at script end via + * session_register_shutdown is executed after fastcgi_finish_request. So in this case + * the data could be missing the next request because it might not be saved the moment + * the new request is processed. + * * A locking save handler (e.g. the native 'files') circumvents concurrency problems like + * the one above. But by saving the session before long-running things in the terminate event, + * we ensure the session is not blocked longer than needed. + * * When regenerating the session ID no locking is involved in PHPs session design. See + * https://bugs.php.net/bug.php?id=61470 for a discussion. So in this case, the session must + * be saved anyway before sending the headers with the new session ID. Otherwise session + * data could get lost again for concurrent requests with the new ID. One result could be + * that you get logged out after just logging in. + * + * This listener should be executed as one of the last listeners, so that previous listeners + * can still operate on the open session. This prevents the overhead of restarting it. + * Listeners after closing the session can still work with the session as usual because + * Symfonys session implementation starts the session on demand. So writing to it after + * it is saved will just restart it. + * + * @author Tobias Schultze + */ +class SaveSessionListener implements EventSubscriberInterface +{ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $session = $event->getRequest()->getSession(); + if ($session && $session->isStarted()) { + $session->save(); + } + } + + public static function getSubscribedEvents() + { + return array( + // low priority but higher than StreamedResponseListener + KernelEvents::RESPONSE => array(array('onKernelResponse', -1000)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/SessionListener.php b/vendor/symfony/http-kernel/EventListener/SessionListener.php new file mode 100644 index 00000000..39ebfd92 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/SessionListener.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Container\ContainerInterface; + +/** + * Sets the session in the request. + * + * @author Fabien Potencier + * + * @final since version 3.3 + */ +class SessionListener extends AbstractSessionListener +{ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + protected function getSession() + { + if (!$this->container->has('session')) { + return; + } + + return $this->container->get('session'); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/StreamedResponseListener.php b/vendor/symfony/http-kernel/EventListener/StreamedResponseListener.php new file mode 100644 index 00000000..571cd74e --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/StreamedResponseListener.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * StreamedResponseListener is responsible for sending the Response + * to the client. + * + * @author Fabien Potencier + */ +class StreamedResponseListener implements EventSubscriberInterface +{ + /** + * Filters the Response. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $response = $event->getResponse(); + + if ($response instanceof StreamedResponse) { + $response->send(); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => array('onKernelResponse', -1024), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/SurrogateListener.php b/vendor/symfony/http-kernel/EventListener/SurrogateListener.php new file mode 100644 index 00000000..a207ab67 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/SurrogateListener.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * SurrogateListener adds a Surrogate-Control HTTP header when the Response needs to be parsed for Surrogates. + * + * @author Fabien Potencier + */ +class SurrogateListener implements EventSubscriberInterface +{ + private $surrogate; + + /** + * Constructor. + * + * @param SurrogateInterface $surrogate An SurrogateInterface instance + */ + public function __construct(SurrogateInterface $surrogate = null) + { + $this->surrogate = $surrogate; + } + + /** + * Filters the Response. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $kernel = $event->getKernel(); + $surrogate = $this->surrogate; + if ($kernel instanceof HttpCache) { + $surrogate = $kernel->getSurrogate(); + if (null !== $this->surrogate && $this->surrogate->getName() !== $surrogate->getName()) { + $surrogate = $this->surrogate; + } + } + + if (null === $surrogate) { + return; + } + + $surrogate->addSurrogateControl($event->getResponse()); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => 'onKernelResponse', + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/TestSessionListener.php b/vendor/symfony/http-kernel/EventListener/TestSessionListener.php new file mode 100644 index 00000000..36abb422 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/TestSessionListener.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Container\ContainerInterface; + +/** + * Sets the session in the request. + * + * @author Fabien Potencier + * + * @final since version 3.3 + */ +class TestSessionListener extends AbstractTestSessionListener +{ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + protected function getSession() + { + if (!$this->container->has('session')) { + return; + } + + return $this->container->get('session'); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/TranslatorListener.php b/vendor/symfony/http-kernel/EventListener/TranslatorListener.php new file mode 100644 index 00000000..6967ad02 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/TranslatorListener.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * Synchronizes the locale between the request and the translator. + * + * @author Fabien Potencier + */ +class TranslatorListener implements EventSubscriberInterface +{ + private $translator; + private $requestStack; + + public function __construct(TranslatorInterface $translator, RequestStack $requestStack) + { + $this->translator = $translator; + $this->requestStack = $requestStack; + } + + public function onKernelRequest(GetResponseEvent $event) + { + $this->setLocale($event->getRequest()); + } + + public function onKernelFinishRequest(FinishRequestEvent $event) + { + if (null === $parentRequest = $this->requestStack->getParentRequest()) { + return; + } + + $this->setLocale($parentRequest); + } + + public static function getSubscribedEvents() + { + return array( + // must be registered after the Locale listener + KernelEvents::REQUEST => array(array('onKernelRequest', 10)), + KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + ); + } + + private function setLocale(Request $request) + { + try { + $this->translator->setLocale($request->getLocale()); + } catch (\InvalidArgumentException $e) { + $this->translator->setLocale($request->getDefaultLocale()); + } + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php b/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php new file mode 100644 index 00000000..b96c7814 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Validates Requests. + * + * @author Magnus Nordlander + */ +class ValidateRequestListener implements EventSubscriberInterface +{ + /** + * Performs the validation. + * + * @param GetResponseEvent $event + */ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + $request = $event->getRequest(); + + if ($request::getTrustedProxies()) { + $request->getClientIps(); + } + + $request->getHost(); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array( + array('onKernelRequest', 256), + ), + ); + } +} diff --git a/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php b/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php new file mode 100644 index 00000000..79d8639a --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * AccessDeniedHttpException. + * + * @author Fabien Potencier + * @author Christophe Coevoet + */ +class AccessDeniedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(403, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/BadRequestHttpException.php b/vendor/symfony/http-kernel/Exception/BadRequestHttpException.php new file mode 100644 index 00000000..5f68172a --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/BadRequestHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * BadRequestHttpException. + * + * @author Ben Ramsey + */ +class BadRequestHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(400, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/ConflictHttpException.php b/vendor/symfony/http-kernel/Exception/ConflictHttpException.php new file mode 100644 index 00000000..34d738ed --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/ConflictHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * ConflictHttpException. + * + * @author Ben Ramsey + */ +class ConflictHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(409, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/GoneHttpException.php b/vendor/symfony/http-kernel/Exception/GoneHttpException.php new file mode 100644 index 00000000..16ea223f --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/GoneHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * GoneHttpException. + * + * @author Ben Ramsey + */ +class GoneHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(410, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/HttpException.php b/vendor/symfony/http-kernel/Exception/HttpException.php new file mode 100644 index 00000000..e8e37605 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/HttpException.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * HttpException. + * + * @author Kris Wallsmith + */ +class HttpException extends \RuntimeException implements HttpExceptionInterface +{ + private $statusCode; + private $headers; + + public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = array(), $code = 0) + { + $this->statusCode = $statusCode; + $this->headers = $headers; + + parent::__construct($message, $code, $previous); + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getHeaders() + { + return $this->headers; + } + + /** + * Set response headers. + * + * @param array $headers Response headers + */ + public function setHeaders(array $headers) + { + $this->headers = $headers; + } +} diff --git a/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php b/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php new file mode 100644 index 00000000..8aa50a9f --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * Interface for HTTP error exceptions. + * + * @author Kris Wallsmith + */ +interface HttpExceptionInterface +{ + /** + * Returns the status code. + * + * @return int An HTTP response status code + */ + public function getStatusCode(); + + /** + * Returns response headers. + * + * @return array Response headers + */ + public function getHeaders(); +} diff --git a/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php b/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php new file mode 100644 index 00000000..0c4b9431 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * LengthRequiredHttpException. + * + * @author Ben Ramsey + */ +class LengthRequiredHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(411, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php b/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php new file mode 100644 index 00000000..78dd26bf --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * MethodNotAllowedHttpException. + * + * @author Kris Wallsmith + */ +class MethodNotAllowedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param array $allow An array of allowed methods + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct(array $allow, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array('Allow' => strtoupper(implode(', ', $allow))); + + parent::__construct(405, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php b/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php new file mode 100644 index 00000000..cc6be4ba --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * NotAcceptableHttpException. + * + * @author Ben Ramsey + */ +class NotAcceptableHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(406, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php b/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php new file mode 100644 index 00000000..4639e379 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * NotFoundHttpException. + * + * @author Fabien Potencier + */ +class NotFoundHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(404, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php b/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php new file mode 100644 index 00000000..9df0e7b4 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * PreconditionFailedHttpException. + * + * @author Ben Ramsey + */ +class PreconditionFailedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(412, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php b/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php new file mode 100644 index 00000000..08ebca22 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * PreconditionRequiredHttpException. + * + * @author Ben Ramsey + * + * @see http://tools.ietf.org/html/rfc6585 + */ +class PreconditionRequiredHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(428, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php b/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php new file mode 100644 index 00000000..32b9e2d2 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * ServiceUnavailableHttpException. + * + * @author Ben Ramsey + */ +class ServiceUnavailableHttpException extends HttpException +{ + /** + * Constructor. + * + * @param int|string $retryAfter The number of seconds or HTTP-date after which the request may be retried + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array(); + if ($retryAfter) { + $headers = array('Retry-After' => $retryAfter); + } + + parent::__construct(503, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php b/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php new file mode 100644 index 00000000..ab86e092 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * TooManyRequestsHttpException. + * + * @author Ben Ramsey + * + * @see http://tools.ietf.org/html/rfc6585 + */ +class TooManyRequestsHttpException extends HttpException +{ + /** + * Constructor. + * + * @param int|string $retryAfter The number of seconds or HTTP-date after which the request may be retried + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array(); + if ($retryAfter) { + $headers = array('Retry-After' => $retryAfter); + } + + parent::__construct(429, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php b/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php new file mode 100644 index 00000000..0dfe42db --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * UnauthorizedHttpException. + * + * @author Ben Ramsey + */ +class UnauthorizedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $challenge WWW-Authenticate challenge string + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($challenge, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array('WWW-Authenticate' => $challenge); + + parent::__construct(401, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php b/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php new file mode 100644 index 00000000..eb13f563 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * UnprocessableEntityHttpException. + * + * @author Steve Hutchins + */ +class UnprocessableEntityHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(422, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php b/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php new file mode 100644 index 00000000..a9d8fa08 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * UnsupportedMediaTypeHttpException. + * + * @author Ben Ramsey + */ +class UnsupportedMediaTypeHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(415, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php new file mode 100644 index 00000000..0d4d26b6 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; +use Symfony\Component\HttpKernel\UriSigner; + +/** + * Implements Surrogate rendering strategy. + * + * @author Fabien Potencier + */ +abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRenderer +{ + private $surrogate; + private $inlineStrategy; + private $signer; + + /** + * Constructor. + * + * The "fallback" strategy when surrogate is not available should always be an + * instance of InlineFragmentRenderer. + * + * @param SurrogateInterface $surrogate An Surrogate instance + * @param FragmentRendererInterface $inlineStrategy The inline strategy to use when the surrogate is not supported + * @param UriSigner $signer + */ + public function __construct(SurrogateInterface $surrogate = null, FragmentRendererInterface $inlineStrategy, UriSigner $signer = null) + { + $this->surrogate = $surrogate; + $this->inlineStrategy = $inlineStrategy; + $this->signer = $signer; + } + + /** + * {@inheritdoc} + * + * Note that if the current Request has no surrogate capability, this method + * falls back to use the inline rendering strategy. + * + * Additional available options: + * + * * alt: an alternative URI to render in case of an error + * * comment: a comment to add when returning the surrogate tag + * + * Note, that not all surrogate strategies support all options. For now + * 'alt' and 'comment' are only supported by ESI. + * + * @see Symfony\Component\HttpKernel\HttpCache\SurrogateInterface + */ + public function render($uri, Request $request, array $options = array()) + { + if (!$this->surrogate || !$this->surrogate->hasSurrogateCapability($request)) { + if ($uri instanceof ControllerReference && $this->containsNonScalars($uri->attributes)) { + @trigger_error('Passing non-scalar values as part of URI attributes to the ESI and SSI rendering strategies is deprecated since version 3.1, and will be removed in 4.0. Use a different rendering strategy or pass scalar values.', E_USER_DEPRECATED); + } + + return $this->inlineStrategy->render($uri, $request, $options); + } + + if ($uri instanceof ControllerReference) { + $uri = $this->generateSignedFragmentUri($uri, $request); + } + + $alt = isset($options['alt']) ? $options['alt'] : null; + if ($alt instanceof ControllerReference) { + $alt = $this->generateSignedFragmentUri($alt, $request); + } + + $tag = $this->surrogate->renderIncludeTag($uri, $alt, isset($options['ignore_errors']) ? $options['ignore_errors'] : false, isset($options['comment']) ? $options['comment'] : ''); + + return new Response($tag); + } + + private function generateSignedFragmentUri($uri, Request $request) + { + if (null === $this->signer) { + throw new \LogicException('You must use a URI when using the ESI rendering strategy or set a URL signer.'); + } + + // we need to sign the absolute URI, but want to return the path only. + $fragmentUri = $this->signer->sign($this->generateFragmentUri($uri, $request, true)); + + return substr($fragmentUri, strlen($request->getSchemeAndHttpHost())); + } + + private function containsNonScalars(array $values) + { + foreach ($values as $value) { + if (is_array($value) && $this->containsNonScalars($value)) { + return true; + } elseif (!is_scalar($value) && null !== $value) { + return true; + } + } + + return false; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php new file mode 100644 index 00000000..a4570e3b --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +/** + * Implements the ESI rendering strategy. + * + * @author Fabien Potencier + */ +class EsiFragmentRenderer extends AbstractSurrogateFragmentRenderer +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'esi'; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/FragmentHandler.php b/vendor/symfony/http-kernel/Fragment/FragmentHandler.php new file mode 100644 index 00000000..0d0a0424 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/FragmentHandler.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +/** + * Renders a URI that represents a resource fragment. + * + * This class handles the rendering of resource fragments that are included into + * a main resource. The handling of the rendering is managed by specialized renderers. + * + * @author Fabien Potencier + * + * @see FragmentRendererInterface + */ +class FragmentHandler +{ + private $debug; + private $renderers = array(); + private $requestStack; + + /** + * Constructor. + * + * @param RequestStack $requestStack The Request stack that controls the lifecycle of requests + * @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances + * @param bool $debug Whether the debug mode is enabled or not + */ + public function __construct(RequestStack $requestStack, array $renderers = array(), $debug = false) + { + $this->requestStack = $requestStack; + foreach ($renderers as $renderer) { + $this->addRenderer($renderer); + } + $this->debug = $debug; + } + + /** + * Adds a renderer. + * + * @param FragmentRendererInterface $renderer A FragmentRendererInterface instance + */ + public function addRenderer(FragmentRendererInterface $renderer) + { + $this->renderers[$renderer->getName()] = $renderer; + } + + /** + * Renders a URI and returns the Response content. + * + * Available options: + * + * * ignore_errors: true to return an empty string in case of an error + * + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance + * @param string $renderer The renderer name + * @param array $options An array of options + * + * @return string|null The Response content or null when the Response is streamed + * + * @throws \InvalidArgumentException when the renderer does not exist + * @throws \LogicException when no master request is being handled + */ + public function render($uri, $renderer = 'inline', array $options = array()) + { + if (!isset($options['ignore_errors'])) { + $options['ignore_errors'] = !$this->debug; + } + + if (!isset($this->renderers[$renderer])) { + throw new \InvalidArgumentException(sprintf('The "%s" renderer does not exist.', $renderer)); + } + + if (!$request = $this->requestStack->getCurrentRequest()) { + throw new \LogicException('Rendering a fragment can only be done when handling a Request.'); + } + + return $this->deliver($this->renderers[$renderer]->render($uri, $request, $options)); + } + + /** + * Delivers the Response as a string. + * + * When the Response is a StreamedResponse, the content is streamed immediately + * instead of being returned. + * + * @param Response $response A Response instance + * + * @return string|null The Response content or null when the Response is streamed + * + * @throws \RuntimeException when the Response is not successful + */ + protected function deliver(Response $response) + { + if (!$response->isSuccessful()) { + throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $this->requestStack->getCurrentRequest()->getUri(), $response->getStatusCode())); + } + + if (!$response instanceof StreamedResponse) { + return $response->getContent(); + } + + $response->sendContent(); + } +} diff --git a/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php b/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php new file mode 100644 index 00000000..b177c3ac --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpFoundation\Response; + +/** + * Interface implemented by all rendering strategies. + * + * @author Fabien Potencier + */ +interface FragmentRendererInterface +{ + /** + * Renders a URI and returns the Response content. + * + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance + * @param Request $request A Request instance + * @param array $options An array of options + * + * @return Response A Response instance + */ + public function render($uri, Request $request, array $options = array()); + + /** + * Gets the name of the strategy. + * + * @return string The strategy name + */ + public function getName(); +} diff --git a/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php new file mode 100644 index 00000000..ec2a4071 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Templating\EngineInterface; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\UriSigner; +use Twig\Environment; +use Twig\Error\LoaderError; +use Twig\Loader\ExistsLoaderInterface; + +/** + * Implements the Hinclude rendering strategy. + * + * @author Fabien Potencier + */ +class HIncludeFragmentRenderer extends RoutableFragmentRenderer +{ + private $globalDefaultTemplate; + private $signer; + private $templating; + private $charset; + + /** + * Constructor. + * + * @param EngineInterface|Environment $templating An EngineInterface or a Twig instance + * @param UriSigner $signer A UriSigner instance + * @param string $globalDefaultTemplate The global default content (it can be a template name or the content) + * @param string $charset + */ + public function __construct($templating = null, UriSigner $signer = null, $globalDefaultTemplate = null, $charset = 'utf-8') + { + $this->setTemplating($templating); + $this->globalDefaultTemplate = $globalDefaultTemplate; + $this->signer = $signer; + $this->charset = $charset; + } + + /** + * Sets the templating engine to use to render the default content. + * + * @param EngineInterface|Environment|null $templating An EngineInterface or an Environment instance + * + * @throws \InvalidArgumentException + */ + public function setTemplating($templating) + { + if (null !== $templating && !$templating instanceof EngineInterface && !$templating instanceof Environment) { + throw new \InvalidArgumentException('The hinclude rendering strategy needs an instance of Twig\Environment or Symfony\Component\Templating\EngineInterface'); + } + + $this->templating = $templating; + } + + /** + * Checks if a templating engine has been set. + * + * @return bool true if the templating engine has been set, false otherwise + */ + public function hasTemplating() + { + return null !== $this->templating; + } + + /** + * {@inheritdoc} + * + * Additional available options: + * + * * default: The default content (it can be a template name or the content) + * * id: An optional hx:include tag id attribute + * * attributes: An optional array of hx:include tag attributes + */ + public function render($uri, Request $request, array $options = array()) + { + if ($uri instanceof ControllerReference) { + if (null === $this->signer) { + throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy or set a URL signer.'); + } + + // we need to sign the absolute URI, but want to return the path only. + $uri = substr($this->signer->sign($this->generateFragmentUri($uri, $request, true)), strlen($request->getSchemeAndHttpHost())); + } + + // We need to replace ampersands in the URI with the encoded form in order to return valid html/xml content. + $uri = str_replace('&', '&', $uri); + + $template = isset($options['default']) ? $options['default'] : $this->globalDefaultTemplate; + if (null !== $this->templating && $template && $this->templateExists($template)) { + $content = $this->templating->render($template); + } else { + $content = $template; + } + + $attributes = isset($options['attributes']) && is_array($options['attributes']) ? $options['attributes'] : array(); + if (isset($options['id']) && $options['id']) { + $attributes['id'] = $options['id']; + } + $renderedAttributes = ''; + if (count($attributes) > 0) { + $flags = ENT_QUOTES | ENT_SUBSTITUTE; + foreach ($attributes as $attribute => $value) { + $renderedAttributes .= sprintf( + ' %s="%s"', + htmlspecialchars($attribute, $flags, $this->charset, false), + htmlspecialchars($value, $flags, $this->charset, false) + ); + } + } + + return new Response(sprintf('%s', $uri, $renderedAttributes, $content)); + } + + /** + * @param string $template + * + * @return bool + */ + private function templateExists($template) + { + if ($this->templating instanceof EngineInterface) { + try { + return $this->templating->exists($template); + } catch (\InvalidArgumentException $e) { + return false; + } + } + + $loader = $this->templating->getLoader(); + if ($loader instanceof ExistsLoaderInterface || method_exists($loader, 'exists')) { + return $loader->exists($template); + } + + try { + if (method_exists($loader, 'getSourceContext')) { + $loader->getSourceContext($template); + } else { + $loader->getSource($template); + } + + return true; + } catch (LoaderError $e) { + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'hinclude'; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php new file mode 100644 index 00000000..437b40bf --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Implements the inline rendering strategy where the Request is rendered by the current HTTP kernel. + * + * @author Fabien Potencier + */ +class InlineFragmentRenderer extends RoutableFragmentRenderer +{ + private $kernel; + private $dispatcher; + + /** + * Constructor. + * + * @param HttpKernelInterface $kernel A HttpKernelInterface instance + * @param EventDispatcherInterface $dispatcher A EventDispatcherInterface instance + */ + public function __construct(HttpKernelInterface $kernel, EventDispatcherInterface $dispatcher = null) + { + $this->kernel = $kernel; + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + * + * Additional available options: + * + * * alt: an alternative URI to render in case of an error + */ + public function render($uri, Request $request, array $options = array()) + { + $reference = null; + if ($uri instanceof ControllerReference) { + $reference = $uri; + + // Remove attributes from the generated URI because if not, the Symfony + // routing system will use them to populate the Request attributes. We don't + // want that as we want to preserve objects (so we manually set Request attributes + // below instead) + $attributes = $reference->attributes; + $reference->attributes = array(); + + // The request format and locale might have been overridden by the user + foreach (array('_format', '_locale') as $key) { + if (isset($attributes[$key])) { + $reference->attributes[$key] = $attributes[$key]; + } + } + + $uri = $this->generateFragmentUri($uri, $request, false, false); + + $reference->attributes = array_merge($attributes, $reference->attributes); + } + + $subRequest = $this->createSubRequest($uri, $request); + + // override Request attributes as they can be objects (which are not supported by the generated URI) + if (null !== $reference) { + $subRequest->attributes->add($reference->attributes); + } + + $level = ob_get_level(); + try { + return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); + } catch (\Exception $e) { + // we dispatch the exception event to trigger the logging + // the response that comes back is simply ignored + if (isset($options['ignore_errors']) && $options['ignore_errors'] && $this->dispatcher) { + $event = new GetResponseForExceptionEvent($this->kernel, $request, HttpKernelInterface::SUB_REQUEST, $e); + + $this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event); + } + + // let's clean up the output buffers that were created by the sub-request + Response::closeOutputBuffers($level, false); + + if (isset($options['alt'])) { + $alt = $options['alt']; + unset($options['alt']); + + return $this->render($alt, $request, $options); + } + + if (!isset($options['ignore_errors']) || !$options['ignore_errors']) { + throw $e; + } + + return new Response(); + } + } + + protected function createSubRequest($uri, Request $request) + { + $cookies = $request->cookies->all(); + $server = $request->server->all(); + + // Override the arguments to emulate a sub-request. + // Sub-request object will point to localhost as client ip and real client ip + // will be included into trusted header for client ip + try { + if (Request::HEADER_X_FORWARDED_FOR & Request::getTrustedHeaderSet()) { + $currentXForwardedFor = $request->headers->get('X_FORWARDED_FOR', ''); + + $server['HTTP_X_FORWARDED_FOR'] = ($currentXForwardedFor ? $currentXForwardedFor.', ' : '').$request->getClientIp(); + } elseif (method_exists(Request::class, 'getTrustedHeaderName') && $trustedHeaderName = Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP, false)) { + $currentXForwardedFor = $request->headers->get($trustedHeaderName, ''); + + $server['HTTP_'.$trustedHeaderName] = ($currentXForwardedFor ? $currentXForwardedFor.', ' : '').$request->getClientIp(); + } + } catch (\InvalidArgumentException $e) { + // Do nothing + } + + $server['REMOTE_ADDR'] = '127.0.0.1'; + unset($server['HTTP_IF_MODIFIED_SINCE']); + unset($server['HTTP_IF_NONE_MATCH']); + + $subRequest = Request::create($uri, 'get', array(), $cookies, array(), $server); + if ($request->headers->has('Surrogate-Capability')) { + $subRequest->headers->set('Surrogate-Capability', $request->headers->get('Surrogate-Capability')); + } + + if ($session = $request->getSession()) { + $subRequest->setSession($session); + } + + return $subRequest; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'inline'; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php new file mode 100644 index 00000000..d7eeb89a --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\EventListener\FragmentListener; + +/** + * Adds the possibility to generate a fragment URI for a given Controller. + * + * @author Fabien Potencier + */ +abstract class RoutableFragmentRenderer implements FragmentRendererInterface +{ + private $fragmentPath = '/_fragment'; + + /** + * Sets the fragment path that triggers the fragment listener. + * + * @param string $path The path + * + * @see FragmentListener + */ + public function setFragmentPath($path) + { + $this->fragmentPath = $path; + } + + /** + * Generates a fragment URI for a given controller. + * + * @param ControllerReference $reference A ControllerReference instance + * @param Request $request A Request instance + * @param bool $absolute Whether to generate an absolute URL or not + * @param bool $strict Whether to allow non-scalar attributes or not + * + * @return string A fragment URI + */ + protected function generateFragmentUri(ControllerReference $reference, Request $request, $absolute = false, $strict = true) + { + if ($strict) { + $this->checkNonScalar($reference->attributes); + } + + // We need to forward the current _format and _locale values as we don't have + // a proper routing pattern to do the job for us. + // This makes things inconsistent if you switch from rendering a controller + // to rendering a route if the route pattern does not contain the special + // _format and _locale placeholders. + if (!isset($reference->attributes['_format'])) { + $reference->attributes['_format'] = $request->getRequestFormat(); + } + if (!isset($reference->attributes['_locale'])) { + $reference->attributes['_locale'] = $request->getLocale(); + } + + $reference->attributes['_controller'] = $reference->controller; + + $reference->query['_path'] = http_build_query($reference->attributes, '', '&'); + + $path = $this->fragmentPath.'?'.http_build_query($reference->query, '', '&'); + + if ($absolute) { + return $request->getUriForPath($path); + } + + return $request->getBaseUrl().$path; + } + + private function checkNonScalar($values) + { + foreach ($values as $key => $value) { + if (is_array($value)) { + $this->checkNonScalar($value); + } elseif (!is_scalar($value) && null !== $value) { + throw new \LogicException(sprintf('Controller attributes cannot contain non-scalar/non-null values (value for key "%s" is not a scalar or null).', $key)); + } + } + } +} diff --git a/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php new file mode 100644 index 00000000..45e7122f --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +/** + * Implements the SSI rendering strategy. + * + * @author Sebastian Krebs + */ +class SsiFragmentRenderer extends AbstractSurrogateFragmentRenderer +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ssi'; + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php b/vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php new file mode 100644 index 00000000..af94bea9 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Abstract class implementing Surrogate capabilities to Request and Response instances. + * + * @author Fabien Potencier + * @author Robin Chalas + */ +abstract class AbstractSurrogate implements SurrogateInterface +{ + protected $contentTypes; + protected $phpEscapeMap = array( + array('', '', '', ''), + ); + + /** + * Constructor. + * + * @param array $contentTypes An array of content-type that should be parsed for Surrogate information + * (default: text/html, text/xml, application/xhtml+xml, and application/xml) + */ + public function __construct(array $contentTypes = array('text/html', 'text/xml', 'application/xhtml+xml', 'application/xml')) + { + $this->contentTypes = $contentTypes; + } + + /** + * Returns a new cache strategy instance. + * + * @return ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance + */ + public function createCacheStrategy() + { + return new ResponseCacheStrategy(); + } + + /** + * {@inheritdoc} + */ + public function hasSurrogateCapability(Request $request) + { + if (null === $value = $request->headers->get('Surrogate-Capability')) { + return false; + } + + return false !== strpos($value, sprintf('%s/1.0', strtoupper($this->getName()))); + } + + /** + * {@inheritdoc} + */ + public function addSurrogateCapability(Request $request) + { + $current = $request->headers->get('Surrogate-Capability'); + $new = sprintf('symfony="%s/1.0"', strtoupper($this->getName())); + + $request->headers->set('Surrogate-Capability', $current ? $current.', '.$new : $new); + } + + /** + * {@inheritdoc} + */ + public function needsParsing(Response $response) + { + if (!$control = $response->headers->get('Surrogate-Control')) { + return false; + } + + $pattern = sprintf('#content="[^"]*%s/1.0[^"]*"#', strtoupper($this->getName())); + + return (bool) preg_match($pattern, $control); + } + + /** + * {@inheritdoc} + */ + public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors) + { + $subRequest = Request::create($uri, Request::METHOD_GET, array(), $cache->getRequest()->cookies->all(), array(), $cache->getRequest()->server->all()); + + try { + $response = $cache->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true); + + if (!$response->isSuccessful()) { + throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $subRequest->getUri(), $response->getStatusCode())); + } + + return $response->getContent(); + } catch (\Exception $e) { + if ($alt) { + return $this->handle($cache, $alt, '', $ignoreErrors); + } + + if (!$ignoreErrors) { + throw $e; + } + } + } + + /** + * Remove the Surrogate from the Surrogate-Control header. + * + * @param Response $response + */ + protected function removeFromControl(Response $response) + { + if (!$response->headers->has('Surrogate-Control')) { + return; + } + + $value = $response->headers->get('Surrogate-Control'); + $upperName = strtoupper($this->getName()); + + if (sprintf('content="%s/1.0"', $upperName) == $value) { + $response->headers->remove('Surrogate-Control'); + } elseif (preg_match(sprintf('#,\s*content="%s/1.0"#', $upperName), $value)) { + $response->headers->set('Surrogate-Control', preg_replace(sprintf('#,\s*content="%s/1.0"#', $upperName), '', $value)); + } elseif (preg_match(sprintf('#content="%s/1.0",\s*#', $upperName), $value)) { + $response->headers->set('Surrogate-Control', preg_replace(sprintf('#content="%s/1.0",\s*#', $upperName), '', $value)); + } + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/Esi.php b/vendor/symfony/http-kernel/HttpCache/Esi.php new file mode 100644 index 00000000..d09907ea --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Esi.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Esi implements the ESI capabilities to Request and Response instances. + * + * For more information, read the following W3C notes: + * + * * ESI Language Specification 1.0 (http://www.w3.org/TR/esi-lang) + * + * * Edge Architecture Specification (http://www.w3.org/TR/edge-arch) + * + * @author Fabien Potencier + */ +class Esi extends AbstractSurrogate +{ + public function getName() + { + return 'esi'; + } + + /** + * {@inheritdoc} + */ + public function addSurrogateControl(Response $response) + { + if (false !== strpos($response->getContent(), 'headers->set('Surrogate-Control', 'content="ESI/1.0"'); + } + } + + /** + * {@inheritdoc} + */ + public function renderIncludeTag($uri, $alt = null, $ignoreErrors = true, $comment = '') + { + $html = sprintf('', + $uri, + $ignoreErrors ? ' onerror="continue"' : '', + $alt ? sprintf(' alt="%s"', $alt) : '' + ); + + if (!empty($comment)) { + return sprintf("\n%s", $comment, $html); + } + + return $html; + } + + /** + * {@inheritdoc} + */ + public function process(Request $request, Response $response) + { + $type = $response->headers->get('Content-Type'); + if (empty($type)) { + $type = 'text/html'; + } + + $parts = explode(';', $type); + if (!in_array($parts[0], $this->contentTypes)) { + return $response; + } + + // we don't use a proper XML parser here as we can have ESI tags in a plain text response + $content = $response->getContent(); + $content = preg_replace('#.*?#s', '', $content); + $content = preg_replace('#]+>#s', '', $content); + + $chunks = preg_split('##', $content, -1, PREG_SPLIT_DELIM_CAPTURE); + $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]); + + $i = 1; + while (isset($chunks[$i])) { + $options = array(); + preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $chunks[$i], $matches, PREG_SET_ORDER); + foreach ($matches as $set) { + $options[$set[1]] = $set[2]; + } + + if (!isset($options['src'])) { + throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.'); + } + + $chunks[$i] = sprintf('surrogate->handle($this, %s, %s, %s) ?>'."\n", + var_export($options['src'], true), + var_export(isset($options['alt']) ? $options['alt'] : '', true), + isset($options['onerror']) && 'continue' === $options['onerror'] ? 'true' : 'false' + ); + ++$i; + $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]); + ++$i; + } + $content = implode('', $chunks); + + $response->setContent($content); + $response->headers->set('X-Body-Eval', 'ESI'); + + // remove ESI/1.0 from the Surrogate-Control header + $this->removeFromControl($response); + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/HttpCache.php b/vendor/symfony/http-kernel/HttpCache/HttpCache.php new file mode 100644 index 00000000..be2bc63d --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/HttpCache.php @@ -0,0 +1,737 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\TerminableInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Cache provides HTTP caching. + * + * @author Fabien Potencier + */ +class HttpCache implements HttpKernelInterface, TerminableInterface +{ + private $kernel; + private $store; + private $request; + private $surrogate; + private $surrogateCacheStrategy; + private $options = array(); + private $traces = array(); + + /** + * Constructor. + * + * The available options are: + * + * * debug: If true, the traces are added as a HTTP header to ease debugging + * + * * default_ttl The number of seconds that a cache entry should be considered + * fresh when no explicit freshness information is provided in + * a response. Explicit Cache-Control or Expires headers + * override this value. (default: 0) + * + * * private_headers Set of request headers that trigger "private" cache-control behavior + * on responses that don't explicitly state whether the response is + * public or private via a Cache-Control directive. (default: Authorization and Cookie) + * + * * allow_reload Specifies whether the client can force a cache reload by including a + * Cache-Control "no-cache" directive in the request. Set it to ``true`` + * for compliance with RFC 2616. (default: false) + * + * * allow_revalidate Specifies whether the client can force a cache revalidate by including + * a Cache-Control "max-age=0" directive in the request. Set it to ``true`` + * for compliance with RFC 2616. (default: false) + * + * * stale_while_revalidate Specifies the default number of seconds (the granularity is the second as the + * Response TTL precision is a second) during which the cache can immediately return + * a stale response while it revalidates it in the background (default: 2). + * This setting is overridden by the stale-while-revalidate HTTP Cache-Control + * extension (see RFC 5861). + * + * * stale_if_error Specifies the default number of seconds (the granularity is the second) during which + * the cache can serve a stale response when an error is encountered (default: 60). + * This setting is overridden by the stale-if-error HTTP Cache-Control extension + * (see RFC 5861). + * + * @param HttpKernelInterface $kernel An HttpKernelInterface instance + * @param StoreInterface $store A Store instance + * @param SurrogateInterface $surrogate A SurrogateInterface instance + * @param array $options An array of options + */ + public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = array()) + { + $this->store = $store; + $this->kernel = $kernel; + $this->surrogate = $surrogate; + + // needed in case there is a fatal error because the backend is too slow to respond + register_shutdown_function(array($this->store, 'cleanup')); + + $this->options = array_merge(array( + 'debug' => false, + 'default_ttl' => 0, + 'private_headers' => array('Authorization', 'Cookie'), + 'allow_reload' => false, + 'allow_revalidate' => false, + 'stale_while_revalidate' => 2, + 'stale_if_error' => 60, + ), $options); + } + + /** + * Gets the current store. + * + * @return StoreInterface $store A StoreInterface instance + */ + public function getStore() + { + return $this->store; + } + + /** + * Returns an array of events that took place during processing of the last request. + * + * @return array An array of events + */ + public function getTraces() + { + return $this->traces; + } + + /** + * Returns a log message for the events of the last request processing. + * + * @return string A log message + */ + public function getLog() + { + $log = array(); + foreach ($this->traces as $request => $traces) { + $log[] = sprintf('%s: %s', $request, implode(', ', $traces)); + } + + return implode('; ', $log); + } + + /** + * Gets the Request instance associated with the master request. + * + * @return Request A Request instance + */ + public function getRequest() + { + return $this->request; + } + + /** + * Gets the Kernel instance. + * + * @return HttpKernelInterface An HttpKernelInterface instance + */ + public function getKernel() + { + return $this->kernel; + } + + /** + * Gets the Surrogate instance. + * + * @return SurrogateInterface A Surrogate instance + * + * @throws \LogicException + */ + public function getSurrogate() + { + return $this->surrogate; + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + // FIXME: catch exceptions and implement a 500 error page here? -> in Varnish, there is a built-in error page mechanism + if (HttpKernelInterface::MASTER_REQUEST === $type) { + $this->traces = array(); + $this->request = $request; + if (null !== $this->surrogate) { + $this->surrogateCacheStrategy = $this->surrogate->createCacheStrategy(); + } + } + + $this->traces[$this->getTraceKey($request)] = array(); + + if (!$request->isMethodSafe(false)) { + $response = $this->invalidate($request, $catch); + } elseif ($request->headers->has('expect') || !$request->isMethodCacheable()) { + $response = $this->pass($request, $catch); + } elseif ($this->options['allow_reload'] && $request->isNoCache()) { + /* + If allow_reload is configured and the client requests "Cache-Control: no-cache", + reload the cache by fetching a fresh response and caching it (if possible). + */ + $this->record($request, 'reload'); + $response = $this->fetch($request, $catch); + } else { + $response = $this->lookup($request, $catch); + } + + $this->restoreResponseBody($request, $response); + + if (HttpKernelInterface::MASTER_REQUEST === $type && $this->options['debug']) { + $response->headers->set('X-Symfony-Cache', $this->getLog()); + } + + if (null !== $this->surrogate) { + if (HttpKernelInterface::MASTER_REQUEST === $type) { + $this->surrogateCacheStrategy->update($response); + } else { + $this->surrogateCacheStrategy->add($response); + } + } + + $response->prepare($request); + + $response->isNotModified($request); + + return $response; + } + + /** + * {@inheritdoc} + */ + public function terminate(Request $request, Response $response) + { + if ($this->getKernel() instanceof TerminableInterface) { + $this->getKernel()->terminate($request, $response); + } + } + + /** + * Forwards the Request to the backend without storing the Response in the cache. + * + * @param Request $request A Request instance + * @param bool $catch Whether to process exceptions + * + * @return Response A Response instance + */ + protected function pass(Request $request, $catch = false) + { + $this->record($request, 'pass'); + + return $this->forward($request, $catch); + } + + /** + * Invalidates non-safe methods (like POST, PUT, and DELETE). + * + * @param Request $request A Request instance + * @param bool $catch Whether to process exceptions + * + * @return Response A Response instance + * + * @throws \Exception + * + * @see RFC2616 13.10 + */ + protected function invalidate(Request $request, $catch = false) + { + $response = $this->pass($request, $catch); + + // invalidate only when the response is successful + if ($response->isSuccessful() || $response->isRedirect()) { + try { + $this->store->invalidate($request); + + // As per the RFC, invalidate Location and Content-Location URLs if present + foreach (array('Location', 'Content-Location') as $header) { + if ($uri = $response->headers->get($header)) { + $subRequest = Request::create($uri, 'get', array(), array(), array(), $request->server->all()); + + $this->store->invalidate($subRequest); + } + } + + $this->record($request, 'invalidate'); + } catch (\Exception $e) { + $this->record($request, 'invalidate-failed'); + + if ($this->options['debug']) { + throw $e; + } + } + } + + return $response; + } + + /** + * Lookups a Response from the cache for the given Request. + * + * When a matching cache entry is found and is fresh, it uses it as the + * response without forwarding any request to the backend. When a matching + * cache entry is found but is stale, it attempts to "validate" the entry with + * the backend using conditional GET. When no matching cache entry is found, + * it triggers "miss" processing. + * + * @param Request $request A Request instance + * @param bool $catch whether to process exceptions + * + * @return Response A Response instance + * + * @throws \Exception + */ + protected function lookup(Request $request, $catch = false) + { + try { + $entry = $this->store->lookup($request); + } catch (\Exception $e) { + $this->record($request, 'lookup-failed'); + + if ($this->options['debug']) { + throw $e; + } + + return $this->pass($request, $catch); + } + + if (null === $entry) { + $this->record($request, 'miss'); + + return $this->fetch($request, $catch); + } + + if (!$this->isFreshEnough($request, $entry)) { + $this->record($request, 'stale'); + + return $this->validate($request, $entry, $catch); + } + + $this->record($request, 'fresh'); + + $entry->headers->set('Age', $entry->getAge()); + + return $entry; + } + + /** + * Validates that a cache entry is fresh. + * + * The original request is used as a template for a conditional + * GET request with the backend. + * + * @param Request $request A Request instance + * @param Response $entry A Response instance to validate + * @param bool $catch Whether to process exceptions + * + * @return Response A Response instance + */ + protected function validate(Request $request, Response $entry, $catch = false) + { + $subRequest = clone $request; + + // send no head requests because we want content + if ('HEAD' === $request->getMethod()) { + $subRequest->setMethod('GET'); + } + + // add our cached last-modified validator + $subRequest->headers->set('if_modified_since', $entry->headers->get('Last-Modified')); + + // Add our cached etag validator to the environment. + // We keep the etags from the client to handle the case when the client + // has a different private valid entry which is not cached here. + $cachedEtags = $entry->getEtag() ? array($entry->getEtag()) : array(); + $requestEtags = $request->getETags(); + if ($etags = array_unique(array_merge($cachedEtags, $requestEtags))) { + $subRequest->headers->set('if_none_match', implode(', ', $etags)); + } + + $response = $this->forward($subRequest, $catch, $entry); + + if (304 == $response->getStatusCode()) { + $this->record($request, 'valid'); + + // return the response and not the cache entry if the response is valid but not cached + $etag = $response->getEtag(); + if ($etag && in_array($etag, $requestEtags) && !in_array($etag, $cachedEtags)) { + return $response; + } + + $entry = clone $entry; + $entry->headers->remove('Date'); + + foreach (array('Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified') as $name) { + if ($response->headers->has($name)) { + $entry->headers->set($name, $response->headers->get($name)); + } + } + + $response = $entry; + } else { + $this->record($request, 'invalid'); + } + + if ($response->isCacheable()) { + $this->store($request, $response); + } + + return $response; + } + + /** + * Unconditionally fetches a fresh response from the backend and + * stores it in the cache if is cacheable. + * + * @param Request $request A Request instance + * @param bool $catch whether to process exceptions + * + * @return Response A Response instance + */ + protected function fetch(Request $request, $catch = false) + { + $subRequest = clone $request; + + // send no head requests because we want content + if ('HEAD' === $request->getMethod()) { + $subRequest->setMethod('GET'); + } + + // avoid that the backend sends no content + $subRequest->headers->remove('if_modified_since'); + $subRequest->headers->remove('if_none_match'); + + $response = $this->forward($subRequest, $catch); + + if ($response->isCacheable()) { + $this->store($request, $response); + } + + return $response; + } + + /** + * Forwards the Request to the backend and returns the Response. + * + * All backend requests (cache passes, fetches, cache validations) + * run through this method. + * + * @param Request $request A Request instance + * @param bool $catch Whether to catch exceptions or not + * @param Response $entry A Response instance (the stale entry if present, null otherwise) + * + * @return Response A Response instance + */ + protected function forward(Request $request, $catch = false, Response $entry = null) + { + if ($this->surrogate) { + $this->surrogate->addSurrogateCapability($request); + } + + // modify the X-Forwarded-For header if needed + $forwardedFor = $request->headers->get('X-Forwarded-For'); + if ($forwardedFor) { + $request->headers->set('X-Forwarded-For', $forwardedFor.', '.$request->server->get('REMOTE_ADDR')); + } else { + $request->headers->set('X-Forwarded-For', $request->server->get('REMOTE_ADDR')); + } + + // fix the client IP address by setting it to 127.0.0.1 as HttpCache + // is always called from the same process as the backend. + $request->server->set('REMOTE_ADDR', '127.0.0.1'); + + // make sure HttpCache is a trusted proxy + if (!in_array('127.0.0.1', $trustedProxies = Request::getTrustedProxies())) { + $trustedProxies[] = '127.0.0.1'; + Request::setTrustedProxies($trustedProxies, Request::HEADER_X_FORWARDED_ALL); + } + + // always a "master" request (as the real master request can be in cache) + $response = $this->kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, $catch); + // FIXME: we probably need to also catch exceptions if raw === true + + // we don't implement the stale-if-error on Requests, which is nonetheless part of the RFC + if (null !== $entry && in_array($response->getStatusCode(), array(500, 502, 503, 504))) { + if (null === $age = $entry->headers->getCacheControlDirective('stale-if-error')) { + $age = $this->options['stale_if_error']; + } + + if (abs($entry->getTtl()) < $age) { + $this->record($request, 'stale-if-error'); + + return $entry; + } + } + + /* + RFC 7231 Sect. 7.1.1.2 says that a server that does not have a reasonably accurate + clock MUST NOT send a "Date" header, although it MUST send one in most other cases + except for 1xx or 5xx responses where it MAY do so. + + Anyway, a client that received a message without a "Date" header MUST add it. + */ + if (!$response->headers->has('Date')) { + $response->setDate(\DateTime::createFromFormat('U', time())); + } + + $this->processResponseBody($request, $response); + + if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) { + $response->setPrivate(); + } elseif ($this->options['default_ttl'] > 0 && null === $response->getTtl() && !$response->headers->getCacheControlDirective('must-revalidate')) { + $response->setTtl($this->options['default_ttl']); + } + + return $response; + } + + /** + * Checks whether the cache entry is "fresh enough" to satisfy the Request. + * + * @param Request $request A Request instance + * @param Response $entry A Response instance + * + * @return bool true if the cache entry if fresh enough, false otherwise + */ + protected function isFreshEnough(Request $request, Response $entry) + { + if (!$entry->isFresh()) { + return $this->lock($request, $entry); + } + + if ($this->options['allow_revalidate'] && null !== $maxAge = $request->headers->getCacheControlDirective('max-age')) { + return $maxAge > 0 && $maxAge >= $entry->getAge(); + } + + return true; + } + + /** + * Locks a Request during the call to the backend. + * + * @param Request $request A Request instance + * @param Response $entry A Response instance + * + * @return bool true if the cache entry can be returned even if it is staled, false otherwise + */ + protected function lock(Request $request, Response $entry) + { + // try to acquire a lock to call the backend + $lock = $this->store->lock($request); + + if (true === $lock) { + // we have the lock, call the backend + return false; + } + + // there is already another process calling the backend + + // May we serve a stale response? + if ($this->mayServeStaleWhileRevalidate($entry)) { + $this->record($request, 'stale-while-revalidate'); + + return true; + } + + // wait for the lock to be released + if ($this->waitForLock($request)) { + // replace the current entry with the fresh one + $new = $this->lookup($request); + $entry->headers = $new->headers; + $entry->setContent($new->getContent()); + $entry->setStatusCode($new->getStatusCode()); + $entry->setProtocolVersion($new->getProtocolVersion()); + foreach ($new->headers->getCookies() as $cookie) { + $entry->headers->setCookie($cookie); + } + } else { + // backend is slow as hell, send a 503 response (to avoid the dog pile effect) + $entry->setStatusCode(503); + $entry->setContent('503 Service Unavailable'); + $entry->headers->set('Retry-After', 10); + } + + return true; + } + + /** + * Writes the Response to the cache. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * + * @throws \Exception + */ + protected function store(Request $request, Response $response) + { + try { + $this->store->write($request, $response); + + $this->record($request, 'store'); + + $response->headers->set('Age', $response->getAge()); + } catch (\Exception $e) { + $this->record($request, 'store-failed'); + + if ($this->options['debug']) { + throw $e; + } + } + + // now that the response is cached, release the lock + $this->store->unlock($request); + } + + /** + * Restores the Response body. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + */ + private function restoreResponseBody(Request $request, Response $response) + { + if ($request->isMethod('HEAD') || 304 === $response->getStatusCode()) { + $response->setContent(null); + $response->headers->remove('X-Body-Eval'); + $response->headers->remove('X-Body-File'); + + return; + } + + if ($response->headers->has('X-Body-Eval')) { + ob_start(); + + if ($response->headers->has('X-Body-File')) { + include $response->headers->get('X-Body-File'); + } else { + eval('; ?>'.$response->getContent().'setContent(ob_get_clean()); + $response->headers->remove('X-Body-Eval'); + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', strlen($response->getContent())); + } + } elseif ($response->headers->has('X-Body-File')) { + $response->setContent(file_get_contents($response->headers->get('X-Body-File'))); + } else { + return; + } + + $response->headers->remove('X-Body-File'); + } + + protected function processResponseBody(Request $request, Response $response) + { + if (null !== $this->surrogate && $this->surrogate->needsParsing($response)) { + $this->surrogate->process($request, $response); + } + } + + /** + * Checks if the Request includes authorization or other sensitive information + * that should cause the Response to be considered private by default. + * + * @param Request $request A Request instance + * + * @return bool true if the Request is private, false otherwise + */ + private function isPrivateRequest(Request $request) + { + foreach ($this->options['private_headers'] as $key) { + $key = strtolower(str_replace('HTTP_', '', $key)); + + if ('cookie' === $key) { + if (count($request->cookies->all())) { + return true; + } + } elseif ($request->headers->has($key)) { + return true; + } + } + + return false; + } + + /** + * Records that an event took place. + * + * @param Request $request A Request instance + * @param string $event The event name + */ + private function record(Request $request, $event) + { + $this->traces[$this->getTraceKey($request)][] = $event; + } + + /** + * Calculates the key we use in the "trace" array for a given request. + * + * @param Request $request + * + * @return string + */ + private function getTraceKey(Request $request) + { + $path = $request->getPathInfo(); + if ($qs = $request->getQueryString()) { + $path .= '?'.$qs; + } + + return $request->getMethod().' '.$path; + } + + /** + * Checks whether the given (cached) response may be served as "stale" when a revalidation + * is currently in progress. + * + * @param Response $entry + * + * @return bool True when the stale response may be served, false otherwise. + */ + private function mayServeStaleWhileRevalidate(Response $entry) + { + $timeout = $entry->headers->getCacheControlDirective('stale-while-revalidate'); + + if (null === $timeout) { + $timeout = $this->options['stale_while_revalidate']; + } + + return abs($entry->getTtl()) < $timeout; + } + + /** + * Waits for the store to release a locked entry. + * + * @param Request $request The request to wait for + * + * @return bool True if the lock was released before the internal timeout was hit; false if the wait timeout was exceeded. + */ + private function waitForLock(Request $request) + { + $wait = 0; + while ($this->store->isLocked($request) && $wait < 5000000) { + usleep(50000); + $wait += 50000; + } + + return $wait < 5000000; + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php new file mode 100644 index 00000000..027b2b17 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php @@ -0,0 +1,96 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Response; + +/** + * ResponseCacheStrategy knows how to compute the Response cache HTTP header + * based on the different response cache headers. + * + * This implementation changes the master response TTL to the smallest TTL received + * or force validation if one of the surrogates has validation cache strategy. + * + * @author Fabien Potencier + */ +class ResponseCacheStrategy implements ResponseCacheStrategyInterface +{ + private $cacheable = true; + private $embeddedResponses = 0; + private $ttls = array(); + private $maxAges = array(); + private $isNotCacheableResponseEmbedded = false; + + /** + * {@inheritdoc} + */ + public function add(Response $response) + { + if (!$response->isFresh() || !$response->isCacheable()) { + $this->cacheable = false; + } else { + $maxAge = $response->getMaxAge(); + $this->ttls[] = $response->getTtl(); + $this->maxAges[] = $maxAge; + + if (null === $maxAge) { + $this->isNotCacheableResponseEmbedded = true; + } + } + + ++$this->embeddedResponses; + } + + /** + * {@inheritdoc} + */ + public function update(Response $response) + { + // if we have no embedded Response, do nothing + if (0 === $this->embeddedResponses) { + return; + } + + // Remove validation related headers in order to avoid browsers using + // their own cache, because some of the response content comes from + // at least one embedded response (which likely has a different caching strategy). + if ($response->isValidateable()) { + $response->setEtag(null); + $response->setLastModified(null); + } + + if (!$response->isFresh()) { + $this->cacheable = false; + } + + if (!$this->cacheable) { + $response->headers->set('Cache-Control', 'no-cache, must-revalidate'); + + return; + } + + $this->ttls[] = $response->getTtl(); + $this->maxAges[] = $response->getMaxAge(); + + if ($this->isNotCacheableResponseEmbedded) { + $response->headers->removeCacheControlDirective('s-maxage'); + } elseif (null !== $maxAge = min($this->maxAges)) { + $response->setSharedMaxAge($maxAge); + $response->headers->set('Age', $maxAge - min($this->ttls)); + } + $response->setMaxAge(0); + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php new file mode 100644 index 00000000..d70c2e06 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php @@ -0,0 +1,41 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Response; + +/** + * ResponseCacheStrategyInterface implementations know how to compute the + * Response cache HTTP header based on the different response cache headers. + * + * @author Fabien Potencier + */ +interface ResponseCacheStrategyInterface +{ + /** + * Adds a Response. + * + * @param Response $response + */ + public function add(Response $response); + + /** + * Updates the Response HTTP headers based on the embedded Responses. + * + * @param Response $response + */ + public function update(Response $response); +} diff --git a/vendor/symfony/http-kernel/HttpCache/Ssi.php b/vendor/symfony/http-kernel/HttpCache/Ssi.php new file mode 100644 index 00000000..3178c335 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Ssi.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Ssi implements the SSI capabilities to Request and Response instances. + * + * @author Sebastian Krebs + */ +class Ssi extends AbstractSurrogate +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ssi'; + } + + /** + * {@inheritdoc} + */ + public function addSurrogateControl(Response $response) + { + if (false !== strpos($response->getContent(), '', $uri); + } + + /** + * {@inheritdoc} + */ + public function process(Request $request, Response $response) + { + $type = $response->headers->get('Content-Type'); + if (empty($type)) { + $type = 'text/html'; + } + + $parts = explode(';', $type); + if (!in_array($parts[0], $this->contentTypes)) { + return $response; + } + + // we don't use a proper XML parser here as we can have SSI tags in a plain text response + $content = $response->getContent(); + + $chunks = preg_split('##', $content, -1, PREG_SPLIT_DELIM_CAPTURE); + $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]); + + $i = 1; + while (isset($chunks[$i])) { + $options = array(); + preg_match_all('/(virtual)="([^"]*?)"/', $chunks[$i], $matches, PREG_SET_ORDER); + foreach ($matches as $set) { + $options[$set[1]] = $set[2]; + } + + if (!isset($options['virtual'])) { + throw new \RuntimeException('Unable to process an SSI tag without a "virtual" attribute.'); + } + + $chunks[$i] = sprintf('surrogate->handle($this, %s, \'\', false) ?>'."\n", + var_export($options['virtual'], true) + ); + ++$i; + $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]); + ++$i; + } + $content = implode('', $chunks); + + $response->setContent($content); + $response->headers->set('X-Body-Eval', 'SSI'); + + // remove SSI/1.0 from the Surrogate-Control header + $this->removeFromControl($response); + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/Store.php b/vendor/symfony/http-kernel/HttpCache/Store.php new file mode 100644 index 00000000..c4d961e6 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Store.php @@ -0,0 +1,508 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Store implements all the logic for storing cache metadata (Request and Response headers). + * + * @author Fabien Potencier + */ +class Store implements StoreInterface +{ + protected $root; + private $keyCache; + private $locks; + + /** + * Constructor. + * + * @param string $root The path to the cache directory + * + * @throws \RuntimeException + */ + public function __construct($root) + { + $this->root = $root; + if (!file_exists($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) { + throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root)); + } + $this->keyCache = new \SplObjectStorage(); + $this->locks = array(); + } + + /** + * Cleanups storage. + */ + public function cleanup() + { + // unlock everything + foreach ($this->locks as $lock) { + flock($lock, LOCK_UN); + fclose($lock); + } + + $this->locks = array(); + } + + /** + * Tries to lock the cache for a given Request, without blocking. + * + * @param Request $request A Request instance + * + * @return bool|string true if the lock is acquired, the path to the current lock otherwise + */ + public function lock(Request $request) + { + $key = $this->getCacheKey($request); + + if (!isset($this->locks[$key])) { + $path = $this->getPath($key); + if (!file_exists(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) { + return $path; + } + $h = fopen($path, 'cb'); + if (!flock($h, LOCK_EX | LOCK_NB)) { + fclose($h); + + return $path; + } + + $this->locks[$key] = $h; + } + + return true; + } + + /** + * Releases the lock for the given Request. + * + * @param Request $request A Request instance + * + * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise + */ + public function unlock(Request $request) + { + $key = $this->getCacheKey($request); + + if (isset($this->locks[$key])) { + flock($this->locks[$key], LOCK_UN); + fclose($this->locks[$key]); + unset($this->locks[$key]); + + return true; + } + + return false; + } + + public function isLocked(Request $request) + { + $key = $this->getCacheKey($request); + + if (isset($this->locks[$key])) { + return true; // shortcut if lock held by this process + } + + if (!file_exists($path = $this->getPath($key))) { + return false; + } + + $h = fopen($path, 'rb'); + flock($h, LOCK_EX | LOCK_NB, $wouldBlock); + flock($h, LOCK_UN); // release the lock we just acquired + fclose($h); + + return (bool) $wouldBlock; + } + + /** + * Locates a cached Response for the Request provided. + * + * @param Request $request A Request instance + * + * @return Response|null A Response instance, or null if no cache entry was found + */ + public function lookup(Request $request) + { + $key = $this->getCacheKey($request); + + if (!$entries = $this->getMetadata($key)) { + return; + } + + // find a cached entry that matches the request. + $match = null; + foreach ($entries as $entry) { + if ($this->requestsMatch(isset($entry[1]['vary'][0]) ? implode(', ', $entry[1]['vary']) : '', $request->headers->all(), $entry[0])) { + $match = $entry; + + break; + } + } + + if (null === $match) { + return; + } + + list($req, $headers) = $match; + if (file_exists($body = $this->getPath($headers['x-content-digest'][0]))) { + return $this->restoreResponse($headers, $body); + } + + // TODO the metaStore referenced an entity that doesn't exist in + // the entityStore. We definitely want to return nil but we should + // also purge the entry from the meta-store when this is detected. + } + + /** + * Writes a cache entry to the store for the given Request and Response. + * + * Existing entries are read and any that match the response are removed. This + * method calls write with the new list of cache entries. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * + * @return string The key under which the response is stored + * + * @throws \RuntimeException + */ + public function write(Request $request, Response $response) + { + $key = $this->getCacheKey($request); + $storedEnv = $this->persistRequest($request); + + // write the response body to the entity store if this is the original response + if (!$response->headers->has('X-Content-Digest')) { + $digest = $this->generateContentDigest($response); + + if (false === $this->save($digest, $response->getContent())) { + throw new \RuntimeException('Unable to store the entity.'); + } + + $response->headers->set('X-Content-Digest', $digest); + + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', strlen($response->getContent())); + } + } + + // read existing cache entries, remove non-varying, and add this one to the list + $entries = array(); + $vary = $response->headers->get('vary'); + foreach ($this->getMetadata($key) as $entry) { + if (!isset($entry[1]['vary'][0])) { + $entry[1]['vary'] = array(''); + } + + if ($vary != $entry[1]['vary'][0] || !$this->requestsMatch($vary, $entry[0], $storedEnv)) { + $entries[] = $entry; + } + } + + $headers = $this->persistResponse($response); + unset($headers['age']); + + array_unshift($entries, array($storedEnv, $headers)); + + if (false === $this->save($key, serialize($entries))) { + throw new \RuntimeException('Unable to store the metadata.'); + } + + return $key; + } + + /** + * Returns content digest for $response. + * + * @param Response $response + * + * @return string + */ + protected function generateContentDigest(Response $response) + { + return 'en'.hash('sha256', $response->getContent()); + } + + /** + * Invalidates all cache entries that match the request. + * + * @param Request $request A Request instance + * + * @throws \RuntimeException + */ + public function invalidate(Request $request) + { + $modified = false; + $key = $this->getCacheKey($request); + + $entries = array(); + foreach ($this->getMetadata($key) as $entry) { + $response = $this->restoreResponse($entry[1]); + if ($response->isFresh()) { + $response->expire(); + $modified = true; + $entries[] = array($entry[0], $this->persistResponse($response)); + } else { + $entries[] = $entry; + } + } + + if ($modified && false === $this->save($key, serialize($entries))) { + throw new \RuntimeException('Unable to store the metadata.'); + } + } + + /** + * Determines whether two Request HTTP header sets are non-varying based on + * the vary response header value provided. + * + * @param string $vary A Response vary header + * @param array $env1 A Request HTTP header array + * @param array $env2 A Request HTTP header array + * + * @return bool true if the two environments match, false otherwise + */ + private function requestsMatch($vary, $env1, $env2) + { + if (empty($vary)) { + return true; + } + + foreach (preg_split('/[\s,]+/', $vary) as $header) { + $key = str_replace('_', '-', strtolower($header)); + $v1 = isset($env1[$key]) ? $env1[$key] : null; + $v2 = isset($env2[$key]) ? $env2[$key] : null; + if ($v1 !== $v2) { + return false; + } + } + + return true; + } + + /** + * Gets all data associated with the given key. + * + * Use this method only if you know what you are doing. + * + * @param string $key The store key + * + * @return array An array of data associated with the key + */ + private function getMetadata($key) + { + if (!$entries = $this->load($key)) { + return array(); + } + + return unserialize($entries); + } + + /** + * Purges data for the given URL. + * + * This method purges both the HTTP and the HTTPS version of the cache entry. + * + * @param string $url A URL + * + * @return bool true if the URL exists with either HTTP or HTTPS scheme and has been purged, false otherwise + */ + public function purge($url) + { + $http = preg_replace('#^https:#', 'http:', $url); + $https = preg_replace('#^http:#', 'https:', $url); + + $purgedHttp = $this->doPurge($http); + $purgedHttps = $this->doPurge($https); + + return $purgedHttp || $purgedHttps; + } + + /** + * Purges data for the given URL. + * + * @param string $url A URL + * + * @return bool true if the URL exists and has been purged, false otherwise + */ + private function doPurge($url) + { + $key = $this->getCacheKey(Request::create($url)); + if (isset($this->locks[$key])) { + flock($this->locks[$key], LOCK_UN); + fclose($this->locks[$key]); + unset($this->locks[$key]); + } + + if (file_exists($path = $this->getPath($key))) { + unlink($path); + + return true; + } + + return false; + } + + /** + * Loads data for the given key. + * + * @param string $key The store key + * + * @return string The data associated with the key + */ + private function load($key) + { + $path = $this->getPath($key); + + return file_exists($path) ? file_get_contents($path) : false; + } + + /** + * Save data for the given key. + * + * @param string $key The store key + * @param string $data The data to store + * + * @return bool + */ + private function save($key, $data) + { + $path = $this->getPath($key); + + if (isset($this->locks[$key])) { + $fp = $this->locks[$key]; + @ftruncate($fp, 0); + @fseek($fp, 0); + $len = @fwrite($fp, $data); + if (strlen($data) !== $len) { + @ftruncate($fp, 0); + + return false; + } + } else { + if (!file_exists(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) { + return false; + } + + $tmpFile = tempnam(dirname($path), basename($path)); + if (false === $fp = @fopen($tmpFile, 'wb')) { + return false; + } + @fwrite($fp, $data); + @fclose($fp); + + if ($data != file_get_contents($tmpFile)) { + return false; + } + + if (false === @rename($tmpFile, $path)) { + return false; + } + } + + @chmod($path, 0666 & ~umask()); + } + + public function getPath($key) + { + return $this->root.DIRECTORY_SEPARATOR.substr($key, 0, 2).DIRECTORY_SEPARATOR.substr($key, 2, 2).DIRECTORY_SEPARATOR.substr($key, 4, 2).DIRECTORY_SEPARATOR.substr($key, 6); + } + + /** + * Generates a cache key for the given Request. + * + * This method should return a key that must only depend on a + * normalized version of the request URI. + * + * If the same URI can have more than one representation, based on some + * headers, use a Vary header to indicate them, and each representation will + * be stored independently under the same cache key. + * + * @param Request $request A Request instance + * + * @return string A key for the given Request + */ + protected function generateCacheKey(Request $request) + { + return 'md'.hash('sha256', $request->getUri()); + } + + /** + * Returns a cache key for the given Request. + * + * @param Request $request A Request instance + * + * @return string A key for the given Request + */ + private function getCacheKey(Request $request) + { + if (isset($this->keyCache[$request])) { + return $this->keyCache[$request]; + } + + return $this->keyCache[$request] = $this->generateCacheKey($request); + } + + /** + * Persists the Request HTTP headers. + * + * @param Request $request A Request instance + * + * @return array An array of HTTP headers + */ + private function persistRequest(Request $request) + { + return $request->headers->all(); + } + + /** + * Persists the Response HTTP headers. + * + * @param Response $response A Response instance + * + * @return array An array of HTTP headers + */ + private function persistResponse(Response $response) + { + $headers = $response->headers->all(); + $headers['X-Status'] = array($response->getStatusCode()); + + return $headers; + } + + /** + * Restores a Response from the HTTP headers and body. + * + * @param array $headers An array of HTTP headers for the Response + * @param string $body The Response body + * + * @return Response + */ + private function restoreResponse($headers, $body = null) + { + $status = $headers['X-Status'][0]; + unset($headers['X-Status']); + + if (null !== $body) { + $headers['X-Body-File'] = array($body); + } + + return new Response($body, $status, $headers); + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/StoreInterface.php b/vendor/symfony/http-kernel/HttpCache/StoreInterface.php new file mode 100644 index 00000000..ddc0c04e --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/StoreInterface.php @@ -0,0 +1,96 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Interface implemented by HTTP cache stores. + * + * @author Fabien Potencier + */ +interface StoreInterface +{ + /** + * Locates a cached Response for the Request provided. + * + * @param Request $request A Request instance + * + * @return Response|null A Response instance, or null if no cache entry was found + */ + public function lookup(Request $request); + + /** + * Writes a cache entry to the store for the given Request and Response. + * + * Existing entries are read and any that match the response are removed. This + * method calls write with the new list of cache entries. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * + * @return string The key under which the response is stored + */ + public function write(Request $request, Response $response); + + /** + * Invalidates all cache entries that match the request. + * + * @param Request $request A Request instance + */ + public function invalidate(Request $request); + + /** + * Locks the cache for a given Request. + * + * @param Request $request A Request instance + * + * @return bool|string true if the lock is acquired, the path to the current lock otherwise + */ + public function lock(Request $request); + + /** + * Releases the lock for the given Request. + * + * @param Request $request A Request instance + * + * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise + */ + public function unlock(Request $request); + + /** + * Returns whether or not a lock exists. + * + * @param Request $request A Request instance + * + * @return bool true if lock exists, false otherwise + */ + public function isLocked(Request $request); + + /** + * Purges data for the given URL. + * + * @param string $url A URL + * + * @return bool true if the URL exists and has been purged, false otherwise + */ + public function purge($url); + + /** + * Cleanups storage. + */ + public function cleanup(); +} diff --git a/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php b/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php new file mode 100644 index 00000000..5d65fd65 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +interface SurrogateInterface +{ + /** + * Returns surrogate name. + * + * @return string + */ + public function getName(); + + /** + * Returns a new cache strategy instance. + * + * @return ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance + */ + public function createCacheStrategy(); + + /** + * Checks that at least one surrogate has Surrogate capability. + * + * @param Request $request A Request instance + * + * @return bool true if one surrogate has Surrogate capability, false otherwise + */ + public function hasSurrogateCapability(Request $request); + + /** + * Adds Surrogate-capability to the given Request. + * + * @param Request $request A Request instance + */ + public function addSurrogateCapability(Request $request); + + /** + * Adds HTTP headers to specify that the Response needs to be parsed for Surrogate. + * + * This method only adds an Surrogate HTTP header if the Response has some Surrogate tags. + * + * @param Response $response A Response instance + */ + public function addSurrogateControl(Response $response); + + /** + * Checks that the Response needs to be parsed for Surrogate tags. + * + * @param Response $response A Response instance + * + * @return bool true if the Response needs to be parsed, false otherwise + */ + public function needsParsing(Response $response); + + /** + * Renders a Surrogate tag. + * + * @param string $uri A URI + * @param string $alt An alternate URI + * @param bool $ignoreErrors Whether to ignore errors or not + * @param string $comment A comment to add as an esi:include tag + * + * @return string + */ + public function renderIncludeTag($uri, $alt = null, $ignoreErrors = true, $comment = ''); + + /** + * Replaces a Response Surrogate tags with the included resource content. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * + * @return Response + */ + public function process(Request $request, Response $response); + + /** + * Handles a Surrogate from the cache. + * + * @param HttpCache $cache An HttpCache instance + * @param string $uri The main URI + * @param string $alt An alternative URI + * @param bool $ignoreErrors Whether to ignore errors or not + * + * @return string + * + * @throws \RuntimeException + * @throws \Exception + */ + public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors); +} diff --git a/vendor/symfony/http-kernel/HttpKernel.php b/vendor/symfony/http-kernel/HttpKernel.php new file mode 100644 index 00000000..8d55ccde --- /dev/null +++ b/vendor/symfony/http-kernel/HttpKernel.php @@ -0,0 +1,301 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * HttpKernel notifies events to convert a Request object to a Response one. + * + * @author Fabien Potencier + */ +class HttpKernel implements HttpKernelInterface, TerminableInterface +{ + protected $dispatcher; + protected $resolver; + protected $requestStack; + private $argumentResolver; + + public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver, RequestStack $requestStack = null, ArgumentResolverInterface $argumentResolver = null) + { + $this->dispatcher = $dispatcher; + $this->resolver = $resolver; + $this->requestStack = $requestStack ?: new RequestStack(); + $this->argumentResolver = $argumentResolver; + + if (null === $this->argumentResolver) { + @trigger_error(sprintf('As of 3.1 an %s is used to resolve arguments. In 4.0 the $argumentResolver becomes the %s if no other is provided instead of using the $resolver argument.', ArgumentResolverInterface::class, ArgumentResolver::class), E_USER_DEPRECATED); + // fallback in case of deprecations + $this->argumentResolver = $resolver; + } + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + $request->headers->set('X-Php-Ob-Level', ob_get_level()); + + try { + return $this->handleRaw($request, $type); + } catch (\Exception $e) { + if ($e instanceof RequestExceptionInterface) { + $e = new BadRequestHttpException($e->getMessage(), $e); + } + if (false === $catch) { + $this->finishRequest($request, $type); + + throw $e; + } + + return $this->handleException($e, $request, $type); + } + } + + /** + * {@inheritdoc} + */ + public function terminate(Request $request, Response $response) + { + $this->dispatcher->dispatch(KernelEvents::TERMINATE, new PostResponseEvent($this, $request, $response)); + } + + /** + * @throws \LogicException If the request stack is empty + * + * @internal + */ + public function terminateWithException(\Exception $exception) + { + if (!$request = $this->requestStack->getMasterRequest()) { + throw new \LogicException('Request stack is empty', 0, $exception); + } + + $response = $this->handleException($exception, $request, self::MASTER_REQUEST); + + $response->sendHeaders(); + $response->sendContent(); + + $this->terminate($request, $response); + } + + /** + * Handles a request to convert it to a response. + * + * Exceptions are not caught. + * + * @param Request $request A Request instance + * @param int $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) + * + * @return Response A Response instance + * + * @throws \LogicException If one of the listener does not behave as expected + * @throws NotFoundHttpException When controller cannot be found + */ + private function handleRaw(Request $request, $type = self::MASTER_REQUEST) + { + $this->requestStack->push($request); + + // request + $event = new GetResponseEvent($this, $request, $type); + $this->dispatcher->dispatch(KernelEvents::REQUEST, $event); + + if ($event->hasResponse()) { + return $this->filterResponse($event->getResponse(), $request, $type); + } + + // load controller + if (false === $controller = $this->resolver->getController($request)) { + throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo())); + } + + $event = new FilterControllerEvent($this, $controller, $request, $type); + $this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event); + $controller = $event->getController(); + + // controller arguments + $arguments = $this->argumentResolver->getArguments($request, $controller); + + $event = new FilterControllerArgumentsEvent($this, $controller, $arguments, $request, $type); + $this->dispatcher->dispatch(KernelEvents::CONTROLLER_ARGUMENTS, $event); + $controller = $event->getController(); + $arguments = $event->getArguments(); + + // call controller + $response = call_user_func_array($controller, $arguments); + + // view + if (!$response instanceof Response) { + $event = new GetResponseForControllerResultEvent($this, $request, $type, $response); + $this->dispatcher->dispatch(KernelEvents::VIEW, $event); + + if ($event->hasResponse()) { + $response = $event->getResponse(); + } + + if (!$response instanceof Response) { + $msg = sprintf('The controller must return a response (%s given).', $this->varToString($response)); + + // the user may have forgotten to return something + if (null === $response) { + $msg .= ' Did you forget to add a return statement somewhere in your controller?'; + } + throw new \LogicException($msg); + } + } + + return $this->filterResponse($response, $request, $type); + } + + /** + * Filters a response object. + * + * @param Response $response A Response instance + * @param Request $request An error message in case the response is not a Response object + * @param int $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) + * + * @return Response The filtered Response instance + * + * @throws \RuntimeException if the passed object is not a Response instance + */ + private function filterResponse(Response $response, Request $request, $type) + { + $event = new FilterResponseEvent($this, $request, $type, $response); + + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->finishRequest($request, $type); + + return $event->getResponse(); + } + + /** + * Publishes the finish request event, then pop the request from the stack. + * + * Note that the order of the operations is important here, otherwise + * operations such as {@link RequestStack::getParentRequest()} can lead to + * weird results. + * + * @param Request $request + * @param int $type + */ + private function finishRequest(Request $request, $type) + { + $this->dispatcher->dispatch(KernelEvents::FINISH_REQUEST, new FinishRequestEvent($this, $request, $type)); + $this->requestStack->pop(); + } + + /** + * Handles an exception by trying to convert it to a Response. + * + * @param \Exception $e An \Exception instance + * @param Request $request A Request instance + * @param int $type The type of the request + * + * @return Response A Response instance + * + * @throws \Exception + */ + private function handleException(\Exception $e, $request, $type) + { + $event = new GetResponseForExceptionEvent($this, $request, $type, $e); + $this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event); + + // a listener might have replaced the exception + $e = $event->getException(); + + if (!$event->hasResponse()) { + $this->finishRequest($request, $type); + + throw $e; + } + + $response = $event->getResponse(); + + // the developer asked for a specific status code + if ($response->headers->has('X-Status-Code')) { + @trigger_error(sprintf('Using the X-Status-Code header is deprecated since version 3.3 and will be removed in 4.0. Use %s::allowCustomResponseCode() instead.', GetResponseForExceptionEvent::class), E_USER_DEPRECATED); + + $response->setStatusCode($response->headers->get('X-Status-Code')); + + $response->headers->remove('X-Status-Code'); + } elseif (!$event->isAllowingCustomResponseCode() && !$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) { + // ensure that we actually have an error response + if ($e instanceof HttpExceptionInterface) { + // keep the HTTP status code and headers + $response->setStatusCode($e->getStatusCode()); + $response->headers->add($e->getHeaders()); + } else { + $response->setStatusCode(500); + } + } + + try { + return $this->filterResponse($response, $request, $type); + } catch (\Exception $e) { + return $response; + } + } + + private function varToString($var) + { + if (is_object($var)) { + return sprintf('Object(%s)', get_class($var)); + } + + if (is_array($var)) { + $a = array(); + foreach ($var as $k => $v) { + $a[] = sprintf('%s => %s', $k, $this->varToString($v)); + } + + return sprintf('Array(%s)', implode(', ', $a)); + } + + if (is_resource($var)) { + return sprintf('Resource(%s)', get_resource_type($var)); + } + + if (null === $var) { + return 'null'; + } + + if (false === $var) { + return 'false'; + } + + if (true === $var) { + return 'true'; + } + + return (string) $var; + } +} diff --git a/vendor/symfony/http-kernel/HttpKernelInterface.php b/vendor/symfony/http-kernel/HttpKernelInterface.php new file mode 100644 index 00000000..5050bfcf --- /dev/null +++ b/vendor/symfony/http-kernel/HttpKernelInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * HttpKernelInterface handles a Request to convert it to a Response. + * + * @author Fabien Potencier + */ +interface HttpKernelInterface +{ + const MASTER_REQUEST = 1; + const SUB_REQUEST = 2; + + /** + * Handles a Request to convert it to a Response. + * + * When $catch is true, the implementation must catch all exceptions + * and do its best to convert them to a Response instance. + * + * @param Request $request A Request instance + * @param int $type The type of the request + * (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) + * @param bool $catch Whether to catch exceptions or not + * + * @return Response A Response instance + * + * @throws \Exception When an Exception occurs during processing + */ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true); +} diff --git a/vendor/symfony/http-kernel/Kernel.php b/vendor/symfony/http-kernel/Kernel.php new file mode 100644 index 00000000..5f986cf4 --- /dev/null +++ b/vendor/symfony/http-kernel/Kernel.php @@ -0,0 +1,866 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Dumper\PhpDumper; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\DependencyInjection\Loader\IniFileLoader; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Loader\DirectoryLoader; +use Symfony\Component\DependencyInjection\Loader\ClosureLoader; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\HttpKernel\Config\EnvParametersResource; +use Symfony\Component\HttpKernel\Config\FileLocator; +use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; +use Symfony\Component\HttpKernel\DependencyInjection\AddAnnotatedClassesToCachePass; +use Symfony\Component\Config\Loader\GlobFileLoader; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\Config\Loader\DelegatingLoader; +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\ClassLoader\ClassCollectionLoader; + +/** + * The Kernel is the heart of the Symfony system. + * + * It manages an environment made of bundles. + * + * @author Fabien Potencier + */ +abstract class Kernel implements KernelInterface, TerminableInterface +{ + /** + * @var BundleInterface[] + */ + protected $bundles = array(); + + protected $bundleMap; + protected $container; + protected $rootDir; + protected $environment; + protected $debug; + protected $booted = false; + protected $name; + protected $startTime; + protected $loadClassCache; + + private $projectDir; + + const VERSION = '3.3.9'; + const VERSION_ID = 30309; + const MAJOR_VERSION = 3; + const MINOR_VERSION = 3; + const RELEASE_VERSION = 9; + const EXTRA_VERSION = ''; + + const END_OF_MAINTENANCE = '01/2018'; + const END_OF_LIFE = '07/2018'; + + /** + * Constructor. + * + * @param string $environment The environment + * @param bool $debug Whether to enable debugging or not + */ + public function __construct($environment, $debug) + { + $this->environment = $environment; + $this->debug = (bool) $debug; + $this->rootDir = $this->getRootDir(); + $this->name = $this->getName(); + + if ($this->debug) { + $this->startTime = microtime(true); + } + } + + public function __clone() + { + if ($this->debug) { + $this->startTime = microtime(true); + } + + $this->booted = false; + $this->container = null; + } + + /** + * Boots the current kernel. + */ + public function boot() + { + if (true === $this->booted) { + return; + } + + if ($this->loadClassCache) { + $this->doLoadClassCache($this->loadClassCache[0], $this->loadClassCache[1]); + } + + // init bundles + $this->initializeBundles(); + + // init container + $this->initializeContainer(); + + foreach ($this->getBundles() as $bundle) { + $bundle->setContainer($this->container); + $bundle->boot(); + } + + $this->booted = true; + } + + /** + * {@inheritdoc} + */ + public function terminate(Request $request, Response $response) + { + if (false === $this->booted) { + return; + } + + if ($this->getHttpKernel() instanceof TerminableInterface) { + $this->getHttpKernel()->terminate($request, $response); + } + } + + /** + * {@inheritdoc} + */ + public function shutdown() + { + if (false === $this->booted) { + return; + } + + $this->booted = false; + + foreach ($this->getBundles() as $bundle) { + $bundle->shutdown(); + $bundle->setContainer(null); + } + + $this->container = null; + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + if (false === $this->booted) { + $this->boot(); + } + + return $this->getHttpKernel()->handle($request, $type, $catch); + } + + /** + * Gets a HTTP kernel from the container. + * + * @return HttpKernel + */ + protected function getHttpKernel() + { + return $this->container->get('http_kernel'); + } + + /** + * {@inheritdoc} + */ + public function getBundles() + { + return $this->bundles; + } + + /** + * {@inheritdoc} + */ + public function getBundle($name, $first = true) + { + if (!isset($this->bundleMap[$name])) { + throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the registerBundles() method of your %s.php file?', $name, get_class($this))); + } + + if (true === $first) { + return $this->bundleMap[$name][0]; + } + + return $this->bundleMap[$name]; + } + + /** + * {@inheritdoc} + * + * @throws \RuntimeException if a custom resource is hidden by a resource in a derived bundle + */ + public function locateResource($name, $dir = null, $first = true) + { + if ('@' !== $name[0]) { + throw new \InvalidArgumentException(sprintf('A resource name must start with @ ("%s" given).', $name)); + } + + if (false !== strpos($name, '..')) { + throw new \RuntimeException(sprintf('File name "%s" contains invalid characters (..).', $name)); + } + + $bundleName = substr($name, 1); + $path = ''; + if (false !== strpos($bundleName, '/')) { + list($bundleName, $path) = explode('/', $bundleName, 2); + } + + $isResource = 0 === strpos($path, 'Resources') && null !== $dir; + $overridePath = substr($path, 9); + $resourceBundle = null; + $bundles = $this->getBundle($bundleName, false); + $files = array(); + + foreach ($bundles as $bundle) { + if ($isResource && file_exists($file = $dir.'/'.$bundle->getName().$overridePath)) { + if (null !== $resourceBundle) { + throw new \RuntimeException(sprintf('"%s" resource is hidden by a resource from the "%s" derived bundle. Create a "%s" file to override the bundle resource.', + $file, + $resourceBundle, + $dir.'/'.$bundles[0]->getName().$overridePath + )); + } + + if ($first) { + return $file; + } + $files[] = $file; + } + + if (file_exists($file = $bundle->getPath().'/'.$path)) { + if ($first && !$isResource) { + return $file; + } + $files[] = $file; + $resourceBundle = $bundle->getName(); + } + } + + if (count($files) > 0) { + return $first && $isResource ? $files[0] : $files; + } + + throw new \InvalidArgumentException(sprintf('Unable to find file "%s".', $name)); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + if (null === $this->name) { + $this->name = preg_replace('/[^a-zA-Z0-9_]+/', '', basename($this->rootDir)); + if (ctype_digit($this->name[0])) { + $this->name = '_'.$this->name; + } + } + + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getEnvironment() + { + return $this->environment; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return $this->debug; + } + + /** + * {@inheritdoc} + */ + public function getRootDir() + { + if (null === $this->rootDir) { + $r = new \ReflectionObject($this); + $this->rootDir = dirname($r->getFileName()); + } + + return $this->rootDir; + } + + /** + * Gets the application root dir (path of the project's composer file). + * + * @return string The project root dir + */ + public function getProjectDir() + { + if (null === $this->projectDir) { + $r = new \ReflectionObject($this); + $dir = $rootDir = dirname($r->getFileName()); + while (!file_exists($dir.'/composer.json')) { + if ($dir === dirname($dir)) { + return $this->projectDir = $rootDir; + } + $dir = dirname($dir); + } + $this->projectDir = $dir; + } + + return $this->projectDir; + } + + /** + * {@inheritdoc} + */ + public function getContainer() + { + return $this->container; + } + + /** + * Loads the PHP class cache. + * + * This methods only registers the fact that you want to load the cache classes. + * The cache will actually only be loaded when the Kernel is booted. + * + * That optimization is mainly useful when using the HttpCache class in which + * case the class cache is not loaded if the Response is in the cache. + * + * @param string $name The cache name prefix + * @param string $extension File extension of the resulting file + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function loadClassCache($name = 'classes', $extension = '.php') + { + if (\PHP_VERSION_ID >= 70000) { + @trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED); + } + + $this->loadClassCache = array($name, $extension); + } + + /** + * @internal + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function setClassCache(array $classes) + { + if (\PHP_VERSION_ID >= 70000) { + @trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED); + } + + file_put_contents($this->getCacheDir().'/classes.map', sprintf('getCacheDir().'/annotations.map', sprintf('debug ? $this->startTime : -INF; + } + + /** + * {@inheritdoc} + */ + public function getCacheDir() + { + return $this->rootDir.'/cache/'.$this->environment; + } + + /** + * {@inheritdoc} + */ + public function getLogDir() + { + return $this->rootDir.'/logs'; + } + + /** + * {@inheritdoc} + */ + public function getCharset() + { + return 'UTF-8'; + } + + /** + * @deprecated since version 3.3, to be removed in 4.0. + */ + protected function doLoadClassCache($name, $extension) + { + if (\PHP_VERSION_ID >= 70000) { + @trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED); + } + + if (!$this->booted && is_file($this->getCacheDir().'/classes.map')) { + ClassCollectionLoader::load(include($this->getCacheDir().'/classes.map'), $this->getCacheDir(), $name, $this->debug, false, $extension); + } + } + + /** + * Initializes the data structures related to the bundle management. + * + * - the bundles property maps a bundle name to the bundle instance, + * - the bundleMap property maps a bundle name to the bundle inheritance hierarchy (most derived bundle first). + * + * @throws \LogicException if two bundles share a common name + * @throws \LogicException if a bundle tries to extend a non-registered bundle + * @throws \LogicException if a bundle tries to extend itself + * @throws \LogicException if two bundles extend the same ancestor + */ + protected function initializeBundles() + { + // init bundles + $this->bundles = array(); + $topMostBundles = array(); + $directChildren = array(); + + foreach ($this->registerBundles() as $bundle) { + $name = $bundle->getName(); + if (isset($this->bundles[$name])) { + throw new \LogicException(sprintf('Trying to register two bundles with the same name "%s"', $name)); + } + $this->bundles[$name] = $bundle; + + if ($parentName = $bundle->getParent()) { + if (isset($directChildren[$parentName])) { + throw new \LogicException(sprintf('Bundle "%s" is directly extended by two bundles "%s" and "%s".', $parentName, $name, $directChildren[$parentName])); + } + if ($parentName == $name) { + throw new \LogicException(sprintf('Bundle "%s" can not extend itself.', $name)); + } + $directChildren[$parentName] = $name; + } else { + $topMostBundles[$name] = $bundle; + } + } + + // look for orphans + if (!empty($directChildren) && count($diff = array_diff_key($directChildren, $this->bundles))) { + $diff = array_keys($diff); + + throw new \LogicException(sprintf('Bundle "%s" extends bundle "%s", which is not registered.', $directChildren[$diff[0]], $diff[0])); + } + + // inheritance + $this->bundleMap = array(); + foreach ($topMostBundles as $name => $bundle) { + $bundleMap = array($bundle); + $hierarchy = array($name); + + while (isset($directChildren[$name])) { + $name = $directChildren[$name]; + array_unshift($bundleMap, $this->bundles[$name]); + $hierarchy[] = $name; + } + + foreach ($hierarchy as $hierarchyBundle) { + $this->bundleMap[$hierarchyBundle] = $bundleMap; + array_pop($bundleMap); + } + } + } + + /** + * The extension point similar to the Bundle::build() method. + * + * Use this method to register compiler passes and manipulate the container during the building process. + * + * @param ContainerBuilder $container + */ + protected function build(ContainerBuilder $container) + { + } + + /** + * Gets the container class. + * + * @return string The container class + */ + protected function getContainerClass() + { + return $this->name.ucfirst($this->environment).($this->debug ? 'Debug' : '').'ProjectContainer'; + } + + /** + * Gets the container's base class. + * + * All names except Container must be fully qualified. + * + * @return string + */ + protected function getContainerBaseClass() + { + return 'Container'; + } + + /** + * Initializes the service container. + * + * The cached version of the service container is used when fresh, otherwise the + * container is built. + */ + protected function initializeContainer() + { + $class = $this->getContainerClass(); + $cache = new ConfigCache($this->getCacheDir().'/'.$class.'.php', $this->debug); + $fresh = true; + if (!$cache->isFresh()) { + if ($this->debug) { + $collectedLogs = array(); + $previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) { + if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) { + return $previousHandler ? $previousHandler($type, $message, $file, $line) : false; + } + + if (isset($collectedLogs[$message])) { + ++$collectedLogs[$message]['count']; + + return; + } + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); + // Clean the trace by removing first frames added by the error handler itself. + for ($i = 0; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { + $backtrace = array_slice($backtrace, 1 + $i); + break; + } + } + + $collectedLogs[$message] = array( + 'type' => $type, + 'message' => $message, + 'file' => $file, + 'line' => $line, + 'trace' => $backtrace, + 'count' => 1, + ); + }); + } + + try { + $container = null; + $container = $this->buildContainer(); + $container->compile(); + } finally { + if ($this->debug) { + restore_error_handler(); + + file_put_contents($this->getCacheDir().'/'.$class.'Deprecations.log', serialize(array_values($collectedLogs))); + file_put_contents($this->getCacheDir().'/'.$class.'Compiler.log', null !== $container ? implode("\n", $container->getCompiler()->getLog()) : ''); + } + } + $this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass()); + + $fresh = false; + } + + require_once $cache->getPath(); + + $this->container = new $class(); + $this->container->set('kernel', $this); + + if (!$fresh && $this->container->has('cache_warmer')) { + $this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir')); + } + } + + /** + * Returns the kernel parameters. + * + * @return array An array of kernel parameters + */ + protected function getKernelParameters() + { + $bundles = array(); + $bundlesMetadata = array(); + + foreach ($this->bundles as $name => $bundle) { + $bundles[$name] = get_class($bundle); + $bundlesMetadata[$name] = array( + 'parent' => $bundle->getParent(), + 'path' => $bundle->getPath(), + 'namespace' => $bundle->getNamespace(), + ); + } + + return array_merge( + array( + 'kernel.root_dir' => realpath($this->rootDir) ?: $this->rootDir, + 'kernel.project_dir' => realpath($this->getProjectDir()) ?: $this->getProjectDir(), + 'kernel.environment' => $this->environment, + 'kernel.debug' => $this->debug, + 'kernel.name' => $this->name, + 'kernel.cache_dir' => realpath($this->getCacheDir()) ?: $this->getCacheDir(), + 'kernel.logs_dir' => realpath($this->getLogDir()) ?: $this->getLogDir(), + 'kernel.bundles' => $bundles, + 'kernel.bundles_metadata' => $bundlesMetadata, + 'kernel.charset' => $this->getCharset(), + 'kernel.container_class' => $this->getContainerClass(), + ), + $this->getEnvParameters(false) + ); + } + + /** + * Gets the environment parameters. + * + * Only the parameters starting with "SYMFONY__" are considered. + * + * @return array An array of parameters + * + * @deprecated since version 3.3, to be removed in 4.0 + */ + protected function getEnvParameters() + { + if (0 === func_num_args() || func_get_arg(0)) { + @trigger_error(sprintf('The %s() method is deprecated as of 3.3 and will be removed in 4.0. Use the %%env()%% syntax to get the value of any environment variable from configuration files instead.', __METHOD__), E_USER_DEPRECATED); + } + + $parameters = array(); + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, 'SYMFONY__')) { + @trigger_error(sprintf('The support of special environment variables that start with SYMFONY__ (such as "%s") is deprecated as of 3.3 and will be removed in 4.0. Use the %%env()%% syntax instead to get the value of environment variables in configuration files.', $key), E_USER_DEPRECATED); + $parameters[strtolower(str_replace('__', '.', substr($key, 9)))] = $value; + } + } + + return $parameters; + } + + /** + * Builds the service container. + * + * @return ContainerBuilder The compiled service container + * + * @throws \RuntimeException + */ + protected function buildContainer() + { + foreach (array('cache' => $this->getCacheDir(), 'logs' => $this->getLogDir()) as $name => $dir) { + if (!is_dir($dir)) { + if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) { + throw new \RuntimeException(sprintf("Unable to create the %s directory (%s)\n", $name, $dir)); + } + } elseif (!is_writable($dir)) { + throw new \RuntimeException(sprintf("Unable to write in the %s directory (%s)\n", $name, $dir)); + } + } + + $container = $this->getContainerBuilder(); + $container->addObjectResource($this); + $this->prepareContainer($container); + + if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) { + $container->merge($cont); + } + + $container->addCompilerPass(new AddAnnotatedClassesToCachePass($this)); + $container->addResource(new EnvParametersResource('SYMFONY__')); + + return $container; + } + + /** + * Prepares the ContainerBuilder before it is compiled. + * + * @param ContainerBuilder $container A ContainerBuilder instance + */ + protected function prepareContainer(ContainerBuilder $container) + { + $extensions = array(); + foreach ($this->bundles as $bundle) { + if ($extension = $bundle->getContainerExtension()) { + $container->registerExtension($extension); + $extensions[] = $extension->getAlias(); + } + + if ($this->debug) { + $container->addObjectResource($bundle); + } + } + + foreach ($this->bundles as $bundle) { + $bundle->build($container); + } + + $this->build($container); + + // ensure these extensions are implicitly loaded + $container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions)); + } + + /** + * Gets a new ContainerBuilder instance used to build the service container. + * + * @return ContainerBuilder + */ + protected function getContainerBuilder() + { + $container = new ContainerBuilder(); + $container->getParameterBag()->add($this->getKernelParameters()); + + if (class_exists('ProxyManager\Configuration') && class_exists('Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator')) { + $container->setProxyInstantiator(new RuntimeInstantiator()); + } + + return $container; + } + + /** + * Dumps the service container to PHP code in the cache. + * + * @param ConfigCache $cache The config cache + * @param ContainerBuilder $container The service container + * @param string $class The name of the class to generate + * @param string $baseClass The name of the container's base class + */ + protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container, $class, $baseClass) + { + // cache the container + $dumper = new PhpDumper($container); + + if (class_exists('ProxyManager\Configuration') && class_exists('Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper')) { + $dumper->setProxyDumper(new ProxyDumper(md5($cache->getPath()))); + } + + $content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass, 'file' => $cache->getPath(), 'debug' => $this->debug)); + + $cache->write($content, $container->getResources()); + } + + /** + * Returns a loader for the container. + * + * @param ContainerInterface $container The service container + * + * @return DelegatingLoader The loader + */ + protected function getContainerLoader(ContainerInterface $container) + { + $locator = new FileLocator($this); + $resolver = new LoaderResolver(array( + new XmlFileLoader($container, $locator), + new YamlFileLoader($container, $locator), + new IniFileLoader($container, $locator), + new PhpFileLoader($container, $locator), + new GlobFileLoader($locator), + new DirectoryLoader($container, $locator), + new ClosureLoader($container), + )); + + return new DelegatingLoader($resolver); + } + + /** + * Removes comments from a PHP source string. + * + * We don't use the PHP php_strip_whitespace() function + * as we want the content to be readable and well-formatted. + * + * @param string $source A PHP string + * + * @return string The PHP string with the comments removed + */ + public static function stripComments($source) + { + if (!function_exists('token_get_all')) { + return $source; + } + + $rawChunk = ''; + $output = ''; + $tokens = token_get_all($source); + $ignoreSpace = false; + for ($i = 0; isset($tokens[$i]); ++$i) { + $token = $tokens[$i]; + if (!isset($token[1]) || 'b"' === $token) { + $rawChunk .= $token; + } elseif (T_START_HEREDOC === $token[0]) { + $output .= $rawChunk.$token[1]; + do { + $token = $tokens[++$i]; + $output .= isset($token[1]) && 'b"' !== $token ? $token[1] : $token; + } while ($token[0] !== T_END_HEREDOC); + $rawChunk = ''; + } elseif (T_WHITESPACE === $token[0]) { + if ($ignoreSpace) { + $ignoreSpace = false; + + continue; + } + + // replace multiple new lines with a single newline + $rawChunk .= preg_replace(array('/\n{2,}/S'), "\n", $token[1]); + } elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { + $ignoreSpace = true; + } else { + $rawChunk .= $token[1]; + + // The PHP-open tag already has a new-line + if (T_OPEN_TAG === $token[0]) { + $ignoreSpace = true; + } + } + } + + $output .= $rawChunk; + + if (\PHP_VERSION_ID >= 70000) { + // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 + unset($tokens, $rawChunk); + gc_mem_caches(); + } + + return $output; + } + + public function serialize() + { + return serialize(array($this->environment, $this->debug)); + } + + public function unserialize($data) + { + if (\PHP_VERSION_ID >= 70000) { + list($environment, $debug) = unserialize($data, array('allowed_classes' => false)); + } else { + list($environment, $debug) = unserialize($data); + } + + $this->__construct($environment, $debug); + } +} diff --git a/vendor/symfony/http-kernel/KernelEvents.php b/vendor/symfony/http-kernel/KernelEvents.php new file mode 100644 index 00000000..3e961737 --- /dev/null +++ b/vendor/symfony/http-kernel/KernelEvents.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +/** + * Contains all events thrown in the HttpKernel component. + * + * @author Bernhard Schussek + */ +final class KernelEvents +{ + /** + * The REQUEST event occurs at the very beginning of request + * dispatching. + * + * This event allows you to create a response for a request before any + * other code in the framework is executed. + * + * @Event("Symfony\Component\HttpKernel\Event\GetResponseEvent") + * + * @var string + */ + const REQUEST = 'kernel.request'; + + /** + * The EXCEPTION event occurs when an uncaught exception appears. + * + * This event allows you to create a response for a thrown exception or + * to modify the thrown exception. + * + * @Event("Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent") + * + * @var string + */ + const EXCEPTION = 'kernel.exception'; + + /** + * The VIEW event occurs when the return value of a controller + * is not a Response instance. + * + * This event allows you to create a response for the return value of the + * controller. + * + * @Event("Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent") + * + * @var string + */ + const VIEW = 'kernel.view'; + + /** + * The CONTROLLER event occurs once a controller was found for + * handling a request. + * + * This event allows you to change the controller that will handle the + * request. + * + * @Event("Symfony\Component\HttpKernel\Event\FilterControllerEvent") + * + * @var string + */ + const CONTROLLER = 'kernel.controller'; + + /** + * The CONTROLLER_ARGUMENTS event occurs once controller arguments have been resolved. + * + * This event allows you to change the arguments that will be passed to + * the controller. + * + * @Event("Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent") + * + * @var string + */ + const CONTROLLER_ARGUMENTS = 'kernel.controller_arguments'; + + /** + * The RESPONSE event occurs once a response was created for + * replying to a request. + * + * This event allows you to modify or replace the response that will be + * replied. + * + * @Event("Symfony\Component\HttpKernel\Event\FilterResponseEvent") + * + * @var string + */ + const RESPONSE = 'kernel.response'; + + /** + * The TERMINATE event occurs once a response was sent. + * + * This event allows you to run expensive post-response jobs. + * + * @Event("Symfony\Component\HttpKernel\Event\PostResponseEvent") + * + * @var string + */ + const TERMINATE = 'kernel.terminate'; + + /** + * The FINISH_REQUEST event occurs when a response was generated for a request. + * + * This event allows you to reset the global and environmental state of + * the application, when it was changed during the request. + * + * @Event("Symfony\Component\HttpKernel\Event\FinishRequestEvent") + * + * @var string + */ + const FINISH_REQUEST = 'kernel.finish_request'; +} diff --git a/vendor/symfony/http-kernel/KernelInterface.php b/vendor/symfony/http-kernel/KernelInterface.php new file mode 100644 index 00000000..b8609b9e --- /dev/null +++ b/vendor/symfony/http-kernel/KernelInterface.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\Config\Loader\LoaderInterface; + +/** + * The Kernel is the heart of the Symfony system. + * + * It manages an environment made of bundles. + * + * @author Fabien Potencier + */ +interface KernelInterface extends HttpKernelInterface, \Serializable +{ + /** + * Returns an array of bundles to register. + * + * @return BundleInterface[] An array of bundle instances + */ + public function registerBundles(); + + /** + * Loads the container configuration. + * + * @param LoaderInterface $loader A LoaderInterface instance + */ + public function registerContainerConfiguration(LoaderInterface $loader); + + /** + * Boots the current kernel. + */ + public function boot(); + + /** + * Shutdowns the kernel. + * + * This method is mainly useful when doing functional testing. + */ + public function shutdown(); + + /** + * Gets the registered bundle instances. + * + * @return BundleInterface[] An array of registered bundle instances + */ + public function getBundles(); + + /** + * Returns a bundle and optionally its descendants by its name. + * + * @param string $name Bundle name + * @param bool $first Whether to return the first bundle only or together with its descendants + * + * @return BundleInterface|BundleInterface[] A BundleInterface instance or an array of BundleInterface instances if $first is false + * + * @throws \InvalidArgumentException when the bundle is not enabled + */ + public function getBundle($name, $first = true); + + /** + * Returns the file path for a given resource. + * + * A Resource can be a file or a directory. + * + * The resource name must follow the following pattern: + * + * "@BundleName/path/to/a/file.something" + * + * where BundleName is the name of the bundle + * and the remaining part is the relative path in the bundle. + * + * If $dir is passed, and the first segment of the path is "Resources", + * this method will look for a file named: + * + * $dir//path/without/Resources + * + * before looking in the bundle resource folder. + * + * @param string $name A resource name to locate + * @param string $dir A directory where to look for the resource first + * @param bool $first Whether to return the first path or paths for all matching bundles + * + * @return string|array The absolute path of the resource or an array if $first is false + * + * @throws \InvalidArgumentException if the file cannot be found or the name is not valid + * @throws \RuntimeException if the name contains invalid/unsafe characters + */ + public function locateResource($name, $dir = null, $first = true); + + /** + * Gets the name of the kernel. + * + * @return string The kernel name + */ + public function getName(); + + /** + * Gets the environment. + * + * @return string The current environment + */ + public function getEnvironment(); + + /** + * Checks if debug mode is enabled. + * + * @return bool true if debug mode is enabled, false otherwise + */ + public function isDebug(); + + /** + * Gets the application root dir (path of the project's Kernel class). + * + * @return string The Kernel root dir + */ + public function getRootDir(); + + /** + * Gets the current container. + * + * @return ContainerInterface A ContainerInterface instance + */ + public function getContainer(); + + /** + * Gets the request start time (not available if debug is disabled). + * + * @return int The request start timestamp + */ + public function getStartTime(); + + /** + * Gets the cache directory. + * + * @return string The cache directory + */ + public function getCacheDir(); + + /** + * Gets the log directory. + * + * @return string The log directory + */ + public function getLogDir(); + + /** + * Gets the charset of the application. + * + * @return string The charset + */ + public function getCharset(); +} diff --git a/vendor/symfony/http-kernel/LICENSE b/vendor/symfony/http-kernel/LICENSE new file mode 100644 index 00000000..17d16a13 --- /dev/null +++ b/vendor/symfony/http-kernel/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2017 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/http-kernel/Log/DebugLoggerInterface.php b/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php new file mode 100644 index 00000000..5635a218 --- /dev/null +++ b/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Log; + +/** + * DebugLoggerInterface. + * + * @author Fabien Potencier + */ +interface DebugLoggerInterface +{ + /** + * Returns an array of logs. + * + * A log is an array with the following mandatory keys: + * timestamp, message, priority, and priorityName. + * It can also have an optional context key containing an array. + * + * @return array An array of logs + */ + public function getLogs(); + + /** + * Returns the number of errors. + * + * @return int The number of errors + */ + public function countErrors(); +} diff --git a/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php b/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php new file mode 100644 index 00000000..e24b2e01 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php @@ -0,0 +1,292 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +/** + * Storage for profiler using files. + * + * @author Alexandre Salomé + */ +class FileProfilerStorage implements ProfilerStorageInterface +{ + /** + * Folder where profiler data are stored. + * + * @var string + */ + private $folder; + + /** + * Constructs the file storage using a "dsn-like" path. + * + * Example : "file:/path/to/the/storage/folder" + * + * @param string $dsn The DSN + * + * @throws \RuntimeException + */ + public function __construct($dsn) + { + if (0 !== strpos($dsn, 'file:')) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use FileStorage with an invalid dsn "%s". The expected format is "file:/path/to/the/storage/folder".', $dsn)); + } + $this->folder = substr($dsn, 5); + + if (!is_dir($this->folder) && false === @mkdir($this->folder, 0777, true) && !is_dir($this->folder)) { + throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder)); + } + } + + /** + * {@inheritdoc} + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null, $statusCode = null) + { + $file = $this->getIndexFilename(); + + if (!file_exists($file)) { + return array(); + } + + $file = fopen($file, 'r'); + fseek($file, 0, SEEK_END); + + $result = array(); + while (count($result) < $limit && $line = $this->readLineFromFile($file)) { + $values = str_getcsv($line); + list($csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode) = $values; + $csvTime = (int) $csvTime; + + if ($ip && false === strpos($csvIp, $ip) || $url && false === strpos($csvUrl, $url) || $method && false === strpos($csvMethod, $method) || $statusCode && false === strpos($csvStatusCode, $statusCode)) { + continue; + } + + if (!empty($start) && $csvTime < $start) { + continue; + } + + if (!empty($end) && $csvTime > $end) { + continue; + } + + $result[$csvToken] = array( + 'token' => $csvToken, + 'ip' => $csvIp, + 'method' => $csvMethod, + 'url' => $csvUrl, + 'time' => $csvTime, + 'parent' => $csvParent, + 'status_code' => $csvStatusCode, + ); + } + + fclose($file); + + return array_values($result); + } + + /** + * {@inheritdoc} + */ + public function purge() + { + $flags = \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveDirectoryIterator($this->folder, $flags); + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($iterator as $file) { + if (is_file($file)) { + unlink($file); + } else { + rmdir($file); + } + } + } + + /** + * {@inheritdoc} + */ + public function read($token) + { + if (!$token || !file_exists($file = $this->getFilename($token))) { + return; + } + + return $this->createProfileFromData($token, unserialize(file_get_contents($file))); + } + + /** + * {@inheritdoc} + * + * @throws \RuntimeException + */ + public function write(Profile $profile) + { + $file = $this->getFilename($profile->getToken()); + + $profileIndexed = is_file($file); + if (!$profileIndexed) { + // Create directory + $dir = dirname($file); + if (!is_dir($dir) && false === @mkdir($dir, 0777, true) && !is_dir($dir)) { + throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir)); + } + } + + $profileToken = $profile->getToken(); + // when there are errors in sub-requests, the parent and/or children tokens + // may equal the profile token, resulting in infinite loops + $parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null; + $childrenToken = array_filter(array_map(function ($p) use ($profileToken) { + return $profileToken !== $p->getToken() ? $p->getToken() : null; + }, $profile->getChildren())); + + // Store profile + $data = array( + 'token' => $profileToken, + 'parent' => $parentToken, + 'children' => $childrenToken, + 'data' => $profile->getCollectors(), + 'ip' => $profile->getIp(), + 'method' => $profile->getMethod(), + 'url' => $profile->getUrl(), + 'time' => $profile->getTime(), + 'status_code' => $profile->getStatusCode(), + ); + + if (false === file_put_contents($file, serialize($data))) { + return false; + } + + if (!$profileIndexed) { + // Add to index + if (false === $file = fopen($this->getIndexFilename(), 'a')) { + return false; + } + + fputcsv($file, array( + $profile->getToken(), + $profile->getIp(), + $profile->getMethod(), + $profile->getUrl(), + $profile->getTime(), + $profile->getParentToken(), + $profile->getStatusCode(), + )); + fclose($file); + } + + return true; + } + + /** + * Gets filename to store data, associated to the token. + * + * @param string $token + * + * @return string The profile filename + */ + protected function getFilename($token) + { + // Uses 4 last characters, because first are mostly the same. + $folderA = substr($token, -2, 2); + $folderB = substr($token, -4, 2); + + return $this->folder.'/'.$folderA.'/'.$folderB.'/'.$token; + } + + /** + * Gets the index filename. + * + * @return string The index filename + */ + protected function getIndexFilename() + { + return $this->folder.'/index.csv'; + } + + /** + * Reads a line in the file, backward. + * + * This function automatically skips the empty lines and do not include the line return in result value. + * + * @param resource $file The file resource, with the pointer placed at the end of the line to read + * + * @return mixed A string representing the line or null if beginning of file is reached + */ + protected function readLineFromFile($file) + { + $line = ''; + $position = ftell($file); + + if (0 === $position) { + return; + } + + while (true) { + $chunkSize = min($position, 1024); + $position -= $chunkSize; + fseek($file, $position); + + if (0 === $chunkSize) { + // bof reached + break; + } + + $buffer = fread($file, $chunkSize); + + if (false === ($upTo = strrpos($buffer, "\n"))) { + $line = $buffer.$line; + continue; + } + + $position += $upTo; + $line = substr($buffer, $upTo + 1).$line; + fseek($file, max(0, $position), SEEK_SET); + + if ('' !== $line) { + break; + } + } + + return '' === $line ? null : $line; + } + + protected function createProfileFromData($token, $data, $parent = null) + { + $profile = new Profile($token); + $profile->setIp($data['ip']); + $profile->setMethod($data['method']); + $profile->setUrl($data['url']); + $profile->setTime($data['time']); + $profile->setStatusCode($data['status_code']); + $profile->setCollectors($data['data']); + + if (!$parent && $data['parent']) { + $parent = $this->read($data['parent']); + } + + if ($parent) { + $profile->setParent($parent); + } + + foreach ($data['children'] as $token) { + if (!$token || !file_exists($file = $this->getFilename($token))) { + continue; + } + + $profile->addChild($this->createProfileFromData($token, unserialize(file_get_contents($file)), $profile)); + } + + return $profile; + } +} diff --git a/vendor/symfony/http-kernel/Profiler/Profile.php b/vendor/symfony/http-kernel/Profiler/Profile.php new file mode 100644 index 00000000..fab8b41d --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/Profile.php @@ -0,0 +1,295 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; + +/** + * Profile. + * + * @author Fabien Potencier + */ +class Profile +{ + private $token; + + /** + * @var DataCollectorInterface[] + */ + private $collectors = array(); + + private $ip; + private $method; + private $url; + private $time; + private $statusCode; + + /** + * @var Profile + */ + private $parent; + + /** + * @var Profile[] + */ + private $children = array(); + + /** + * Constructor. + * + * @param string $token The token + */ + public function __construct($token) + { + $this->token = $token; + } + + /** + * Sets the token. + * + * @param string $token The token + */ + public function setToken($token) + { + $this->token = $token; + } + + /** + * Gets the token. + * + * @return string The token + */ + public function getToken() + { + return $this->token; + } + + /** + * Sets the parent token. + * + * @param Profile $parent + */ + public function setParent(Profile $parent) + { + $this->parent = $parent; + } + + /** + * Returns the parent profile. + * + * @return self + */ + public function getParent() + { + return $this->parent; + } + + /** + * Returns the parent token. + * + * @return null|string The parent token + */ + public function getParentToken() + { + return $this->parent ? $this->parent->getToken() : null; + } + + /** + * Returns the IP. + * + * @return string The IP + */ + public function getIp() + { + return $this->ip; + } + + /** + * Sets the IP. + * + * @param string $ip + */ + public function setIp($ip) + { + $this->ip = $ip; + } + + /** + * Returns the request method. + * + * @return string The request method + */ + public function getMethod() + { + return $this->method; + } + + public function setMethod($method) + { + $this->method = $method; + } + + /** + * Returns the URL. + * + * @return string The URL + */ + public function getUrl() + { + return $this->url; + } + + public function setUrl($url) + { + $this->url = $url; + } + + /** + * Returns the time. + * + * @return int The time + */ + public function getTime() + { + if (null === $this->time) { + return 0; + } + + return $this->time; + } + + /** + * @param int The time + */ + public function setTime($time) + { + $this->time = $time; + } + + /** + * @param int $statusCode + */ + public function setStatusCode($statusCode) + { + $this->statusCode = $statusCode; + } + + /** + * @return int + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Finds children profilers. + * + * @return self[] + */ + public function getChildren() + { + return $this->children; + } + + /** + * Sets children profiler. + * + * @param Profile[] $children + */ + public function setChildren(array $children) + { + $this->children = array(); + foreach ($children as $child) { + $this->addChild($child); + } + } + + /** + * Adds the child token. + * + * @param Profile $child + */ + public function addChild(Profile $child) + { + $this->children[] = $child; + $child->setParent($this); + } + + /** + * Gets a Collector by name. + * + * @param string $name A collector name + * + * @return DataCollectorInterface A DataCollectorInterface instance + * + * @throws \InvalidArgumentException if the collector does not exist + */ + public function getCollector($name) + { + if (!isset($this->collectors[$name])) { + throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); + } + + return $this->collectors[$name]; + } + + /** + * Gets the Collectors associated with this profile. + * + * @return DataCollectorInterface[] + */ + public function getCollectors() + { + return $this->collectors; + } + + /** + * Sets the Collectors associated with this profile. + * + * @param DataCollectorInterface[] $collectors + */ + public function setCollectors(array $collectors) + { + $this->collectors = array(); + foreach ($collectors as $collector) { + $this->addCollector($collector); + } + } + + /** + * Adds a Collector. + * + * @param DataCollectorInterface $collector A DataCollectorInterface instance + */ + public function addCollector(DataCollectorInterface $collector) + { + $this->collectors[$collector->getName()] = $collector; + } + + /** + * Returns true if a Collector for the given name exists. + * + * @param string $name A collector name + * + * @return bool + */ + public function hasCollector($name) + { + return isset($this->collectors[$name]); + } + + public function __sleep() + { + return array('token', 'parent', 'children', 'collectors', 'ip', 'method', 'url', 'time', 'statusCode'); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/Profiler.php b/vendor/symfony/http-kernel/Profiler/Profiler.php new file mode 100644 index 00000000..f31e2437 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/Profiler.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Psr\Log\LoggerInterface; + +/** + * Profiler. + * + * @author Fabien Potencier + */ +class Profiler +{ + /** + * @var ProfilerStorageInterface + */ + private $storage; + + /** + * @var DataCollectorInterface[] + */ + private $collectors = array(); + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var bool + */ + private $enabled = true; + + /** + * Constructor. + * + * @param ProfilerStorageInterface $storage A ProfilerStorageInterface instance + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(ProfilerStorageInterface $storage, LoggerInterface $logger = null) + { + $this->storage = $storage; + $this->logger = $logger; + } + + /** + * Disables the profiler. + */ + public function disable() + { + $this->enabled = false; + } + + /** + * Enables the profiler. + */ + public function enable() + { + $this->enabled = true; + } + + /** + * Loads the Profile for the given Response. + * + * @param Response $response A Response instance + * + * @return Profile|false A Profile instance + */ + public function loadProfileFromResponse(Response $response) + { + if (!$token = $response->headers->get('X-Debug-Token')) { + return false; + } + + return $this->loadProfile($token); + } + + /** + * Loads the Profile for the given token. + * + * @param string $token A token + * + * @return Profile A Profile instance + */ + public function loadProfile($token) + { + return $this->storage->read($token); + } + + /** + * Saves a Profile. + * + * @param Profile $profile A Profile instance + * + * @return bool + */ + public function saveProfile(Profile $profile) + { + // late collect + foreach ($profile->getCollectors() as $collector) { + if ($collector instanceof LateDataCollectorInterface) { + $collector->lateCollect(); + } + } + + if (!($ret = $this->storage->write($profile)) && null !== $this->logger) { + $this->logger->warning('Unable to store the profiler information.', array('configured_storage' => get_class($this->storage))); + } + + return $ret; + } + + /** + * Purges all data from the storage. + */ + public function purge() + { + $this->storage->purge(); + } + + /** + * Finds profiler tokens for the given criteria. + * + * @param string $ip The IP + * @param string $url The URL + * @param string $limit The maximum number of tokens to return + * @param string $method The request method + * @param string $start The start date to search from + * @param string $end The end date to search to + * @param string $statusCode The request status code + * + * @return array An array of tokens + * + * @see http://php.net/manual/en/datetime.formats.php for the supported date/time formats + */ + public function find($ip, $url, $limit, $method, $start, $end, $statusCode = null) + { + return $this->storage->find($ip, $url, $limit, $method, $this->getTimestamp($start), $this->getTimestamp($end), $statusCode); + } + + /** + * Collects data for the given Response. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * @param \Exception $exception An exception instance if the request threw one + * + * @return Profile|null A Profile instance or null if the profiler is disabled + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if (false === $this->enabled) { + return; + } + + $profile = new Profile(substr(hash('sha256', uniqid(mt_rand(), true)), 0, 6)); + $profile->setTime(time()); + $profile->setUrl($request->getUri()); + $profile->setMethod($request->getMethod()); + $profile->setStatusCode($response->getStatusCode()); + try { + $profile->setIp($request->getClientIp()); + } catch (ConflictingHeadersException $e) { + $profile->setIp('Unknown'); + } + + $response->headers->set('X-Debug-Token', $profile->getToken()); + + foreach ($this->collectors as $collector) { + $collector->collect($request, $response, $exception); + + // we need to clone for sub-requests + $profile->addCollector(clone $collector); + } + + return $profile; + } + + /** + * Gets the Collectors associated with this profiler. + * + * @return array An array of collectors + */ + public function all() + { + return $this->collectors; + } + + /** + * Sets the Collectors associated with this profiler. + * + * @param DataCollectorInterface[] $collectors An array of collectors + */ + public function set(array $collectors = array()) + { + $this->collectors = array(); + foreach ($collectors as $collector) { + $this->add($collector); + } + } + + /** + * Adds a Collector. + * + * @param DataCollectorInterface $collector A DataCollectorInterface instance + */ + public function add(DataCollectorInterface $collector) + { + $this->collectors[$collector->getName()] = $collector; + } + + /** + * Returns true if a Collector for the given name exists. + * + * @param string $name A collector name + * + * @return bool + */ + public function has($name) + { + return isset($this->collectors[$name]); + } + + /** + * Gets a Collector by name. + * + * @param string $name A collector name + * + * @return DataCollectorInterface A DataCollectorInterface instance + * + * @throws \InvalidArgumentException if the collector does not exist + */ + public function get($name) + { + if (!isset($this->collectors[$name])) { + throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); + } + + return $this->collectors[$name]; + } + + private function getTimestamp($value) + { + if (null === $value || '' == $value) { + return; + } + + try { + $value = new \DateTime(is_numeric($value) ? '@'.$value : $value); + } catch (\Exception $e) { + return; + } + + return $value->getTimestamp(); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php b/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php new file mode 100644 index 00000000..ea72af23 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +/** + * ProfilerStorageInterface. + * + * @author Fabien Potencier + */ +interface ProfilerStorageInterface +{ + /** + * Finds profiler tokens for the given criteria. + * + * @param string $ip The IP + * @param string $url The URL + * @param string $limit The maximum number of tokens to return + * @param string $method The request method + * @param int|null $start The start date to search from + * @param int|null $end The end date to search to + * + * @return array An array of tokens + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null); + + /** + * Reads data associated with the given token. + * + * The method returns false if the token does not exist in the storage. + * + * @param string $token A token + * + * @return Profile The profile associated with token + */ + public function read($token); + + /** + * Saves a Profile. + * + * @param Profile $profile A Profile instance + * + * @return bool Write operation successful + */ + public function write(Profile $profile); + + /** + * Purges all data from the database. + */ + public function purge(); +} diff --git a/vendor/symfony/http-kernel/README.md b/vendor/symfony/http-kernel/README.md new file mode 100644 index 00000000..cc5e74b6 --- /dev/null +++ b/vendor/symfony/http-kernel/README.md @@ -0,0 +1,16 @@ +HttpKernel Component +==================== + +The HttpKernel component provides a structured process for converting a Request +into a Response by making use of the EventDispatcher component. It's flexible +enough to create a full-stack framework (Symfony), a micro-framework (Silex) or +an advanced CMS system (Drupal). + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/http_kernel/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/http-kernel/TerminableInterface.php b/vendor/symfony/http-kernel/TerminableInterface.php new file mode 100644 index 00000000..d55a15b8 --- /dev/null +++ b/vendor/symfony/http-kernel/TerminableInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Terminable extends the Kernel request/response cycle with dispatching a post + * response event after sending the response and before shutting down the kernel. + * + * @author Jordi Boggiano + * @author Pierre Minnieur + */ +interface TerminableInterface +{ + /** + * Terminates a request/response cycle. + * + * Should be called after sending the response and before shutting down the kernel. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + */ + public function terminate(Request $request, Response $response); +} diff --git a/vendor/symfony/http-kernel/Tests/Bundle/BundleTest.php b/vendor/symfony/http-kernel/Tests/Bundle/BundleTest.php new file mode 100644 index 00000000..eeefccab --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Bundle/BundleTest.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Bundle; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionNotValidBundle\ExtensionNotValidBundle; +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\ExtensionPresentBundle; +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionAbsentBundle\ExtensionAbsentBundle; +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\Command\FooCommand; + +class BundleTest extends TestCase +{ + public function testGetContainerExtension() + { + $bundle = new ExtensionPresentBundle(); + + $this->assertInstanceOf( + 'Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\DependencyInjection\ExtensionPresentExtension', + $bundle->getContainerExtension() + ); + } + + public function testRegisterCommands() + { + $cmd = new FooCommand(); + $app = $this->getMockBuilder('Symfony\Component\Console\Application')->getMock(); + $app->expects($this->once())->method('add')->with($this->equalTo($cmd)); + + $bundle = new ExtensionPresentBundle(); + $bundle->registerCommands($app); + + $bundle2 = new ExtensionAbsentBundle(); + + $this->assertNull($bundle2->registerCommands($app)); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage must implement Symfony\Component\DependencyInjection\Extension\ExtensionInterface + */ + public function testGetContainerExtensionWithInvalidClass() + { + $bundle = new ExtensionNotValidBundle(); + $bundle->getContainerExtension(); + } + + public function testHttpKernelRegisterCommandsIgnoresCommandsThatAreRegisteredAsServices() + { + $container = new ContainerBuilder(); + $container->register('console.command.symfony_component_httpkernel_tests_fixtures_extensionpresentbundle_command_foocommand', 'Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\Command\FooCommand'); + + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->getMock(); + // add() is never called when the found command classes are already registered as services + $application->expects($this->never())->method('add'); + + $bundle = new ExtensionPresentBundle(); + $bundle->setContainer($container); + $bundle->registerCommands($application); + } + + public function testBundleNameIsGuessedFromClass() + { + $bundle = new GuessedNameBundle(); + + $this->assertSame('Symfony\Component\HttpKernel\Tests\Bundle', $bundle->getNamespace()); + $this->assertSame('GuessedNameBundle', $bundle->getName()); + } + + public function testBundleNameCanBeExplicitlyProvided() + { + $bundle = new NamedBundle(); + + $this->assertSame('ExplicitlyNamedBundle', $bundle->getName()); + $this->assertSame('Symfony\Component\HttpKernel\Tests\Bundle', $bundle->getNamespace()); + $this->assertSame('ExplicitlyNamedBundle', $bundle->getName()); + } +} + +class NamedBundle extends Bundle +{ + public function __construct() + { + $this->name = 'ExplicitlyNamedBundle'; + } +} + +class GuessedNameBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Tests/CacheClearer/ChainCacheClearerTest.php b/vendor/symfony/http-kernel/Tests/CacheClearer/ChainCacheClearerTest.php new file mode 100644 index 00000000..1bc85334 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/CacheClearer/ChainCacheClearerTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\CacheClearer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer; + +class ChainCacheClearerTest extends TestCase +{ + protected static $cacheDir; + + public static function setUpBeforeClass() + { + self::$cacheDir = tempnam(sys_get_temp_dir(), 'sf2_cache_clearer_dir'); + } + + public static function tearDownAfterClass() + { + @unlink(self::$cacheDir); + } + + public function testInjectClearersInConstructor() + { + $clearer = $this->getMockClearer(); + $clearer + ->expects($this->once()) + ->method('clear'); + + $chainClearer = new ChainCacheClearer(array($clearer)); + $chainClearer->clear(self::$cacheDir); + } + + public function testInjectClearerUsingAdd() + { + $clearer = $this->getMockClearer(); + $clearer + ->expects($this->once()) + ->method('clear'); + + $chainClearer = new ChainCacheClearer(); + $chainClearer->add($clearer); + $chainClearer->clear(self::$cacheDir); + } + + protected function getMockClearer() + { + return $this->getMockBuilder('Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface')->getMock(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/CacheClearer/Psr6CacheClearerTest.php b/vendor/symfony/http-kernel/Tests/CacheClearer/Psr6CacheClearerTest.php new file mode 100644 index 00000000..a5d9b6ef --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/CacheClearer/Psr6CacheClearerTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\CacheClearer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; +use Psr\Cache\CacheItemPoolInterface; + +class Psr6CacheClearerTest extends TestCase +{ + public function testClearPoolsInjectedInConstructor() + { + $pool = $this->getMockBuilder(CacheItemPoolInterface::class)->getMock(); + $pool + ->expects($this->once()) + ->method('clear'); + + (new Psr6CacheClearer(array('pool' => $pool)))->clear(''); + } + + public function testClearPool() + { + $pool = $this->getMockBuilder(CacheItemPoolInterface::class)->getMock(); + $pool + ->expects($this->once()) + ->method('clear'); + + (new Psr6CacheClearer(array('pool' => $pool)))->clearPool('pool'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Cache pool not found: unknown + */ + public function testClearPoolThrowsExceptionOnUnreferencedPool() + { + (new Psr6CacheClearer())->clearPool('unknown'); + } + + /** + * @group legacy + * @expectedDeprecation The Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer::addPool() method is deprecated since version 3.3 and will be removed in 4.0. Pass an array of pools indexed by name to the constructor instead. + */ + public function testClearPoolsInjectedByAdder() + { + $pool1 = $this->getMockBuilder(CacheItemPoolInterface::class)->getMock(); + $pool1 + ->expects($this->once()) + ->method('clear'); + + $pool2 = $this->getMockBuilder(CacheItemPoolInterface::class)->getMock(); + $pool2 + ->expects($this->once()) + ->method('clear'); + + $clearer = new Psr6CacheClearer(array('pool1' => $pool1)); + $clearer->addPool($pool2); + $clearer->clear(''); + } +} diff --git a/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php b/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php new file mode 100644 index 00000000..d07ade30 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\CacheWarmer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; + +class CacheWarmerAggregateTest extends TestCase +{ + protected static $cacheDir; + + public static function setUpBeforeClass() + { + self::$cacheDir = tempnam(sys_get_temp_dir(), 'sf2_cache_warmer_dir'); + } + + public static function tearDownAfterClass() + { + @unlink(self::$cacheDir); + } + + public function testInjectWarmersUsingConstructor() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + public function testInjectWarmersUsingAdd() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(); + $aggregate->add($warmer); + $aggregate->warmUp(self::$cacheDir); + } + + public function testInjectWarmersUsingSetWarmers() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(); + $aggregate->setWarmers(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + public function testWarmupDoesCallWarmupOnOptionalWarmersWhenEnableOptionalWarmersIsEnabled() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->never()) + ->method('isOptional'); + $warmer + ->expects($this->once()) + ->method('warmUp'); + + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->enableOptionalWarmers(); + $aggregate->warmUp(self::$cacheDir); + } + + public function testWarmupDoesNotCallWarmupOnOptionalWarmersWhenEnableOptionalWarmersIsNotEnabled() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('isOptional') + ->will($this->returnValue(true)); + $warmer + ->expects($this->never()) + ->method('warmUp'); + + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + protected function getCacheWarmerMock() + { + $warmer = $this->getMockBuilder('Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface') + ->disableOriginalConstructor() + ->getMock(); + + return $warmer; + } +} diff --git a/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerTest.php b/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerTest.php new file mode 100644 index 00000000..05666cb0 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\CacheWarmer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer; + +class CacheWarmerTest extends TestCase +{ + protected static $cacheFile; + + public static function setUpBeforeClass() + { + self::$cacheFile = tempnam(sys_get_temp_dir(), 'sf2_cache_warmer_dir'); + } + + public static function tearDownAfterClass() + { + @unlink(self::$cacheFile); + } + + public function testWriteCacheFileCreatesTheFile() + { + $warmer = new TestCacheWarmer(self::$cacheFile); + $warmer->warmUp(dirname(self::$cacheFile)); + + $this->assertFileExists(self::$cacheFile); + } + + /** + * @expectedException \RuntimeException + */ + public function testWriteNonWritableCacheFileThrowsARuntimeException() + { + $nonWritableFile = '/this/file/is/very/probably/not/writable'; + $warmer = new TestCacheWarmer($nonWritableFile); + $warmer->warmUp(dirname($nonWritableFile)); + } +} + +class TestCacheWarmer extends CacheWarmer +{ + protected $file; + + public function __construct($file) + { + $this->file = $file; + } + + public function warmUp($cacheDir) + { + $this->writeCacheFile($this->file, 'content'); + } + + public function isOptional() + { + return false; + } +} diff --git a/vendor/symfony/http-kernel/Tests/ClientTest.php b/vendor/symfony/http-kernel/Tests/ClientTest.php new file mode 100644 index 00000000..1ac72c73 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/ClientTest.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpKernel\Tests\Fixtures\TestClient; + +/** + * @group time-sensitive + */ +class ClientTest extends TestCase +{ + public function testDoRequest() + { + $client = new Client(new TestHttpKernel()); + + $client->request('GET', '/'); + $this->assertEquals('Request: /', $client->getResponse()->getContent(), '->doRequest() uses the request handler to make the request'); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Request', $client->getInternalRequest()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Request', $client->getRequest()); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Response', $client->getInternalResponse()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $client->getResponse()); + + $client->request('GET', 'http://www.example.com/'); + $this->assertEquals('Request: /', $client->getResponse()->getContent(), '->doRequest() uses the request handler to make the request'); + $this->assertEquals('www.example.com', $client->getRequest()->getHost(), '->doRequest() uses the request handler to make the request'); + + $client->request('GET', 'http://www.example.com/?parameter=http://google.com'); + $this->assertEquals('http://www.example.com/?parameter='.urlencode('http://google.com'), $client->getRequest()->getUri(), '->doRequest() uses the request handler to make the request'); + } + + public function testGetScript() + { + $client = new TestClient(new TestHttpKernel()); + $client->insulate(); + $client->request('GET', '/'); + + $this->assertEquals('Request: /', $client->getResponse()->getContent(), '->getScript() returns a script that uses the request handler to make the request'); + } + + public function testFilterResponseConvertsCookies() + { + $client = new Client(new TestHttpKernel()); + + $r = new \ReflectionObject($client); + $m = $r->getMethod('filterResponse'); + $m->setAccessible(true); + + $expected = array( + 'foo=bar; expires=Sun, 15-Feb-2009 20:00:00 GMT; max-age='.(strtotime('Sun, 15-Feb-2009 20:00:00 GMT') - time()).'; path=/foo; domain=http://example.com; secure; httponly', + 'foo1=bar1; expires=Sun, 15-Feb-2009 20:00:00 GMT; max-age='.(strtotime('Sun, 15-Feb-2009 20:00:00 GMT') - time()).'; path=/foo; domain=http://example.com; secure; httponly', + ); + + $response = new Response(); + $response->headers->setCookie(new Cookie('foo', 'bar', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); + $domResponse = $m->invoke($client, $response); + $this->assertEquals($expected[0], $domResponse->getHeader('Set-Cookie')); + + $response = new Response(); + $response->headers->setCookie(new Cookie('foo', 'bar', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); + $response->headers->setCookie(new Cookie('foo1', 'bar1', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); + $domResponse = $m->invoke($client, $response); + $this->assertEquals($expected[0], $domResponse->getHeader('Set-Cookie')); + $this->assertEquals($expected, $domResponse->getHeader('Set-Cookie', false)); + } + + public function testFilterResponseSupportsStreamedResponses() + { + $client = new Client(new TestHttpKernel()); + + $r = new \ReflectionObject($client); + $m = $r->getMethod('filterResponse'); + $m->setAccessible(true); + + $response = new StreamedResponse(function () { + echo 'foo'; + }); + + $domResponse = $m->invoke($client, $response); + $this->assertEquals('foo', $domResponse->getContent()); + } + + public function testUploadedFile() + { + $source = tempnam(sys_get_temp_dir(), 'source'); + $target = sys_get_temp_dir().'/sf.moved.file'; + @unlink($target); + + $kernel = new TestHttpKernel(); + $client = new Client($kernel); + + $files = array( + array('tmp_name' => $source, 'name' => 'original', 'type' => 'mime/original', 'size' => 123, 'error' => UPLOAD_ERR_OK), + new UploadedFile($source, 'original', 'mime/original', 123, UPLOAD_ERR_OK, true), + ); + + $file = null; + foreach ($files as $file) { + $client->request('POST', '/', array(), array('foo' => $file)); + + $files = $client->getRequest()->files->all(); + + $this->assertCount(1, $files); + + $file = $files['foo']; + + $this->assertEquals('original', $file->getClientOriginalName()); + $this->assertEquals('mime/original', $file->getClientMimeType()); + $this->assertEquals('123', $file->getClientSize()); + $this->assertTrue($file->isValid()); + } + + $file->move(dirname($target), basename($target)); + + $this->assertFileExists($target); + unlink($target); + } + + public function testUploadedFileWhenNoFileSelected() + { + $kernel = new TestHttpKernel(); + $client = new Client($kernel); + + $file = array('tmp_name' => '', 'name' => '', 'type' => '', 'size' => 0, 'error' => UPLOAD_ERR_NO_FILE); + + $client->request('POST', '/', array(), array('foo' => $file)); + + $files = $client->getRequest()->files->all(); + + $this->assertCount(1, $files); + $this->assertNull($files['foo']); + } + + public function testUploadedFileWhenSizeExceedsUploadMaxFileSize() + { + $source = tempnam(sys_get_temp_dir(), 'source'); + + $kernel = new TestHttpKernel(); + $client = new Client($kernel); + + $file = $this + ->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile') + ->setConstructorArgs(array($source, 'original', 'mime/original', 123, UPLOAD_ERR_OK, true)) + ->setMethods(array('getSize')) + ->getMock() + ; + + $file->expects($this->once()) + ->method('getSize') + ->will($this->returnValue(INF)) + ; + + $client->request('POST', '/', array(), array($file)); + + $files = $client->getRequest()->files->all(); + + $this->assertCount(1, $files); + + $file = $files[0]; + + $this->assertFalse($file->isValid()); + $this->assertEquals(UPLOAD_ERR_INI_SIZE, $file->getError()); + $this->assertEquals('mime/original', $file->getClientMimeType()); + $this->assertEquals('original', $file->getClientOriginalName()); + $this->assertEquals(0, $file->getClientSize()); + + unlink($source); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Config/EnvParametersResourceTest.php b/vendor/symfony/http-kernel/Tests/Config/EnvParametersResourceTest.php new file mode 100644 index 00000000..6fe8a675 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Config/EnvParametersResourceTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Config; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Config\EnvParametersResource; + +class EnvParametersResourceTest extends TestCase +{ + protected $prefix = '__DUMMY_'; + protected $initialEnv; + protected $resource; + + protected function setUp() + { + $this->initialEnv = array( + $this->prefix.'1' => 'foo', + $this->prefix.'2' => 'bar', + ); + + foreach ($this->initialEnv as $key => $value) { + $_SERVER[$key] = $value; + } + + $this->resource = new EnvParametersResource($this->prefix); + } + + protected function tearDown() + { + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, $this->prefix)) { + unset($_SERVER[$key]); + } + } + } + + public function testGetResource() + { + $this->assertSame( + array('prefix' => $this->prefix, 'variables' => $this->initialEnv), + $this->resource->getResource(), + '->getResource() returns the resource' + ); + } + + public function testToString() + { + $this->assertSame( + serialize(array('prefix' => $this->prefix, 'variables' => $this->initialEnv)), + (string) $this->resource + ); + } + + public function testIsFreshNotChanged() + { + $this->assertTrue( + $this->resource->isFresh(time()), + '->isFresh() returns true if the variables have not changed' + ); + } + + public function testIsFreshValueChanged() + { + reset($this->initialEnv); + $_SERVER[key($this->initialEnv)] = 'baz'; + + $this->assertFalse( + $this->resource->isFresh(time()), + '->isFresh() returns false if a variable has been changed' + ); + } + + public function testIsFreshValueRemoved() + { + reset($this->initialEnv); + unset($_SERVER[key($this->initialEnv)]); + + $this->assertFalse( + $this->resource->isFresh(time()), + '->isFresh() returns false if a variable has been removed' + ); + } + + public function testIsFreshValueAdded() + { + $_SERVER[$this->prefix.'3'] = 'foo'; + + $this->assertFalse( + $this->resource->isFresh(time()), + '->isFresh() returns false if a variable has been added' + ); + } + + public function testSerializeUnserialize() + { + $this->assertEquals($this->resource, unserialize(serialize($this->resource))); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Config/FileLocatorTest.php b/vendor/symfony/http-kernel/Tests/Config/FileLocatorTest.php new file mode 100644 index 00000000..6265f027 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Config/FileLocatorTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Config; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Config\FileLocator; + +class FileLocatorTest extends TestCase +{ + public function testLocate() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); + $kernel + ->expects($this->atLeastOnce()) + ->method('locateResource') + ->with('@BundleName/some/path', null, true) + ->will($this->returnValue('/bundle-name/some/path')); + $locator = new FileLocator($kernel); + $this->assertEquals('/bundle-name/some/path', $locator->locate('@BundleName/some/path')); + + $kernel + ->expects($this->never()) + ->method('locateResource'); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('LogicException'); + $locator->locate('/some/path'); + } + + public function testLocateWithGlobalResourcePath() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); + $kernel + ->expects($this->atLeastOnce()) + ->method('locateResource') + ->with('@BundleName/some/path', '/global/resource/path', false); + + $locator = new FileLocator($kernel, '/global/resource/path'); + $locator->locate('@BundleName/some/path', null, false); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolverTest.php b/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolverTest.php new file mode 100644 index 00000000..08041390 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolverTest.php @@ -0,0 +1,349 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Controller; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\ExtendingRequest; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\ExtendingSession; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\NullableController; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController; +use Symfony\Component\HttpFoundation\Request; + +class ArgumentResolverTest extends TestCase +{ + /** @var ArgumentResolver */ + private static $resolver; + + public static function setUpBeforeClass() + { + $factory = new ArgumentMetadataFactory(); + + self::$resolver = new ArgumentResolver($factory); + } + + public function testDefaultState() + { + $this->assertEquals(self::$resolver, new ArgumentResolver()); + $this->assertNotEquals(self::$resolver, new ArgumentResolver(null, array(new RequestAttributeValueResolver()))); + } + + public function testGetArguments() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerWithFoo'); + + $this->assertEquals(array('foo'), self::$resolver->getArguments($request, $controller), '->getArguments() returns an array of arguments for the controller method'); + } + + public function testGetArgumentsReturnsEmptyArrayWhenNoArguments() + { + $request = Request::create('/'); + $controller = array(new self(), 'controllerWithoutArguments'); + + $this->assertEquals(array(), self::$resolver->getArguments($request, $controller), '->getArguments() returns an empty array if the method takes no arguments'); + } + + public function testGetArgumentsUsesDefaultValue() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerWithFooAndDefaultBar'); + + $this->assertEquals(array('foo', null), self::$resolver->getArguments($request, $controller), '->getArguments() uses default values if present'); + } + + public function testGetArgumentsOverrideDefaultValueByRequestAttribute() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', 'bar'); + $controller = array(new self(), 'controllerWithFooAndDefaultBar'); + + $this->assertEquals(array('foo', 'bar'), self::$resolver->getArguments($request, $controller), '->getArguments() overrides default values if provided in the request attributes'); + } + + public function testGetArgumentsFromClosure() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo) {}; + + $this->assertEquals(array('foo'), self::$resolver->getArguments($request, $controller)); + } + + public function testGetArgumentsUsesDefaultValueFromClosure() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo, $bar = 'bar') {}; + + $this->assertEquals(array('foo', 'bar'), self::$resolver->getArguments($request, $controller)); + } + + public function testGetArgumentsFromInvokableObject() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = new self(); + + $this->assertEquals(array('foo', null), self::$resolver->getArguments($request, $controller)); + + // Test default bar overridden by request attribute + $request->attributes->set('bar', 'bar'); + + $this->assertEquals(array('foo', 'bar'), self::$resolver->getArguments($request, $controller)); + } + + public function testGetArgumentsFromFunctionName() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = __NAMESPACE__.'\controller_function'; + + $this->assertEquals(array('foo', 'foobar'), self::$resolver->getArguments($request, $controller)); + } + + public function testGetArgumentsFailsOnUnresolvedValue() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = array(new self(), 'controllerWithFooBarFoobar'); + + try { + self::$resolver->getArguments($request, $controller); + $this->fail('->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } catch (\Exception $e) { + $this->assertInstanceOf('\RuntimeException', $e, '->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } + } + + public function testGetArgumentsInjectsRequest() + { + $request = Request::create('/'); + $controller = array(new self(), 'controllerWithRequest'); + + $this->assertEquals(array($request), self::$resolver->getArguments($request, $controller), '->getArguments() injects the request'); + } + + public function testGetArgumentsInjectsExtendingRequest() + { + $request = ExtendingRequest::create('/'); + $controller = array(new self(), 'controllerWithExtendingRequest'); + + $this->assertEquals(array($request), self::$resolver->getArguments($request, $controller), '->getArguments() injects the request when extended'); + } + + /** + * @requires PHP 5.6 + */ + public function testGetVariadicArguments() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', array('foo', 'bar')); + $controller = array(new VariadicController(), 'action'); + + $this->assertEquals(array('foo', 'foo', 'bar'), self::$resolver->getArguments($request, $controller)); + } + + /** + * @requires PHP 5.6 + * @expectedException \InvalidArgumentException + */ + public function testGetVariadicArgumentsWithoutArrayInRequest() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', 'foo'); + $controller = array(new VariadicController(), 'action'); + + self::$resolver->getArguments($request, $controller); + } + + /** + * @requires PHP 5.6 + * @expectedException \InvalidArgumentException + */ + public function testGetArgumentWithoutArray() + { + $factory = new ArgumentMetadataFactory(); + $valueResolver = $this->getMockBuilder(ArgumentValueResolverInterface::class)->getMock(); + $resolver = new ArgumentResolver($factory, array($valueResolver)); + + $valueResolver->expects($this->any())->method('supports')->willReturn(true); + $valueResolver->expects($this->any())->method('resolve')->willReturn('foo'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', 'foo'); + $controller = array($this, 'controllerWithFooAndDefaultBar'); + $resolver->getArguments($request, $controller); + } + + /** + * @expectedException \RuntimeException + */ + public function testIfExceptionIsThrownWhenMissingAnArgument() + { + $request = Request::create('/'); + $controller = array($this, 'controllerWithFoo'); + + self::$resolver->getArguments($request, $controller); + } + + /** + * @requires PHP 7.1 + */ + public function testGetNullableArguments() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', new \stdClass()); + $request->attributes->set('mandatory', 'mandatory'); + $controller = array(new NullableController(), 'action'); + + $this->assertEquals(array('foo', new \stdClass(), 'value', 'mandatory'), self::$resolver->getArguments($request, $controller)); + } + + /** + * @requires PHP 7.1 + */ + public function testGetNullableArgumentsWithDefaults() + { + $request = Request::create('/'); + $request->attributes->set('mandatory', 'mandatory'); + $controller = array(new NullableController(), 'action'); + + $this->assertEquals(array(null, null, 'value', 'mandatory'), self::$resolver->getArguments($request, $controller)); + } + + public function testGetSessionArguments() + { + $session = new Session(new MockArraySessionStorage()); + $request = Request::create('/'); + $request->setSession($session); + $controller = array($this, 'controllerWithSession'); + + $this->assertEquals(array($session), self::$resolver->getArguments($request, $controller)); + } + + public function testGetSessionArgumentsWithExtendedSession() + { + $session = new ExtendingSession(new MockArraySessionStorage()); + $request = Request::create('/'); + $request->setSession($session); + $controller = array($this, 'controllerWithExtendingSession'); + + $this->assertEquals(array($session), self::$resolver->getArguments($request, $controller)); + } + + public function testGetSessionArgumentsWithInterface() + { + $session = $this->getMockBuilder(SessionInterface::class)->getMock(); + $request = Request::create('/'); + $request->setSession($session); + $controller = array($this, 'controllerWithSessionInterface'); + + $this->assertEquals(array($session), self::$resolver->getArguments($request, $controller)); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetSessionMissMatchWithInterface() + { + $session = $this->getMockBuilder(SessionInterface::class)->getMock(); + $request = Request::create('/'); + $request->setSession($session); + $controller = array($this, 'controllerWithExtendingSession'); + + self::$resolver->getArguments($request, $controller); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetSessionMissMatchWithImplementation() + { + $session = new Session(new MockArraySessionStorage()); + $request = Request::create('/'); + $request->setSession($session); + $controller = array($this, 'controllerWithExtendingSession'); + + self::$resolver->getArguments($request, $controller); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetSessionMissMatchOnNull() + { + $request = Request::create('/'); + $controller = array($this, 'controllerWithExtendingSession'); + + self::$resolver->getArguments($request, $controller); + } + + public function __invoke($foo, $bar = null) + { + } + + public function controllerWithFoo($foo) + { + } + + public function controllerWithoutArguments() + { + } + + protected function controllerWithFooAndDefaultBar($foo, $bar = null) + { + } + + protected function controllerWithFooBarFoobar($foo, $bar, $foobar) + { + } + + protected function controllerWithRequest(Request $request) + { + } + + protected function controllerWithExtendingRequest(ExtendingRequest $request) + { + } + + protected function controllerWithSession(Session $session) + { + } + + protected function controllerWithSessionInterface(SessionInterface $session) + { + } + + protected function controllerWithExtendingSession(ExtendingSession $session) + { + } +} + +function controller_function($foo, $foobar) +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Controller/ContainerControllerResolverTest.php b/vendor/symfony/http-kernel/Tests/Controller/ContainerControllerResolverTest.php new file mode 100644 index 00000000..30b535e8 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Controller/ContainerControllerResolverTest.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Controller; + +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ContainerControllerResolver; + +class ContainerControllerResolverTest extends ControllerResolverTest +{ + public function testGetControllerService() + { + $container = $this->createMockContainer(); + $container->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($this)) + ; + + $resolver = $this->createControllerResolver(null, $container); + $request = Request::create('/'); + $request->attributes->set('_controller', 'foo:controllerMethod1'); + + $controller = $resolver->getController($request); + + $this->assertInstanceOf(get_class($this), $controller[0]); + $this->assertSame('controllerMethod1', $controller[1]); + } + + public function testGetControllerInvokableService() + { + $invokableController = new InvokableController('bar'); + + $container = $this->createMockContainer(); + $container->expects($this->once()) + ->method('has') + ->with('foo') + ->will($this->returnValue(true)) + ; + $container->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($invokableController)) + ; + + $resolver = $this->createControllerResolver(null, $container); + $request = Request::create('/'); + $request->attributes->set('_controller', 'foo'); + + $controller = $resolver->getController($request); + + $this->assertEquals($invokableController, $controller); + } + + public function testGetControllerInvokableServiceWithClassNameAsName() + { + $invokableController = new InvokableController('bar'); + $className = __NAMESPACE__.'\InvokableController'; + + $container = $this->createMockContainer(); + $container->expects($this->once()) + ->method('has') + ->with($className) + ->will($this->returnValue(true)) + ; + $container->expects($this->once()) + ->method('get') + ->with($className) + ->will($this->returnValue($invokableController)) + ; + + $resolver = $this->createControllerResolver(null, $container); + $request = Request::create('/'); + $request->attributes->set('_controller', $className); + + $controller = $resolver->getController($request); + + $this->assertEquals($invokableController, $controller); + } + + /** + * @dataProvider getUndefinedControllers + */ + public function testGetControllerOnNonUndefinedFunction($controller, $exceptionName = null, $exceptionMessage = null) + { + // All this logic needs to be duplicated, since calling parent::testGetControllerOnNonUndefinedFunction will override the expected excetion and not use the regex + $resolver = $this->createControllerResolver(); + if (method_exists($this, 'expectException')) { + $this->expectException($exceptionName); + $this->expectExceptionMessageRegExp($exceptionMessage); + } else { + $this->setExpectedExceptionRegExp($exceptionName, $exceptionMessage); + } + + $request = Request::create('/'); + $request->attributes->set('_controller', $controller); + $resolver->getController($request); + } + + public function getUndefinedControllers() + { + return array( + array('foo', \LogicException::class, '/Unable to parse the controller name "foo"\./'), + array('oof::bar', \InvalidArgumentException::class, '/Class "oof" does not exist\./'), + array('stdClass', \LogicException::class, '/Unable to parse the controller name "stdClass"\./'), + array( + 'Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest::bar', + \InvalidArgumentException::class, + '/.?[cC]ontroller(.*?) for URI "\/" is not callable\.( Expected method(.*) Available methods)?/', + ), + ); + } + + protected function createControllerResolver(LoggerInterface $logger = null, ContainerInterface $container = null) + { + if (!$container) { + $container = $this->createMockContainer(); + } + + return new ContainerControllerResolver($container, $logger); + } + + protected function createMockContainer() + { + return $this->getMockBuilder(ContainerInterface::class)->getMock(); + } +} + +class InvokableController +{ + public function __construct($bar) // mandatory argument to prevent automatic instantiation + { + } + + public function __invoke() + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php b/vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php new file mode 100644 index 00000000..190e15ad --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php @@ -0,0 +1,331 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Controller; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpKernel\Controller\ControllerResolver; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\NullableController; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController; +use Symfony\Component\HttpFoundation\Request; + +class ControllerResolverTest extends TestCase +{ + public function testGetControllerWithoutControllerParameter() + { + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $logger->expects($this->once())->method('warning')->with('Unable to look for the controller as the "_controller" parameter is missing.'); + $resolver = $this->createControllerResolver($logger); + + $request = Request::create('/'); + $this->assertFalse($resolver->getController($request), '->getController() returns false when the request has no _controller attribute'); + } + + public function testGetControllerWithLambda() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', $lambda = function () {}); + $controller = $resolver->getController($request); + $this->assertSame($lambda, $controller); + } + + public function testGetControllerWithObjectAndInvokeMethod() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', $this); + $controller = $resolver->getController($request); + $this->assertSame($this, $controller); + } + + public function testGetControllerWithObjectAndMethod() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', array($this, 'controllerMethod1')); + $controller = $resolver->getController($request); + $this->assertSame(array($this, 'controllerMethod1'), $controller); + } + + public function testGetControllerWithClassAndMethod() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', array('Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest', 'controllerMethod4')); + $controller = $resolver->getController($request); + $this->assertSame(array('Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest', 'controllerMethod4'), $controller); + } + + public function testGetControllerWithObjectAndMethodAsString() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest::controllerMethod1'); + $controller = $resolver->getController($request); + $this->assertInstanceOf('Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest', $controller[0], '->getController() returns a PHP callable'); + } + + public function testGetControllerWithClassAndInvokeMethod() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest'); + $controller = $resolver->getController($request); + $this->assertInstanceOf('Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest', $controller); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetControllerOnObjectWithoutInvokeMethod() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', new \stdClass()); + $resolver->getController($request); + } + + public function testGetControllerWithFunction() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'Symfony\Component\HttpKernel\Tests\Controller\some_controller_function'); + $controller = $resolver->getController($request); + $this->assertSame('Symfony\Component\HttpKernel\Tests\Controller\some_controller_function', $controller); + } + + /** + * @dataProvider getUndefinedControllers + */ + public function testGetControllerOnNonUndefinedFunction($controller, $exceptionName = null, $exceptionMessage = null) + { + $resolver = $this->createControllerResolver(); + if (method_exists($this, 'expectException')) { + $this->expectException($exceptionName); + $this->expectExceptionMessage($exceptionMessage); + } else { + $this->setExpectedException($exceptionName, $exceptionMessage); + } + + $request = Request::create('/'); + $request->attributes->set('_controller', $controller); + $resolver->getController($request); + } + + public function getUndefinedControllers() + { + return array( + array(1, 'InvalidArgumentException', 'Unable to find controller "1".'), + array('foo', 'InvalidArgumentException', 'Unable to find controller "foo".'), + array('oof::bar', 'InvalidArgumentException', 'Class "oof" does not exist.'), + array('stdClass', 'InvalidArgumentException', 'Unable to find controller "stdClass".'), + array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::staticsAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Expected method "staticsAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest", did you mean "staticAction"?'), + array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::privateAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Method "privateAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'), + array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::protectedAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Method "protectedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'), + array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::undefinedAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Expected method "undefinedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest". Available methods: "publicAction", "staticAction"'), + ); + } + + /** + * @group legacy + */ + public function testGetArguments() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $controller = array(new self(), 'testGetArguments'); + $this->assertEquals(array(), $resolver->getArguments($request, $controller), '->getArguments() returns an empty array if the method takes no arguments'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerMethod1'); + $this->assertEquals(array('foo'), $resolver->getArguments($request, $controller), '->getArguments() returns an array of arguments for the controller method'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerMethod2'); + $this->assertEquals(array('foo', null), $resolver->getArguments($request, $controller), '->getArguments() uses default values if present'); + + $request->attributes->set('bar', 'bar'); + $this->assertEquals(array('foo', 'bar'), $resolver->getArguments($request, $controller), '->getArguments() overrides default values if provided in the request attributes'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo) {}; + $this->assertEquals(array('foo'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo, $bar = 'bar') {}; + $this->assertEquals(array('foo', 'bar'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = new self(); + $this->assertEquals(array('foo', null), $resolver->getArguments($request, $controller)); + $request->attributes->set('bar', 'bar'); + $this->assertEquals(array('foo', 'bar'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = 'Symfony\Component\HttpKernel\Tests\Controller\some_controller_function'; + $this->assertEquals(array('foo', 'foobar'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = array(new self(), 'controllerMethod3'); + + try { + $resolver->getArguments($request, $controller); + $this->fail('->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } catch (\Exception $e) { + $this->assertInstanceOf('\RuntimeException', $e, '->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } + + $request = Request::create('/'); + $controller = array(new self(), 'controllerMethod5'); + $this->assertEquals(array($request), $resolver->getArguments($request, $controller), '->getArguments() injects the request'); + } + + /** + * @requires PHP 5.6 + * @group legacy + */ + public function testGetVariadicArguments() + { + $resolver = new ControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', array('foo', 'bar')); + $controller = array(new VariadicController(), 'action'); + $this->assertEquals(array('foo', 'foo', 'bar'), $resolver->getArguments($request, $controller)); + } + + public function testCreateControllerCanReturnAnyCallable() + { + $mock = $this->getMockBuilder('Symfony\Component\HttpKernel\Controller\ControllerResolver')->setMethods(array('createController'))->getMock(); + $mock->expects($this->once())->method('createController')->will($this->returnValue('Symfony\Component\HttpKernel\Tests\Controller\some_controller_function')); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'foobar'); + $mock->getController($request); + } + + /** + * @expectedException \RuntimeException + * @group legacy + */ + public function testIfExceptionIsThrownWhenMissingAnArgument() + { + $resolver = new ControllerResolver(); + $request = Request::create('/'); + + $controller = array($this, 'controllerMethod1'); + + $resolver->getArguments($request, $controller); + } + + /** + * @requires PHP 7.1 + * @group legacy + */ + public function testGetNullableArguments() + { + $resolver = new ControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', new \stdClass()); + $request->attributes->set('mandatory', 'mandatory'); + $controller = array(new NullableController(), 'action'); + $this->assertEquals(array('foo', new \stdClass(), 'value', 'mandatory'), $resolver->getArguments($request, $controller)); + } + + /** + * @requires PHP 7.1 + * @group legacy + */ + public function testGetNullableArgumentsWithDefaults() + { + $resolver = new ControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('mandatory', 'mandatory'); + $controller = array(new NullableController(), 'action'); + $this->assertEquals(array(null, null, 'value', 'mandatory'), $resolver->getArguments($request, $controller)); + } + + protected function createControllerResolver(LoggerInterface $logger = null) + { + return new ControllerResolver($logger); + } + + public function __invoke($foo, $bar = null) + { + } + + public function controllerMethod1($foo) + { + } + + protected function controllerMethod2($foo, $bar = null) + { + } + + protected function controllerMethod3($foo, $bar, $foobar) + { + } + + protected static function controllerMethod4() + { + } + + protected function controllerMethod5(Request $request) + { + } +} + +function some_controller_function($foo, $foobar) +{ +} + +class ControllerTest +{ + public function publicAction() + { + } + + private function privateAction() + { + } + + protected function protectedAction() + { + } + + public static function staticAction() + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php b/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php new file mode 100644 index 00000000..b4b449f3 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\ControllerMetadata; + +use Fake\ImportedAndFake; +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\BasicTypesController; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\NullableController; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController; + +class ArgumentMetadataFactoryTest extends TestCase +{ + /** + * @var ArgumentMetadataFactory + */ + private $factory; + + protected function setUp() + { + $this->factory = new ArgumentMetadataFactory(); + } + + public function testSignature1() + { + $arguments = $this->factory->createArgumentMetadata(array($this, 'signature1')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', self::class, false, false, null), + new ArgumentMetadata('bar', 'array', false, false, null), + new ArgumentMetadata('baz', 'callable', false, false, null), + ), $arguments); + } + + public function testSignature2() + { + $arguments = $this->factory->createArgumentMetadata(array($this, 'signature2')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', self::class, false, true, null, true), + new ArgumentMetadata('bar', __NAMESPACE__.'\FakeClassThatDoesNotExist', false, true, null, true), + new ArgumentMetadata('baz', 'Fake\ImportedAndFake', false, true, null, true), + ), $arguments); + } + + public function testSignature3() + { + $arguments = $this->factory->createArgumentMetadata(array($this, 'signature3')); + + $this->assertEquals(array( + new ArgumentMetadata('bar', __NAMESPACE__.'\FakeClassThatDoesNotExist', false, false, null), + new ArgumentMetadata('baz', 'Fake\ImportedAndFake', false, false, null), + ), $arguments); + } + + public function testSignature4() + { + $arguments = $this->factory->createArgumentMetadata(array($this, 'signature4')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', null, false, true, 'default'), + new ArgumentMetadata('bar', null, false, true, 500), + new ArgumentMetadata('baz', null, false, true, array()), + ), $arguments); + } + + public function testSignature5() + { + $arguments = $this->factory->createArgumentMetadata(array($this, 'signature5')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', 'array', false, true, null, true), + new ArgumentMetadata('bar', null, false, false, null), + ), $arguments); + } + + /** + * @requires PHP 5.6 + */ + public function testVariadicSignature() + { + $arguments = $this->factory->createArgumentMetadata(array(new VariadicController(), 'action')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', null, false, false, null), + new ArgumentMetadata('bar', null, true, false, null), + ), $arguments); + } + + /** + * @requires PHP 7.0 + */ + public function testBasicTypesSignature() + { + $arguments = $this->factory->createArgumentMetadata(array(new BasicTypesController(), 'action')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', 'string', false, false, null), + new ArgumentMetadata('bar', 'int', false, false, null), + new ArgumentMetadata('baz', 'float', false, false, null), + ), $arguments); + } + + /** + * @requires PHP 7.1 + */ + public function testNullableTypesSignature() + { + $arguments = $this->factory->createArgumentMetadata(array(new NullableController(), 'action')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', 'string', false, false, null, true), + new ArgumentMetadata('bar', \stdClass::class, false, false, null, true), + new ArgumentMetadata('baz', 'string', false, true, 'value', true), + new ArgumentMetadata('mandatory', null, false, false, null, true), + ), $arguments); + } + + private function signature1(ArgumentMetadataFactoryTest $foo, array $bar, callable $baz) + { + } + + private function signature2(ArgumentMetadataFactoryTest $foo = null, FakeClassThatDoesNotExist $bar = null, ImportedAndFake $baz = null) + { + } + + private function signature3(FakeClassThatDoesNotExist $bar, ImportedAndFake $baz) + { + } + + private function signature4($foo = 'default', $bar = 500, $baz = array()) + { + } + + private function signature5(array $foo = null, $bar) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataTest.php b/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataTest.php new file mode 100644 index 00000000..05351445 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\ControllerMetadata; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +class ArgumentMetadataTest extends TestCase +{ + public function testWithBcLayerWithDefault() + { + $argument = new ArgumentMetadata('foo', 'string', false, true, 'default value'); + + $this->assertFalse($argument->isNullable()); + } + + public function testDefaultValueAvailable() + { + $argument = new ArgumentMetadata('foo', 'string', false, true, 'default value', true); + + $this->assertTrue($argument->isNullable()); + $this->assertTrue($argument->hasDefaultValue()); + $this->assertSame('default value', $argument->getDefaultValue()); + } + + /** + * @expectedException \LogicException + */ + public function testDefaultValueUnavailable() + { + $argument = new ArgumentMetadata('foo', 'string', false, false, null, false); + + $this->assertFalse($argument->isNullable()); + $this->assertFalse($argument->hasDefaultValue()); + $argument->getDefaultValue(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/Compiler.log b/vendor/symfony/http-kernel/Tests/DataCollector/Compiler.log new file mode 100644 index 00000000..88b6840e --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/Compiler.log @@ -0,0 +1,4 @@ +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Psr\Container\ContainerInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\DependencyInjection\ContainerInterface"; reason: private alias. +Some custom logging message +With ending : diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.php new file mode 100644 index 00000000..4fb36afd --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\ConfigDataCollector; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class ConfigDataCollectorTest extends TestCase +{ + public function testCollect() + { + $kernel = new KernelForTest('test', true); + $c = new ConfigDataCollector(); + $c->setKernel($kernel); + $c->collect(new Request(), new Response()); + + $this->assertSame('test', $c->getEnv()); + $this->assertTrue($c->isDebug()); + $this->assertSame('config', $c->getName()); + $this->assertSame('testkernel', $c->getAppName()); + $this->assertRegExp('~^'.preg_quote($c->getPhpVersion(), '~').'~', PHP_VERSION); + $this->assertRegExp('~'.preg_quote((string) $c->getPhpVersionExtra(), '~').'$~', PHP_VERSION); + $this->assertSame(PHP_INT_SIZE * 8, $c->getPhpArchitecture()); + $this->assertSame(class_exists('Locale', false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a', $c->getPhpIntlLocale()); + $this->assertSame(date_default_timezone_get(), $c->getPhpTimezone()); + $this->assertSame(Kernel::VERSION, $c->getSymfonyVersion()); + $this->assertNull($c->getToken()); + $this->assertSame(extension_loaded('xdebug'), $c->hasXDebug()); + $this->assertSame(extension_loaded('Zend OPcache') && ini_get('opcache.enable'), $c->hasZendOpcache()); + $this->assertSame(extension_loaded('apcu') && ini_get('apc.enabled'), $c->hasApcu()); + } +} + +class KernelForTest extends Kernel +{ + public function getName() + { + return 'testkernel'; + } + + public function registerBundles() + { + } + + public function getBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/DataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/DataCollectorTest.php new file mode 100644 index 00000000..54fd39e0 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/DataCollectorTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Tests\Fixtures\DataCollector\CloneVarDataCollector; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +class DataCollectorTest extends TestCase +{ + public function testCloneVarStringWithScheme() + { + $c = new CloneVarDataCollector('scheme://foo'); + $c->collect(new Request(), new Response()); + $cloner = new VarCloner(); + + $this->assertEquals($cloner->cloneVar('scheme://foo'), $c->getData()); + } + + public function testCloneVarExistingFilePath() + { + $c = new CloneVarDataCollector(array($filePath = tempnam(sys_get_temp_dir(), 'clone_var_data_collector_'))); + $c->collect(new Request(), new Response()); + + $this->assertSame($filePath, $c->getData()[0]); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/DumpDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/DumpDataCollectorTest.php new file mode 100644 index 00000000..e642e3c3 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/DumpDataCollectorTest.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Nicolas Grekas + */ +class DumpDataCollectorTest extends TestCase +{ + public function testDump() + { + $data = new Data(array(array(123))); + + $collector = new DumpDataCollector(); + + $this->assertSame('dump', $collector->getName()); + + $collector->dump($data); + $line = __LINE__ - 1; + $this->assertSame(1, $collector->getDumpsCount()); + + $dump = $collector->getDumps('html'); + $this->assertTrue(isset($dump[0]['data'])); + $dump[0]['data'] = preg_replace('/^.*?
 "
123\n
\n", + 'name' => 'DumpDataCollectorTest.php', + 'file' => __FILE__, + 'line' => $line, + 'fileExcerpt' => false, + ), + ); + $this->assertEquals($xDump, $dump); + + $this->assertStringMatchesFormat('a:3:{i:0;a:5:{s:4:"data";%c:39:"Symfony\Component\VarDumper\Cloner\Data":%a', $collector->serialize()); + $this->assertSame(0, $collector->getDumpsCount()); + $this->assertSame('a:2:{i:0;b:0;i:1;s:5:"UTF-8";}', $collector->serialize()); + } + + public function testCollectDefault() + { + $data = new Data(array(array(123))); + + $collector = new DumpDataCollector(); + + $collector->dump($data); + $line = __LINE__ - 1; + + ob_start(); + $collector->collect(new Request(), new Response()); + $output = ob_get_clean(); + + $this->assertSame("DumpDataCollectorTest.php on line {$line}:\n123\n", $output); + $this->assertSame(1, $collector->getDumpsCount()); + $collector->serialize(); + } + + public function testCollectHtml() + { + $data = new Data(array(array(123))); + + $collector = new DumpDataCollector(null, 'test://%f:%l'); + + $collector->dump($data); + $line = __LINE__ - 1; + $file = __FILE__; + $xOutput = <<DumpDataCollectorTest.php on line {$line}: +123 +
+EOTXT; + + ob_start(); + $response = new Response(); + $response->headers->set('Content-Type', 'text/html'); + $collector->collect(new Request(), $response); + $output = ob_get_clean(); + $output = preg_replace('#<(script|style).*?#s', '', $output); + $output = preg_replace('/sf-dump-\d+/', 'sf-dump', $output); + + $this->assertSame($xOutput, trim($output)); + $this->assertSame(1, $collector->getDumpsCount()); + $collector->serialize(); + } + + public function testFlush() + { + $data = new Data(array(array(456))); + $collector = new DumpDataCollector(); + $collector->dump($data); + $line = __LINE__ - 1; + + ob_start(); + $collector->__destruct(); + $this->assertSame("DumpDataCollectorTest.php on line {$line}:\n456\n", ob_get_clean()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/ExceptionDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/ExceptionDataCollectorTest.php new file mode 100644 index 00000000..afad9f58 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/ExceptionDataCollectorTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class ExceptionDataCollectorTest extends TestCase +{ + public function testCollect() + { + $e = new \Exception('foo', 500); + $c = new ExceptionDataCollector(); + $flattened = FlattenException::create($e); + $trace = $flattened->getTrace(); + + $this->assertFalse($c->hasException()); + + $c->collect(new Request(), new Response(), $e); + + $this->assertTrue($c->hasException()); + $this->assertEquals($flattened, $c->getException()); + $this->assertSame('foo', $c->getMessage()); + $this->assertSame(500, $c->getCode()); + $this->assertSame('exception', $c->getName()); + $this->assertSame($trace, $c->getTrace()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/LoggerDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/LoggerDataCollectorTest.php new file mode 100644 index 00000000..62bf2c00 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/LoggerDataCollectorTest.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\SilencedErrorContext; +use Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector; + +class LoggerDataCollectorTest extends TestCase +{ + public function testCollectWithUnexpectedFormat() + { + $logger = $this->getMockBuilder('Symfony\Component\HttpKernel\Log\DebugLoggerInterface')->getMock(); + $logger->expects($this->once())->method('countErrors')->will($this->returnValue('foo')); + $logger->expects($this->exactly(2))->method('getLogs')->will($this->returnValue(array())); + + $c = new LoggerDataCollector($logger, __DIR__.'/'); + $c->lateCollect(); + $compilerLogs = $c->getCompilerLogs()->getValue('message'); + + $this->assertSame(array( + array('message' => 'Removed service "Psr\Container\ContainerInterface"; reason: private alias.'), + array('message' => 'Removed service "Symfony\Component\DependencyInjection\ContainerInterface"; reason: private alias.'), + ), $compilerLogs['Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass']); + + $this->assertSame(array( + array('message' => 'Some custom logging message'), + array('message' => 'With ending :'), + ), $compilerLogs['Unknown Compiler Pass']); + } + + /** + * @dataProvider getCollectTestData + */ + public function testCollect($nb, $logs, $expectedLogs, $expectedDeprecationCount, $expectedScreamCount, $expectedPriorities = null) + { + $logger = $this->getMockBuilder('Symfony\Component\HttpKernel\Log\DebugLoggerInterface')->getMock(); + $logger->expects($this->once())->method('countErrors')->will($this->returnValue($nb)); + $logger->expects($this->exactly(2))->method('getLogs')->will($this->returnValue($logs)); + + $c = new LoggerDataCollector($logger); + $c->lateCollect(); + + $this->assertEquals('logger', $c->getName()); + $this->assertEquals($nb, $c->countErrors()); + + $logs = array_map(function ($v) { + if (isset($v['context']['exception'])) { + $e = &$v['context']['exception']; + $e = isset($e["\0*\0message"]) ? array($e["\0*\0message"], $e["\0*\0severity"]) : array($e["\0Symfony\Component\Debug\Exception\SilencedErrorContext\0severity"]); + } + + return $v; + }, $c->getLogs()->getValue(true)); + $this->assertEquals($expectedLogs, $logs); + $this->assertEquals($expectedDeprecationCount, $c->countDeprecations()); + $this->assertEquals($expectedScreamCount, $c->countScreams()); + + if (isset($expectedPriorities)) { + $this->assertSame($expectedPriorities, $c->getPriorities()->getValue(true)); + } + } + + public function getCollectTestData() + { + yield 'simple log' => array( + 1, + array(array('message' => 'foo', 'context' => array(), 'priority' => 100, 'priorityName' => 'DEBUG')), + array(array('message' => 'foo', 'context' => array(), 'priority' => 100, 'priorityName' => 'DEBUG')), + 0, + 0, + ); + + yield 'log with a context' => array( + 1, + array(array('message' => 'foo', 'context' => array('foo' => 'bar'), 'priority' => 100, 'priorityName' => 'DEBUG')), + array(array('message' => 'foo', 'context' => array('foo' => 'bar'), 'priority' => 100, 'priorityName' => 'DEBUG')), + 0, + 0, + ); + + if (!class_exists(SilencedErrorContext::class)) { + return; + } + + yield 'logs with some deprecations' => array( + 1, + array( + array('message' => 'foo3', 'context' => array('exception' => new \ErrorException('warning', 0, E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo', 'context' => array('exception' => new \ErrorException('deprecated', 0, E_DEPRECATED)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo2', 'context' => array('exception' => new \ErrorException('deprecated', 0, E_USER_DEPRECATED)), 'priority' => 100, 'priorityName' => 'DEBUG'), + ), + array( + array('message' => 'foo3', 'context' => array('exception' => array('warning', E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo', 'context' => array('exception' => array('deprecated', E_DEPRECATED)), 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => false), + array('message' => 'foo2', 'context' => array('exception' => array('deprecated', E_USER_DEPRECATED)), 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => false), + ), + 2, + 0, + array(100 => array('count' => 3, 'name' => 'DEBUG')), + ); + + yield 'logs with some silent errors' => array( + 1, + array( + array('message' => 'foo3', 'context' => array('exception' => new \ErrorException('warning', 0, E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo3', 'context' => array('exception' => new SilencedErrorContext(E_USER_WARNING, __FILE__, __LINE__)), 'priority' => 100, 'priorityName' => 'DEBUG'), + ), + array( + array('message' => 'foo3', 'context' => array('exception' => array('warning', E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo3', 'context' => array('exception' => array(E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => true), + ), + 0, + 1, + ); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/MemoryDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/MemoryDataCollectorTest.php new file mode 100644 index 00000000..ab78e9e8 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/MemoryDataCollectorTest.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class MemoryDataCollectorTest extends TestCase +{ + public function testCollect() + { + $collector = new MemoryDataCollector(); + $collector->collect(new Request(), new Response()); + + $this->assertInternalType('integer', $collector->getMemory()); + $this->assertInternalType('integer', $collector->getMemoryLimit()); + $this->assertSame('memory', $collector->getName()); + } + + /** @dataProvider getBytesConversionTestData */ + public function testBytesConversion($limit, $bytes) + { + $collector = new MemoryDataCollector(); + $method = new \ReflectionMethod($collector, 'convertToBytes'); + $method->setAccessible(true); + $this->assertEquals($bytes, $method->invoke($collector, $limit)); + } + + public function getBytesConversionTestData() + { + return array( + array('2k', 2048), + array('2 k', 2048), + array('8m', 8 * 1024 * 1024), + array('+2 k', 2048), + array('+2???k', 2048), + array('0x10', 16), + array('0xf', 15), + array('010', 8), + array('+0x10 k', 16 * 1024), + array('1g', 1024 * 1024 * 1024), + array('1G', 1024 * 1024 * 1024), + array('-1', -1), + array('0', 0), + array('2mk', 2048), // the unit must be the last char, so in this case 'k', not 'm' + ); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/RequestDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/RequestDataCollectorTest.php new file mode 100644 index 00000000..93767b96 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/RequestDataCollectorTest.php @@ -0,0 +1,287 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class RequestDataCollectorTest extends TestCase +{ + public function testCollect() + { + $c = new RequestDataCollector(); + + $c->collect($request = $this->createRequest(), $this->createResponse()); + $c->lateCollect(); + + $attributes = $c->getRequestAttributes(); + + $this->assertSame('request', $c->getName()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestHeaders()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestServer()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestCookies()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $attributes); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestRequest()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestQuery()); + $this->assertInstanceOf(ParameterBag::class, $c->getResponseCookies()); + $this->assertSame('html', $c->getFormat()); + $this->assertEquals('foobar', $c->getRoute()); + $this->assertEquals(array('name' => 'foo'), $c->getRouteParams()); + $this->assertSame(array(), $c->getSessionAttributes()); + $this->assertSame('en', $c->getLocale()); + $this->assertContains(__FILE__, $attributes->get('resource')); + $this->assertSame('stdClass', $attributes->get('object')->getType()); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getResponseHeaders()); + $this->assertSame('OK', $c->getStatusText()); + $this->assertSame(200, $c->getStatusCode()); + $this->assertSame('application/json', $c->getContentType()); + } + + public function testCollectWithoutRouteParams() + { + $request = $this->createRequest(array()); + + $c = new RequestDataCollector(); + $c->collect($request, $this->createResponse()); + $c->lateCollect(); + + $this->assertEquals(array(), $c->getRouteParams()); + } + + public function testKernelResponseDoesNotStartSession() + { + $kernel = $this->getMockBuilder(HttpKernelInterface::class)->getMock(); + $request = new Request(); + $session = new Session(new MockArraySessionStorage()); + $request->setSession($session); + $response = new Response(); + + $c = new RequestDataCollector(); + $c->onKernelResponse(new FilterResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response)); + + $this->assertFalse($session->isStarted()); + } + + /** + * @dataProvider provideControllerCallables + */ + public function testControllerInspection($name, $callable, $expected) + { + $c = new RequestDataCollector(); + $request = $this->createRequest(); + $response = $this->createResponse(); + $this->injectController($c, $callable, $request); + $c->collect($request, $response); + $c->lateCollect(); + + $this->assertSame($expected, $c->getController()->getValue(true), sprintf('Testing: %s', $name)); + } + + public function provideControllerCallables() + { + // make sure we always match the line number + $r1 = new \ReflectionMethod($this, 'testControllerInspection'); + $r2 = new \ReflectionMethod($this, 'staticControllerMethod'); + $r3 = new \ReflectionClass($this); + + // test name, callable, expected + return array( + array( + '"Regular" callable', + array($this, 'testControllerInspection'), + array( + 'class' => __NAMESPACE__.'\RequestDataCollectorTest', + 'method' => 'testControllerInspection', + 'file' => __FILE__, + 'line' => $r1->getStartLine(), + ), + ), + + array( + 'Closure', + function () { return 'foo'; }, + array( + 'class' => __NAMESPACE__.'\{closure}', + 'method' => null, + 'file' => __FILE__, + 'line' => __LINE__ - 5, + ), + ), + + array( + 'Static callback as string', + __NAMESPACE__.'\RequestDataCollectorTest::staticControllerMethod', + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'staticControllerMethod', + 'file' => __FILE__, + 'line' => $r2->getStartLine(), + ), + ), + + array( + 'Static callable with instance', + array($this, 'staticControllerMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'staticControllerMethod', + 'file' => __FILE__, + 'line' => $r2->getStartLine(), + ), + ), + + array( + 'Static callable with class name', + array('Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', 'staticControllerMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'staticControllerMethod', + 'file' => __FILE__, + 'line' => $r2->getStartLine(), + ), + ), + + array( + 'Callable with instance depending on __call()', + array($this, 'magicMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'magicMethod', + 'file' => 'n/a', + 'line' => 'n/a', + ), + ), + + array( + 'Callable with class name depending on __callStatic()', + array('Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', 'magicMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'magicMethod', + 'file' => 'n/a', + 'line' => 'n/a', + ), + ), + + array( + 'Invokable controller', + $this, + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => null, + 'file' => __FILE__, + 'line' => $r3->getStartLine(), + ), + ), + ); + } + + public function testItIgnoresInvalidCallables() + { + $request = $this->createRequestWithSession(); + $response = new RedirectResponse('/'); + + $c = new RequestDataCollector(); + $c->collect($request, $response); + + $this->assertSame('n/a', $c->getController()); + } + + protected function createRequest($routeParams = array('name' => 'foo')) + { + $request = Request::create('http://test.com/foo?bar=baz'); + $request->attributes->set('foo', 'bar'); + $request->attributes->set('_route', 'foobar'); + $request->attributes->set('_route_params', $routeParams); + $request->attributes->set('resource', fopen(__FILE__, 'r')); + $request->attributes->set('object', new \stdClass()); + + return $request; + } + + private function createRequestWithSession() + { + $request = $this->createRequest(); + $request->attributes->set('_controller', 'Foo::bar'); + $request->setSession(new Session(new MockArraySessionStorage())); + $request->getSession()->start(); + + return $request; + } + + protected function createResponse() + { + $response = new Response(); + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'application/json'); + $response->headers->set('X-Foo-Bar', null); + $response->headers->setCookie(new Cookie('foo', 'bar', 1, '/foo', 'localhost', true, true)); + $response->headers->setCookie(new Cookie('bar', 'foo', new \DateTime('@946684800'))); + $response->headers->setCookie(new Cookie('bazz', 'foo', '2000-12-12')); + + return $response; + } + + /** + * Inject the given controller callable into the data collector. + */ + protected function injectController($collector, $controller, $request) + { + $resolver = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface')->getMock(); + $httpKernel = new HttpKernel(new EventDispatcher(), $resolver, null, $this->getMockBuilder(ArgumentResolverInterface::class)->getMock()); + $event = new FilterControllerEvent($httpKernel, $controller, $request, HttpKernelInterface::MASTER_REQUEST); + $collector->onKernelController($event); + } + + /** + * Dummy method used as controller callable. + */ + public static function staticControllerMethod() + { + throw new \LogicException('Unexpected method call'); + } + + /** + * Magic method to allow non existing methods to be called and delegated. + */ + public function __call($method, $args) + { + throw new \LogicException('Unexpected method call'); + } + + /** + * Magic method to allow non existing methods to be called and delegated. + */ + public static function __callStatic($method, $args) + { + throw new \LogicException('Unexpected method call'); + } + + public function __invoke() + { + throw new \LogicException('Unexpected method call'); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/TimeDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/TimeDataCollectorTest.php new file mode 100644 index 00000000..8048cc37 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/TimeDataCollectorTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\TimeDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @group time-sensitive + */ +class TimeDataCollectorTest extends TestCase +{ + public function testCollect() + { + $c = new TimeDataCollector(); + + $request = new Request(); + $request->server->set('REQUEST_TIME', 1); + + $c->collect($request, new Response()); + + $this->assertEquals(0, $c->getStartTime()); + + $request->server->set('REQUEST_TIME_FLOAT', 2); + + $c->collect($request, new Response()); + + $this->assertEquals(2000, $c->getStartTime()); + + $request = new Request(); + $c->collect($request, new Response()); + $this->assertEquals(0, $c->getStartTime()); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); + $kernel->expects($this->once())->method('getStartTime')->will($this->returnValue(123456)); + + $c = new TimeDataCollector($kernel); + $request = new Request(); + $request->server->set('REQUEST_TIME', 1); + + $c->collect($request, new Response()); + $this->assertEquals(123456000, $c->getStartTime()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/Util/ValueExporterTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/Util/ValueExporterTest.php new file mode 100644 index 00000000..5fe92d60 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/Util/ValueExporterTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector\Util; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; + +/** + * @group legacy + */ +class ValueExporterTest extends TestCase +{ + /** + * @var ValueExporter + */ + private $valueExporter; + + protected function setUp() + { + $this->valueExporter = new ValueExporter(); + } + + public function testDateTime() + { + $dateTime = new \DateTime('2014-06-10 07:35:40', new \DateTimeZone('UTC')); + $this->assertSame('Object(DateTime) - 2014-06-10T07:35:40+00:00', $this->valueExporter->exportValue($dateTime)); + } + + public function testDateTimeImmutable() + { + $dateTime = new \DateTimeImmutable('2014-06-10 07:35:40', new \DateTimeZone('UTC')); + $this->assertSame('Object(DateTimeImmutable) - 2014-06-10T07:35:40+00:00', $this->valueExporter->exportValue($dateTime)); + } + + public function testIncompleteClass() + { + $foo = new \__PHP_Incomplete_Class(); + $array = new \ArrayObject($foo); + $array['__PHP_Incomplete_Class_Name'] = 'AppBundle/Foo'; + $this->assertSame('__PHP_Incomplete_Class(AppBundle/Foo)', $this->valueExporter->exportValue($foo)); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Debug/FileLinkFormatterTest.php b/vendor/symfony/http-kernel/Tests/Debug/FileLinkFormatterTest.php new file mode 100644 index 00000000..d616098a --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Debug/FileLinkFormatterTest.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Debug; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; + +class FileLinkFormatterTest extends TestCase +{ + public function testWhenNoFileLinkFormatAndNoRequest() + { + $sut = new FileLinkFormatter(); + + $this->assertFalse($sut->format('/kernel/root/src/my/very/best/file.php', 3)); + } + + public function testWhenFileLinkFormatAndNoRequest() + { + $file = __DIR__.DIRECTORY_SEPARATOR.'file.php'; + + $sut = new FileLinkFormatter('debug://open?url=file://%f&line=%l', new RequestStack()); + + $this->assertSame("debug://open?url=file://$file&line=3", $sut->format($file, 3)); + } + + public function testWhenFileLinkFormatAndRequest() + { + $file = __DIR__.DIRECTORY_SEPARATOR.'file.php'; + $baseDir = __DIR__; + $requestStack = new RequestStack(); + $request = new Request(); + $requestStack->push($request); + + $sut = new FileLinkFormatter('debug://open?url=file://%f&line=%l', $requestStack, __DIR__, '/_profiler/open?file=%f&line=%l#line%l'); + + $this->assertSame("debug://open?url=file://$file&line=3", $sut->format($file, 3)); + } + + public function testWhenNoFileLinkFormatAndRequest() + { + $file = __DIR__.DIRECTORY_SEPARATOR.'file.php'; + $requestStack = new RequestStack(); + $request = new Request(); + $requestStack->push($request); + + $request->server->set('SERVER_NAME', 'www.example.org'); + $request->server->set('SERVER_PORT', 80); + $request->server->set('SCRIPT_NAME', '/app.php'); + $request->server->set('SCRIPT_FILENAME', '/web/app.php'); + $request->server->set('REQUEST_URI', '/app.php/example'); + + $sut = new FileLinkFormatter(null, $requestStack, __DIR__, '/_profiler/open?file=%f&line=%l#line%l'); + + $this->assertSame('http://www.example.org/app.php/_profiler/open?file=file.php&line=3#line3', $sut->format($file, 3)); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Debug/TraceableEventDispatcherTest.php b/vendor/symfony/http-kernel/Tests/Debug/TraceableEventDispatcherTest.php new file mode 100644 index 00000000..aaa82d52 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Debug/TraceableEventDispatcherTest.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Debug; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Stopwatch\Stopwatch; + +class TraceableEventDispatcherTest extends TestCase +{ + public function testStopwatchSections() + { + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch = new Stopwatch()); + $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $request = Request::create('/'); + $response = $kernel->handle($request); + $kernel->terminate($request, $response); + + $events = $stopwatch->getSectionEvents($response->headers->get('X-Debug-Token')); + $this->assertEquals(array( + '__section__', + 'kernel.request', + 'kernel.controller', + 'kernel.controller_arguments', + 'controller', + 'kernel.response', + 'kernel.terminate', + ), array_keys($events)); + } + + public function testStopwatchCheckControllerOnRequestEvent() + { + $stopwatch = $this->getMockBuilder('Symfony\Component\Stopwatch\Stopwatch') + ->setMethods(array('isStarted')) + ->getMock(); + $stopwatch->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(false)); + + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch); + + $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $request = Request::create('/'); + $kernel->handle($request); + } + + public function testStopwatchStopControllerOnRequestEvent() + { + $stopwatch = $this->getMockBuilder('Symfony\Component\Stopwatch\Stopwatch') + ->setMethods(array('isStarted', 'stop', 'stopSection')) + ->getMock(); + $stopwatch->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(true)); + $stopwatch->expects($this->once()) + ->method('stop'); + $stopwatch->expects($this->once()) + ->method('stopSection'); + + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch); + + $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $request = Request::create('/'); + $kernel->handle($request); + } + + public function testAddListenerNested() + { + $called1 = false; + $called2 = false; + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $dispatcher->addListener('my-event', function () use ($dispatcher, &$called1, &$called2) { + $called1 = true; + $dispatcher->addListener('my-event', function () use (&$called2) { + $called2 = true; + }); + }); + $dispatcher->dispatch('my-event'); + $this->assertTrue($called1); + $this->assertFalse($called2); + $dispatcher->dispatch('my-event'); + $this->assertTrue($called2); + } + + public function testListenerCanRemoveItselfWhenExecuted() + { + $eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $listener1 = function () use ($eventDispatcher, &$listener1) { + $eventDispatcher->removeListener('foo', $listener1); + }; + $eventDispatcher->addListener('foo', $listener1); + $eventDispatcher->addListener('foo', function () {}); + $eventDispatcher->dispatch('foo'); + + $this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed'); + } + + protected function getHttpKernel($dispatcher, $controller) + { + $controllerResolver = $this->getMockBuilder('Symfony\Component\HttpKernel\Controller\ControllerResolverInterface')->getMock(); + $controllerResolver->expects($this->once())->method('getController')->will($this->returnValue($controller)); + $argumentResolver = $this->getMockBuilder('Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface')->getMock(); + $argumentResolver->expects($this->once())->method('getArguments')->will($this->returnValue(array())); + + return new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.php new file mode 100644 index 00000000..a2fb6afc --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DependencyInjection\AddAnnotatedClassesToCachePass; + +class AddAnnotatedClassesToCachePassTest extends TestCase +{ + public function testExpandClasses() + { + $r = new \ReflectionClass(AddAnnotatedClassesToCachePass::class); + $pass = $r->newInstanceWithoutConstructor(); + $r = new \ReflectionMethod(AddAnnotatedClassesToCachePass::class, 'expandClasses'); + $r->setAccessible(true); + $expand = $r->getClosure($pass); + + $this->assertSame('Foo', $expand(array('Foo'), array())[0]); + $this->assertSame('Foo', $expand(array('\\Foo'), array())[0]); + $this->assertSame('Foo', $expand(array('Foo'), array('\\Foo'))[0]); + $this->assertSame('Foo', $expand(array('Foo'), array('Foo'))[0]); + $this->assertSame('Foo', $expand(array('\\Foo'), array('\\Foo\\Bar'))[0]); + $this->assertSame('Foo', $expand(array('Foo'), array('\\Foo\\Bar'))[0]); + $this->assertSame('Foo', $expand(array('\\Foo'), array('\\Foo\\Bar\\Acme'))[0]); + + $this->assertSame('Foo\\Bar', $expand(array('Foo\\'), array('\\Foo\\Bar'))[0]); + $this->assertSame('Foo\\Bar\\Acme', $expand(array('Foo\\'), array('\\Foo\\Bar\\Acme'))[0]); + $this->assertEmpty($expand(array('Foo\\'), array('\\Foo'))); + + $this->assertSame('Acme\\Foo\\Bar', $expand(array('**\\Foo\\'), array('\\Acme\\Foo\\Bar'))[0]); + $this->assertEmpty($expand(array('**\\Foo\\'), array('\\Foo\\Bar'))); + $this->assertEmpty($expand(array('**\\Foo\\'), array('\\Acme\\Foo'))); + $this->assertEmpty($expand(array('**\\Foo\\'), array('\\Foo'))); + + $this->assertSame('Acme\\Foo', $expand(array('**\\Foo'), array('\\Acme\\Foo'))[0]); + $this->assertEmpty($expand(array('**\\Foo'), array('\\Acme\\Foo\\AcmeBundle'))); + $this->assertEmpty($expand(array('**\\Foo'), array('\\Acme\\FooBar\\AcmeBundle'))); + + $this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\*\\Bar'), array('\\Foo\\Acme\\Bar'))[0]); + $this->assertEmpty($expand(array('Foo\\*\\Bar'), array('\\Foo\\Acme\\Bundle\\Bar'))); + + $this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\**\\Bar'), array('\\Foo\\Acme\\Bar'))[0]); + $this->assertSame('Foo\\Acme\\Bundle\\Bar', $expand(array('Foo\\**\\Bar'), array('\\Foo\\Acme\\Bundle\\Bar'))[0]); + + $this->assertSame('Acme\\Bar', $expand(array('*\\Bar'), array('\\Acme\\Bar'))[0]); + $this->assertEmpty($expand(array('*\\Bar'), array('\\Bar'))); + $this->assertEmpty($expand(array('*\\Bar'), array('\\Foo\\Acme\\Bar'))); + + $this->assertSame('Foo\\Acme\\Bar', $expand(array('**\\Bar'), array('\\Foo\\Acme\\Bar'))[0]); + $this->assertSame('Foo\\Acme\\Bundle\\Bar', $expand(array('**\\Bar'), array('\\Foo\\Acme\\Bundle\\Bar'))[0]); + $this->assertEmpty($expand(array('**\\Bar'), array('\\Bar'))); + + $this->assertSame('Foo\\Bar', $expand(array('Foo\\*'), array('\\Foo\\Bar'))[0]); + $this->assertEmpty($expand(array('Foo\\*'), array('\\Foo\\Acme\\Bar'))); + + $this->assertSame('Foo\\Bar', $expand(array('Foo\\**'), array('\\Foo\\Bar'))[0]); + $this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\**'), array('\\Foo\\Acme\\Bar'))[0]); + + $this->assertSame(array('Foo\\Bar'), $expand(array('Foo\\*'), array('Foo\\Bar', 'Foo\\BarTest'))); + $this->assertSame(array('Foo\\Bar', 'Foo\\BarTest'), $expand(array('Foo\\*', 'Foo\\*Test'), array('Foo\\Bar', 'Foo\\BarTest'))); + + $this->assertSame( + 'Acme\\FooBundle\\Controller\\DefaultController', + $expand(array('**Bundle\\Controller\\'), array('\\Acme\\FooBundle\\Controller\\DefaultController'))[0] + ); + + $this->assertSame( + 'FooBundle\\Controller\\DefaultController', + $expand(array('**Bundle\\Controller\\'), array('\\FooBundle\\Controller\\DefaultController'))[0] + ); + + $this->assertSame( + 'Acme\\FooBundle\\Controller\\Bar\\DefaultController', + $expand(array('**Bundle\\Controller\\'), array('\\Acme\\FooBundle\\Controller\\Bar\\DefaultController'))[0] + ); + + $this->assertSame( + 'Bundle\\Controller\\Bar\\DefaultController', + $expand(array('**Bundle\\Controller\\'), array('\\Bundle\\Controller\\Bar\\DefaultController'))[0] + ); + + $this->assertSame( + 'Acme\\Bundle\\Controller\\Bar\\DefaultController', + $expand(array('**Bundle\\Controller\\'), array('\\Acme\\Bundle\\Controller\\Bar\\DefaultController'))[0] + ); + + $this->assertSame('Foo\\Bar', $expand(array('Foo\\Bar'), array())[0]); + $this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\**'), array('\\Foo\\Acme\\Bar'))[0]); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php new file mode 100644 index 00000000..df8977de --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass; + +class ControllerArgumentValueResolverPassTest extends TestCase +{ + public function testServicesAreOrderedAccordingToPriority() + { + $services = array( + 'n3' => array(array()), + 'n1' => array(array('priority' => 200)), + 'n2' => array(array('priority' => 100)), + ); + + $expected = array( + new Reference('n1'), + new Reference('n2'), + new Reference('n3'), + ); + + $definition = new Definition(ArgumentResolver::class, array(null, array())); + $container = new ContainerBuilder(); + $container->setDefinition('argument_resolver', $definition); + + foreach ($services as $id => list($tag)) { + $container->register($id)->addTag('controller.argument_value_resolver', $tag); + } + + (new ControllerArgumentValueResolverPass())->process($container); + $this->assertEquals($expected, $definition->getArgument(1)->getValues()); + } + + public function testReturningEmptyArrayWhenNoService() + { + $definition = new Definition(ArgumentResolver::class, array(null, array())); + $container = new ContainerBuilder(); + $container->setDefinition('argument_resolver', $definition); + + (new ControllerArgumentValueResolverPass())->process($container); + $this->assertEquals(array(), $definition->getArgument(1)->getValues()); + } + + public function testNoArgumentResolver() + { + $container = new ContainerBuilder(); + + (new ControllerArgumentValueResolverPass())->process($container); + + $this->assertFalse($container->hasDefinition('argument_resolver')); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/FragmentRendererPassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/FragmentRendererPassTest.php new file mode 100644 index 00000000..d28c6eca --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/FragmentRendererPassTest.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\DependencyInjection\FragmentRendererPass; +use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; + +class FragmentRendererPassTest extends TestCase +{ + /** + * Tests that content rendering not implementing FragmentRendererInterface + * trigger an exception. + * + * @expectedException \InvalidArgumentException + */ + public function testContentRendererWithoutInterface() + { + // one service, not implementing any interface + $services = array( + 'my_content_renderer' => array(array('alias' => 'foo')), + ); + + $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); + + $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition'))->getMock(); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.fragment_renderer here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue($services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $pass = new FragmentRendererPass(); + $pass->process($builder); + } + + public function testValidContentRenderer() + { + $services = array( + 'my_content_renderer' => array(array('alias' => 'foo')), + ); + + $renderer = new Definition('', array(null)); + + $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService')); + + $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition', 'getReflectionClass'))->getMock(); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.fragment_renderer here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue($services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->onConsecutiveCalls($renderer, $definition)); + + $builder->expects($this->atLeastOnce()) + ->method('getReflectionClass') + ->with('Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService') + ->will($this->returnValue(new \ReflectionClass('Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService'))); + + $pass = new FragmentRendererPass(); + $pass->process($builder); + + $this->assertInstanceOf(Reference::class, $renderer->getArgument(0)); + } +} + +class RendererService implements FragmentRendererInterface +{ + public function render($uri, Request $request = null, array $options = array()) + { + } + + public function getName() + { + return 'test'; + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php new file mode 100644 index 00000000..0406345d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DependencyInjection\LazyLoadingFragmentHandler; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class LazyLoadingFragmentHandlerTest extends TestCase +{ + /** + * @group legacy + * @expectedDeprecation The Symfony\Component\HttpKernel\DependencyInjection\LazyLoadingFragmentHandler::addRendererService() method is deprecated since version 3.3 and will be removed in 4.0. + */ + public function testRenderWithLegacyMapping() + { + $renderer = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface')->getMock(); + $renderer->expects($this->once())->method('getName')->will($this->returnValue('foo')); + $renderer->expects($this->any())->method('render')->will($this->returnValue(new Response())); + + $requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock(); + $requestStack->expects($this->any())->method('getCurrentRequest')->will($this->returnValue(Request::create('/'))); + + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); + $container->expects($this->once())->method('get')->will($this->returnValue($renderer)); + + $handler = new LazyLoadingFragmentHandler($container, $requestStack, false); + $handler->addRendererService('foo', 'foo'); + + $handler->render('/foo', 'foo'); + + // second call should not lazy-load anymore (see once() above on the get() method) + $handler->render('/foo', 'foo'); + } + + public function testRender() + { + $renderer = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface')->getMock(); + $renderer->expects($this->once())->method('getName')->will($this->returnValue('foo')); + $renderer->expects($this->any())->method('render')->will($this->returnValue(new Response())); + + $requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock(); + $requestStack->expects($this->any())->method('getCurrentRequest')->will($this->returnValue(Request::create('/'))); + + $container = $this->getMockBuilder('Psr\Container\ContainerInterface')->getMock(); + $container->expects($this->once())->method('has')->with('foo')->willReturn(true); + $container->expects($this->once())->method('get')->will($this->returnValue($renderer)); + + $handler = new LazyLoadingFragmentHandler($container, $requestStack, false); + + $handler->render('/foo', 'foo'); + + // second call should not lazy-load anymore (see once() above on the get() method) + $handler->render('/foo', 'foo'); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php new file mode 100644 index 00000000..81fc8b45 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; + +class MergeExtensionConfigurationPassTest extends TestCase +{ + public function testAutoloadMainExtension() + { + $container = $this->getMockBuilder('Symfony\\Component\\DependencyInjection\\ContainerBuilder')->setMethods(array('getExtensionConfig', 'loadFromExtension', 'getParameterBag', 'getDefinitions', 'getAliases', 'getExtensions'))->getMock(); + $params = $this->getMockBuilder('Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag')->getMock(); + + $container->expects($this->at(0)) + ->method('getExtensionConfig') + ->with('loaded') + ->will($this->returnValue(array(array()))); + $container->expects($this->at(1)) + ->method('getExtensionConfig') + ->with('notloaded') + ->will($this->returnValue(array())); + $container->expects($this->once()) + ->method('loadFromExtension') + ->with('notloaded', array()); + + $container->expects($this->any()) + ->method('getParameterBag') + ->will($this->returnValue($params)); + $params->expects($this->any()) + ->method('all') + ->will($this->returnValue(array())); + $container->expects($this->any()) + ->method('getDefinitions') + ->will($this->returnValue(array())); + $container->expects($this->any()) + ->method('getAliases') + ->will($this->returnValue(array())); + $container->expects($this->any()) + ->method('getExtensions') + ->will($this->returnValue(array())); + + $configPass = new MergeExtensionConfigurationPass(array('loaded', 'notloaded')); + $configPass->process($container); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php new file mode 100644 index 00000000..83a07cb4 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php @@ -0,0 +1,344 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerAwareTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass; + +class RegisterControllerArgumentLocatorsPassTest extends TestCase +{ + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Class "Symfony\Component\HttpKernel\Tests\DependencyInjection\NotFound" used for service "foo" cannot be found. + */ + public function testInvalidClass() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', NotFound::class) + ->addTag('controller.service_arguments') + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing "action" attribute on tag "controller.service_arguments" {"argument":"bar"} for service "foo". + */ + public function testNoAction() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('argument' => 'bar')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing "argument" attribute on tag "controller.service_arguments" {"action":"fooAction"} for service "foo". + */ + public function testNoArgument() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'fooAction')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing "id" attribute on tag "controller.service_arguments" {"action":"fooAction","argument":"bar"} for service "foo". + */ + public function testNoService() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'bar')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid "action" attribute on tag "controller.service_arguments" for service "foo": no public "barAction()" method found on class "Symfony\Component\HttpKernel\Tests\DependencyInjection\RegisterTestController". + */ + public function testInvalidMethod() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'barAction', 'argument' => 'bar', 'id' => 'bar_service')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid "controller.service_arguments" tag for service "foo": method "fooAction()" has no "baz" argument on class "Symfony\Component\HttpKernel\Tests\DependencyInjection\RegisterTestController". + */ + public function testInvalidArgument() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'baz', 'id' => 'bar')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + public function testAllActions() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments') + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + + $this->assertEquals(array('foo:fooAction'), array_keys($locator)); + $this->assertInstanceof(ServiceClosureArgument::class, $locator['foo:fooAction']); + + $locator = $container->getDefinition((string) $locator['foo:fooAction']->getValues()[0]); + + $this->assertSame(ServiceLocator::class, $locator->getClass()); + $this->assertFalse($locator->isPublic()); + + $expected = array('bar' => new ServiceClosureArgument(new TypedReference(ControllerDummy::class, ControllerDummy::class, RegisterTestController::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE))); + $this->assertEquals($expected, $locator->getArgument(0)); + } + + public function testExplicitArgument() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'bar', 'id' => 'bar')) + ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'bar', 'id' => 'baz')) // should be ignored, the first wins + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $locator = $container->getDefinition((string) $locator['foo:fooAction']->getValues()[0]); + + $expected = array('bar' => new ServiceClosureArgument(new TypedReference('bar', ControllerDummy::class, RegisterTestController::class))); + $this->assertEquals($expected, $locator->getArgument(0)); + } + + public function testOptionalArgument() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'bar', 'id' => '?bar')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $locator = $container->getDefinition((string) $locator['foo:fooAction']->getValues()[0]); + + $expected = array('bar' => new ServiceClosureArgument(new TypedReference('bar', ControllerDummy::class, RegisterTestController::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE))); + $this->assertEquals($expected, $locator->getArgument(0)); + } + + public function testSkipSetContainer() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', ContainerAwareRegisterTestController::class) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $this->assertSame(array('foo:fooAction'), array_keys($locator)); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Cannot determine controller argument for "Symfony\Component\HttpKernel\Tests\DependencyInjection\NonExistentClassController::fooAction()": the $nonExistent argument is type-hinted with the non-existent class or interface: "Symfony\Component\HttpKernel\Tests\DependencyInjection\NonExistentClass". Did you forget to add a use statement? + */ + public function testExceptionOnNonExistentTypeHint() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', NonExistentClassController::class) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Cannot determine controller argument for "Symfony\Component\HttpKernel\Tests\DependencyInjection\NonExistentClassDifferentNamespaceController::fooAction()": the $nonExistent argument is type-hinted with the non-existent class or interface: "Acme\NonExistentClass". + */ + public function testExceptionOnNonExistentTypeHintDifferentNamespace() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', NonExistentClassDifferentNamespaceController::class) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + public function testNoExceptionOnNonExistentTypeHintOptionalArg() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', NonExistentClassOptionalController::class) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $this->assertSame(array('foo:barAction', 'foo:fooAction'), array_keys($locator)); + } + + public function testArgumentWithNoTypeHintIsOk() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', ArgumentWithoutTypeController::class) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $this->assertEmpty(array_keys($locator)); + } + + public function testControllersAreMadePublic() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', ArgumentWithoutTypeController::class) + ->setPublic(false) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $this->assertTrue($container->getDefinition('foo')->isPublic()); + } +} + +class RegisterTestController +{ + public function __construct(ControllerDummy $bar) + { + } + + public function fooAction(ControllerDummy $bar) + { + } + + protected function barAction(ControllerDummy $bar) + { + } +} + +class ContainerAwareRegisterTestController implements ContainerAwareInterface +{ + use ContainerAwareTrait; + + public function fooAction(ControllerDummy $bar) + { + } +} + +class ControllerDummy +{ +} + +class NonExistentClassController +{ + public function fooAction(NonExistentClass $nonExistent) + { + } +} + +class NonExistentClassDifferentNamespaceController +{ + public function fooAction(\Acme\NonExistentClass $nonExistent) + { + } +} + +class NonExistentClassOptionalController +{ + public function fooAction(NonExistentClass $nonExistent = null) + { + } + + public function barAction(NonExistentClass $nonExistent = null, $bar) + { + } +} + +class ArgumentWithoutTypeController +{ + public function fooAction($someArg) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php new file mode 100644 index 00000000..9cac9681 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Compiler\ResolveInvalidReferencesPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass; +use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass; + +class RemoveEmptyControllerArgumentLocatorsPassTest extends TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('stdClass', 'stdClass'); + $container->register(parent::class, 'stdClass'); + $container->register('c1', RemoveTestController1::class)->addTag('controller.service_arguments'); + $container->register('c2', RemoveTestController2::class)->addTag('controller.service_arguments') + ->addMethodCall('setTestCase', array(new Reference('c1'))); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $controllers = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + + $this->assertCount(2, $container->getDefinition((string) $controllers['c1:fooAction']->getValues()[0])->getArgument(0)); + $this->assertCount(1, $container->getDefinition((string) $controllers['c2:setTestCase']->getValues()[0])->getArgument(0)); + $this->assertCount(1, $container->getDefinition((string) $controllers['c2:fooAction']->getValues()[0])->getArgument(0)); + + (new ResolveInvalidReferencesPass())->process($container); + + $this->assertCount(1, $container->getDefinition((string) $controllers['c2:setTestCase']->getValues()[0])->getArgument(0)); + $this->assertSame(array(), $container->getDefinition((string) $controllers['c2:fooAction']->getValues()[0])->getArgument(0)); + + (new RemoveEmptyControllerArgumentLocatorsPass())->process($container); + + $controllers = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + + $this->assertSame(array('c1:fooAction'), array_keys($controllers)); + $this->assertSame(array('bar'), array_keys($container->getDefinition((string) $controllers['c1:fooAction']->getValues()[0])->getArgument(0))); + + $expectedLog = array( + 'Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass: Removing service-argument resolver for controller "c2:fooAction": no corresponding services exist for the referenced types.', + 'Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass: Removing method "setTestCase" of service "c2" from controller candidates: the method is called at instantiation, thus cannot be an action.', + ); + + $this->assertSame($expectedLog, $container->getCompiler()->getLog()); + } + + public function testSameIdClass() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register(RegisterTestController::class, RegisterTestController::class) + ->addTag('controller.service_arguments') + ; + + (new RegisterControllerArgumentLocatorsPass())->process($container); + (new RemoveEmptyControllerArgumentLocatorsPass())->process($container); + + $expected = array( + RegisterTestController::class.':fooAction', + RegisterTestController::class.'::fooAction', + ); + $this->assertEquals($expected, array_keys($container->getDefinition((string) $resolver->getArgument(0))->getArgument(0))); + } + + public function testInvoke() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('invokable', InvokableRegisterTestController::class) + ->addTag('controller.service_arguments') + ; + + (new RegisterControllerArgumentLocatorsPass())->process($container); + (new RemoveEmptyControllerArgumentLocatorsPass())->process($container); + + $this->assertEquals( + array('invokable:__invoke', 'invokable'), + array_keys($container->getDefinition((string) $resolver->getArgument(0))->getArgument(0)) + ); + } + + public function testInvokeSameIdClass() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register(InvokableRegisterTestController::class, InvokableRegisterTestController::class) + ->addTag('controller.service_arguments') + ; + + (new RegisterControllerArgumentLocatorsPass())->process($container); + (new RemoveEmptyControllerArgumentLocatorsPass())->process($container); + + $expected = array( + InvokableRegisterTestController::class.':__invoke', + InvokableRegisterTestController::class.'::__invoke', + InvokableRegisterTestController::class, + ); + $this->assertEquals($expected, array_keys($container->getDefinition((string) $resolver->getArgument(0))->getArgument(0))); + } +} + +class RemoveTestController1 +{ + public function fooAction(\stdClass $bar, ClassNotInContainer $baz) + { + } +} + +class RemoveTestController2 +{ + public function setTestCase(TestCase $test) + { + } + + public function fooAction(ClassNotInContainer $bar) + { + } +} + +class InvokableRegisterTestController +{ + public function __invoke(\stdClass $bar) + { + } +} + +class ClassNotInContainer +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Event/GetResponseForExceptionEventTest.php b/vendor/symfony/http-kernel/Tests/Event/GetResponseForExceptionEventTest.php new file mode 100644 index 00000000..72425793 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Event/GetResponseForExceptionEventTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Event; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Tests\TestHttpKernel; + +class GetResponseForExceptionEventTest extends TestCase +{ + public function testAllowSuccessfulResponseIsFalseByDefault() + { + $event = new GetResponseForExceptionEvent(new TestHttpKernel(), new Request(), 1, new \Exception()); + + $this->assertFalse($event->isAllowingCustomResponseCode()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/AddRequestFormatsListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/AddRequestFormatsListenerTest.php new file mode 100644 index 00000000..f4878f62 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/AddRequestFormatsListenerTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\EventListener\AddRequestFormatsListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Test AddRequestFormatsListener class. + * + * @author Gildas Quemener + */ +class AddRequestFormatsListenerTest extends TestCase +{ + /** + * @var AddRequestFormatsListener + */ + private $listener; + + protected function setUp() + { + $this->listener = new AddRequestFormatsListener(array('csv' => array('text/csv', 'text/plain'))); + } + + protected function tearDown() + { + $this->listener = null; + } + + public function testIsAnEventSubscriber() + { + $this->assertInstanceOf('Symfony\Component\EventDispatcher\EventSubscriberInterface', $this->listener); + } + + public function testRegisteredEvent() + { + $this->assertEquals( + array(KernelEvents::REQUEST => array('onKernelRequest', 1)), + AddRequestFormatsListener::getSubscribedEvents() + ); + } + + public function testSetAdditionalFormats() + { + $request = $this->getRequestMock(); + $event = $this->getGetResponseEventMock($request); + + $request->expects($this->once()) + ->method('setFormat') + ->with('csv', array('text/csv', 'text/plain')); + + $this->listener->onKernelRequest($event); + } + + protected function getRequestMock() + { + return $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->getMock(); + } + + protected function getGetResponseEventMock(Request $request) + { + $event = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + + $event->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($request)); + + return $event; + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/DebugHandlersListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/DebugHandlersListenerTest.php new file mode 100644 index 00000000..d1349906 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/DebugHandlersListenerTest.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LogLevel; +use Symfony\Component\Console\Event\ConsoleEvent; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Debug\ErrorHandler; +use Symfony\Component\Debug\ExceptionHandler; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\KernelEvent; +use Symfony\Component\HttpKernel\EventListener\DebugHandlersListener; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * DebugHandlersListenerTest. + * + * @author Nicolas Grekas + */ +class DebugHandlersListenerTest extends TestCase +{ + public function testConfigure() + { + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $userHandler = function () {}; + $listener = new DebugHandlersListener($userHandler, $logger); + $xHandler = new ExceptionHandler(); + $eHandler = new ErrorHandler(); + $eHandler->setExceptionHandler(array($xHandler, 'handle')); + + $exception = null; + set_error_handler(array($eHandler, 'handleError')); + set_exception_handler(array($eHandler, 'handleException')); + try { + $listener->configure(); + } catch (\Exception $exception) { + } + restore_exception_handler(); + restore_error_handler(); + + if (null !== $exception) { + throw $exception; + } + + $this->assertSame($userHandler, $xHandler->setHandler('var_dump')); + + $loggers = $eHandler->setLoggers(array()); + + $this->assertArrayHasKey(E_DEPRECATED, $loggers); + $this->assertSame(array($logger, LogLevel::INFO), $loggers[E_DEPRECATED]); + } + + public function testConfigureForHttpKernelWithNoTerminateWithException() + { + $listener = new DebugHandlersListener(null); + $eHandler = new ErrorHandler(); + $event = new KernelEvent( + $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), + Request::create('/'), + HttpKernelInterface::MASTER_REQUEST + ); + + $exception = null; + $h = set_exception_handler(array($eHandler, 'handleException')); + try { + $listener->configure($event); + } catch (\Exception $exception) { + } + restore_exception_handler(); + + if (null !== $exception) { + throw $exception; + } + + $this->assertNull($h); + } + + public function testConsoleEvent() + { + $dispatcher = new EventDispatcher(); + $listener = new DebugHandlersListener(null); + $app = $this->getMockBuilder('Symfony\Component\Console\Application')->getMock(); + $app->expects($this->once())->method('getHelperSet')->will($this->returnValue(new HelperSet())); + $command = new Command(__FUNCTION__); + $command->setApplication($app); + $event = new ConsoleEvent($command, new ArgvInput(), new ConsoleOutput()); + + $dispatcher->addSubscriber($listener); + + $xListeners = array( + KernelEvents::REQUEST => array(array($listener, 'configure')), + ConsoleEvents::COMMAND => array(array($listener, 'configure')), + ); + $this->assertSame($xListeners, $dispatcher->getListeners()); + + $exception = null; + $eHandler = new ErrorHandler(); + set_error_handler(array($eHandler, 'handleError')); + set_exception_handler(array($eHandler, 'handleException')); + try { + $dispatcher->dispatch(ConsoleEvents::COMMAND, $event); + } catch (\Exception $exception) { + } + restore_exception_handler(); + restore_error_handler(); + + if (null !== $exception) { + throw $exception; + } + + $xHandler = $eHandler->setExceptionHandler('var_dump'); + $this->assertInstanceOf('Closure', $xHandler); + + $app->expects($this->once()) + ->method('renderException'); + + $xHandler(new \Exception()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/DumpListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/DumpListenerTest.php new file mode 100644 index 00000000..509f4430 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/DumpListenerTest.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\HttpKernel\EventListener\DumpListener; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\DataDumperInterface; +use Symfony\Component\VarDumper\VarDumper; + +/** + * DumpListenerTest. + * + * @author Nicolas Grekas + */ +class DumpListenerTest extends TestCase +{ + public function testSubscribedEvents() + { + $this->assertSame( + array(ConsoleEvents::COMMAND => array('configure', 1024)), + DumpListener::getSubscribedEvents() + ); + } + + public function testConfigure() + { + $prevDumper = VarDumper::setHandler('var_dump'); + VarDumper::setHandler($prevDumper); + + $cloner = new MockCloner(); + $dumper = new MockDumper(); + + ob_start(); + $exception = null; + $listener = new DumpListener($cloner, $dumper); + + try { + $listener->configure(); + + VarDumper::dump('foo'); + VarDumper::dump('bar'); + + $this->assertSame('+foo-+bar-', ob_get_clean()); + } catch (\Exception $exception) { + } + + VarDumper::setHandler($prevDumper); + + if (null !== $exception) { + throw $exception; + } + } +} + +class MockCloner implements ClonerInterface +{ + public function cloneVar($var) + { + return new Data(array(array($var.'-'))); + } +} + +class MockDumper implements DataDumperInterface +{ + public function dump(Data $data) + { + echo '+'.$data->getValue(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/ExceptionListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/ExceptionListenerTest.php new file mode 100644 index 00000000..f5501a3f --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ExceptionListenerTest.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\EventListener\ExceptionListener; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Tests\Logger; + +/** + * ExceptionListenerTest. + * + * @author Robert Schönthal + * + * @group time-sensitive + */ +class ExceptionListenerTest extends TestCase +{ + public function testConstruct() + { + $logger = new TestLogger(); + $l = new ExceptionListener('foo', $logger); + + $_logger = new \ReflectionProperty(get_class($l), 'logger'); + $_logger->setAccessible(true); + $_controller = new \ReflectionProperty(get_class($l), 'controller'); + $_controller->setAccessible(true); + + $this->assertSame($logger, $_logger->getValue($l)); + $this->assertSame('foo', $_controller->getValue($l)); + } + + /** + * @dataProvider provider + */ + public function testHandleWithoutLogger($event, $event2) + { + $this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); + + $l = new ExceptionListener('foo'); + $l->onKernelException($event); + + $this->assertEquals(new Response('foo'), $event->getResponse()); + + try { + $l->onKernelException($event2); + $this->fail('RuntimeException expected'); + } catch (\RuntimeException $e) { + $this->assertSame('bar', $e->getMessage()); + $this->assertSame('foo', $e->getPrevious()->getMessage()); + } + } + + /** + * @dataProvider provider + */ + public function testHandleWithLogger($event, $event2) + { + $logger = new TestLogger(); + + $l = new ExceptionListener('foo', $logger); + $l->onKernelException($event); + + $this->assertEquals(new Response('foo'), $event->getResponse()); + + try { + $l->onKernelException($event2); + $this->fail('RuntimeException expected'); + } catch (\RuntimeException $e) { + $this->assertSame('bar', $e->getMessage()); + $this->assertSame('foo', $e->getPrevious()->getMessage()); + } + + $this->assertEquals(3, $logger->countErrors()); + $this->assertCount(3, $logger->getLogs('critical')); + } + + public function provider() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + return array(array(null, null)); + } + + $request = new Request(); + $exception = new \Exception('foo'); + $event = new GetResponseForExceptionEvent(new TestKernel(), $request, 'foo', $exception); + $event2 = new GetResponseForExceptionEvent(new TestKernelThatThrowsException(), $request, 'foo', $exception); + + return array( + array($event, $event2), + ); + } + + public function testSubRequestFormat() + { + $listener = new ExceptionListener('foo', $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock()); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $kernel->expects($this->once())->method('handle')->will($this->returnCallback(function (Request $request) { + return new Response($request->getRequestFormat()); + })); + + $request = Request::create('/'); + $request->setRequestFormat('xml'); + + $event = new GetResponseForExceptionEvent($kernel, $request, 'foo', new \Exception('foo')); + $listener->onKernelException($event); + + $response = $event->getResponse(); + $this->assertEquals('xml', $response->getContent()); + } +} + +class TestLogger extends Logger implements DebugLoggerInterface +{ + public function countErrors() + { + return count($this->logs['critical']); + } +} + +class TestKernel implements HttpKernelInterface +{ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + return new Response('foo'); + } +} + +class TestKernelThatThrowsException implements HttpKernelInterface +{ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + throw new \RuntimeException('bar'); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/FragmentListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/FragmentListenerTest.php new file mode 100644 index 00000000..464b2ab4 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/FragmentListenerTest.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\EventListener\FragmentListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\UriSigner; + +class FragmentListenerTest extends TestCase +{ + public function testOnlyTriggeredOnFragmentRoute() + { + $request = Request::create('http://example.com/foo?_path=foo%3Dbar%26_controller%3Dfoo'); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $expected = $request->attributes->all(); + + $listener->onKernelRequest($event); + + $this->assertEquals($expected, $request->attributes->all()); + $this->assertTrue($request->query->has('_path')); + } + + public function testOnlyTriggeredIfControllerWasNotDefinedYet() + { + $request = Request::create('http://example.com/_fragment?_path=foo%3Dbar%26_controller%3Dfoo'); + $request->attributes->set('_controller', 'bar'); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request, HttpKernelInterface::SUB_REQUEST); + + $expected = $request->attributes->all(); + + $listener->onKernelRequest($event); + + $this->assertEquals($expected, $request->attributes->all()); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testAccessDeniedWithNonSafeMethods() + { + $request = Request::create('http://example.com/_fragment', 'POST'); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testAccessDeniedWithWrongSignature() + { + $request = Request::create('http://example.com/_fragment', 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + } + + public function testWithSignature() + { + $signer = new UriSigner('foo'); + $request = Request::create($signer->sign('http://example.com/_fragment?_path=foo%3Dbar%26_controller%3Dfoo'), 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new FragmentListener($signer); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + + $this->assertEquals(array('foo' => 'bar', '_controller' => 'foo'), $request->attributes->get('_route_params')); + $this->assertFalse($request->query->has('_path')); + } + + public function testRemovesPathWithControllerDefined() + { + $request = Request::create('http://example.com/_fragment?_path=foo%3Dbar%26_controller%3Dfoo'); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request, HttpKernelInterface::SUB_REQUEST); + + $listener->onKernelRequest($event); + + $this->assertFalse($request->query->has('_path')); + } + + public function testRemovesPathWithControllerNotDefined() + { + $signer = new UriSigner('foo'); + $request = Request::create($signer->sign('http://example.com/_fragment?_path=foo%3Dbar'), 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new FragmentListener($signer); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + + $this->assertFalse($request->query->has('_path')); + } + + private function createGetResponseEvent(Request $request, $requestType = HttpKernelInterface::MASTER_REQUEST) + { + return new GetResponseEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $request, $requestType); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.php new file mode 100644 index 00000000..2ce32819 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\EventListener\LocaleListener; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; + +class LocaleListenerTest extends TestCase +{ + private $requestStack; + + protected function setUp() + { + $this->requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->disableOriginalConstructor()->getMock(); + } + + public function testDefaultLocaleWithoutSession() + { + $listener = new LocaleListener($this->requestStack, 'fr'); + $event = $this->getEvent($request = Request::create('/')); + + $listener->onKernelRequest($event); + $this->assertEquals('fr', $request->getLocale()); + } + + public function testLocaleFromRequestAttribute() + { + $request = Request::create('/'); + session_name('foo'); + $request->cookies->set('foo', 'value'); + + $request->attributes->set('_locale', 'es'); + $listener = new LocaleListener($this->requestStack, 'fr'); + $event = $this->getEvent($request); + + $listener->onKernelRequest($event); + $this->assertEquals('es', $request->getLocale()); + } + + public function testLocaleSetForRoutingContext() + { + // the request context is updated + $context = $this->getMockBuilder('Symfony\Component\Routing\RequestContext')->getMock(); + $context->expects($this->once())->method('setParameter')->with('_locale', 'es'); + + $router = $this->getMockBuilder('Symfony\Component\Routing\Router')->setMethods(array('getContext'))->disableOriginalConstructor()->getMock(); + $router->expects($this->once())->method('getContext')->will($this->returnValue($context)); + + $request = Request::create('/'); + + $request->attributes->set('_locale', 'es'); + $listener = new LocaleListener($this->requestStack, 'fr', $router); + $listener->onKernelRequest($this->getEvent($request)); + } + + public function testRouterResetWithParentRequestOnKernelFinishRequest() + { + // the request context is updated + $context = $this->getMockBuilder('Symfony\Component\Routing\RequestContext')->getMock(); + $context->expects($this->once())->method('setParameter')->with('_locale', 'es'); + + $router = $this->getMockBuilder('Symfony\Component\Routing\Router')->setMethods(array('getContext'))->disableOriginalConstructor()->getMock(); + $router->expects($this->once())->method('getContext')->will($this->returnValue($context)); + + $parentRequest = Request::create('/'); + $parentRequest->setLocale('es'); + + $this->requestStack->expects($this->once())->method('getParentRequest')->will($this->returnValue($parentRequest)); + + $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\FinishRequestEvent')->disableOriginalConstructor()->getMock(); + + $listener = new LocaleListener($this->requestStack, 'fr', $router); + $listener->onKernelFinishRequest($event); + } + + public function testRequestLocaleIsNotOverridden() + { + $request = Request::create('/'); + $request->setLocale('de'); + $listener = new LocaleListener($this->requestStack, 'fr'); + $event = $this->getEvent($request); + + $listener->onKernelRequest($event); + $this->assertEquals('de', $request->getLocale()); + } + + private function getEvent(Request $request) + { + return new GetResponseEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $request, HttpKernelInterface::MASTER_REQUEST); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php new file mode 100644 index 00000000..751aee86 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\EventListener\ProfilerListener; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\Kernel; + +class ProfilerListenerTest extends TestCase +{ + /** + * Test a master and sub request with an exception and `onlyException` profiler option enabled. + */ + public function testKernelTerminate() + { + $profile = $this->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profile') + ->disableOriginalConstructor() + ->getMock(); + + $profiler = $this->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler') + ->disableOriginalConstructor() + ->getMock(); + + $profiler->expects($this->once()) + ->method('collect') + ->will($this->returnValue($profile)); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + + $masterRequest = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $subRequest = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $response = $this->getMockBuilder('Symfony\Component\HttpFoundation\Response') + ->disableOriginalConstructor() + ->getMock(); + + $requestStack = new RequestStack(); + $requestStack->push($masterRequest); + + $onlyException = true; + $listener = new ProfilerListener($profiler, $requestStack, null, $onlyException); + + // master request + $listener->onKernelResponse(new FilterResponseEvent($kernel, $masterRequest, Kernel::MASTER_REQUEST, $response)); + + // sub request + $listener->onKernelException(new GetResponseForExceptionEvent($kernel, $subRequest, Kernel::SUB_REQUEST, new HttpException(404))); + $listener->onKernelResponse(new FilterResponseEvent($kernel, $subRequest, Kernel::SUB_REQUEST, $response)); + + $listener->onKernelTerminate(new PostResponseEvent($kernel, $masterRequest, $response)); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php new file mode 100644 index 00000000..12a31eb3 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\EventListener\ResponseListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class ResponseListenerTest extends TestCase +{ + private $dispatcher; + + private $kernel; + + protected function setUp() + { + $this->dispatcher = new EventDispatcher(); + $listener = new ResponseListener('UTF-8'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + + $this->kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + } + + protected function tearDown() + { + $this->dispatcher = null; + $this->kernel = null; + } + + public function testFilterDoesNothingForSubRequests() + { + $response = new Response('foo'); + + $event = new FilterResponseEvent($this->kernel, new Request(), HttpKernelInterface::SUB_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('', $event->getResponse()->headers->get('content-type')); + } + + public function testFilterSetsNonDefaultCharsetIfNotOverridden() + { + $listener = new ResponseListener('ISO-8859-15'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse'), 1); + + $response = new Response('foo'); + + $event = new FilterResponseEvent($this->kernel, Request::create('/'), HttpKernelInterface::MASTER_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('ISO-8859-15', $response->getCharset()); + } + + public function testFilterDoesNothingIfCharsetIsOverridden() + { + $listener = new ResponseListener('ISO-8859-15'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse'), 1); + + $response = new Response('foo'); + $response->setCharset('ISO-8859-1'); + + $event = new FilterResponseEvent($this->kernel, Request::create('/'), HttpKernelInterface::MASTER_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('ISO-8859-1', $response->getCharset()); + } + + public function testFiltersSetsNonDefaultCharsetIfNotOverriddenOnNonTextContentType() + { + $listener = new ResponseListener('ISO-8859-15'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse'), 1); + + $response = new Response('foo'); + $request = Request::create('/'); + $request->setRequestFormat('application/json'); + + $event = new FilterResponseEvent($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('ISO-8859-15', $response->getCharset()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php new file mode 100644 index 00000000..a40e5799 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ControllerResolver; +use Symfony\Component\HttpKernel\EventListener\ExceptionListener; +use Symfony\Component\HttpKernel\EventListener\RouterListener; +use Symfony\Component\HttpKernel\EventListener\ValidateRequestListener; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\Routing\RequestContext; + +class RouterListenerTest extends TestCase +{ + private $requestStack; + + protected function setUp() + { + $this->requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->disableOriginalConstructor()->getMock(); + } + + /** + * @dataProvider getPortData + */ + public function testPort($defaultHttpPort, $defaultHttpsPort, $uri, $expectedHttpPort, $expectedHttpsPort) + { + $urlMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\UrlMatcherInterface') + ->disableOriginalConstructor() + ->getMock(); + $context = new RequestContext(); + $context->setHttpPort($defaultHttpPort); + $context->setHttpsPort($defaultHttpsPort); + $urlMatcher->expects($this->any()) + ->method('getContext') + ->will($this->returnValue($context)); + + $listener = new RouterListener($urlMatcher, $this->requestStack); + $event = $this->createGetResponseEventForUri($uri); + $listener->onKernelRequest($event); + + $this->assertEquals($expectedHttpPort, $context->getHttpPort()); + $this->assertEquals($expectedHttpsPort, $context->getHttpsPort()); + $this->assertEquals(0 === strpos($uri, 'https') ? 'https' : 'http', $context->getScheme()); + } + + public function getPortData() + { + return array( + array(80, 443, 'http://localhost/', 80, 443), + array(80, 443, 'http://localhost:90/', 90, 443), + array(80, 443, 'https://localhost/', 80, 443), + array(80, 443, 'https://localhost:90/', 80, 90), + ); + } + + /** + * @param string $uri + * + * @return GetResponseEvent + */ + private function createGetResponseEventForUri($uri) + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create($uri); + $request->attributes->set('_controller', null); // Prevents going in to routing process + + return new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidMatcher() + { + new RouterListener(new \stdClass(), $this->requestStack); + } + + public function testRequestMatcher() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create('http://localhost/'); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $requestMatcher->expects($this->once()) + ->method('matchRequest') + ->with($this->isInstanceOf('Symfony\Component\HttpFoundation\Request')) + ->will($this->returnValue(array())); + + $listener = new RouterListener($requestMatcher, $this->requestStack, new RequestContext()); + $listener->onKernelRequest($event); + } + + public function testSubRequestWithDifferentMethod() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create('http://localhost/', 'post'); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $requestMatcher->expects($this->any()) + ->method('matchRequest') + ->with($this->isInstanceOf('Symfony\Component\HttpFoundation\Request')) + ->will($this->returnValue(array())); + + $context = new RequestContext(); + + $listener = new RouterListener($requestMatcher, $this->requestStack, new RequestContext()); + $listener->onKernelRequest($event); + + // sub-request with another HTTP method + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create('http://localhost/', 'get'); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::SUB_REQUEST); + + $listener->onKernelRequest($event); + + $this->assertEquals('GET', $context->getMethod()); + } + + /** + * @dataProvider getLoggingParameterData + */ + public function testLoggingParameter($parameter, $log, $parameters) + { + $requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $requestMatcher->expects($this->once()) + ->method('matchRequest') + ->will($this->returnValue($parameter)); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $logger->expects($this->once()) + ->method('info') + ->with($this->equalTo($log), $this->equalTo($parameters)); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create('http://localhost/'); + + $listener = new RouterListener($requestMatcher, $this->requestStack, new RequestContext(), $logger); + $listener->onKernelRequest(new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST)); + } + + public function getLoggingParameterData() + { + return array( + array(array('_route' => 'foo'), 'Matched route "{route}".', array('route' => 'foo', 'route_parameters' => array('_route' => 'foo'), 'request_uri' => 'http://localhost/', 'method' => 'GET')), + array(array(), 'Matched route "{route}".', array('route' => 'n/a', 'route_parameters' => array(), 'request_uri' => 'http://localhost/', 'method' => 'GET')), + ); + } + + public function testWithBadRequest() + { + $requestStack = new RequestStack(); + + $requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $requestMatcher->expects($this->never())->method('matchRequest'); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new ValidateRequestListener()); + $dispatcher->addSubscriber(new RouterListener($requestMatcher, $requestStack, new RequestContext())); + $dispatcher->addSubscriber(new ExceptionListener(function () { + return new Response('Exception handled', 400); + })); + + $kernel = new HttpKernel($dispatcher, new ControllerResolver(), $requestStack, new ArgumentResolver()); + + $request = Request::create('http://localhost/'); + $request->headers->set('host', '###'); + $response = $kernel->handle($request); + $this->assertSame(400, $response->getStatusCode()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.php new file mode 100644 index 00000000..1e79ebe0 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpKernel\EventListener\SurrogateListener; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class SurrogateListenerTest extends TestCase +{ + public function testFilterDoesNothingForSubRequests() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $response = new Response('foo '); + $listener = new SurrogateListener(new Esi()); + + $dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + $event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::SUB_REQUEST, $response); + $dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('', $event->getResponse()->headers->get('Surrogate-Control')); + } + + public function testFilterWhenThereIsSomeEsiIncludes() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $response = new Response('foo '); + $listener = new SurrogateListener(new Esi()); + + $dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + $event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, $response); + $dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('content="ESI/1.0"', $event->getResponse()->headers->get('Surrogate-Control')); + } + + public function testFilterWhenThereIsNoEsiIncludes() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $response = new Response('foo'); + $listener = new SurrogateListener(new Esi()); + + $dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + $event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, $response); + $dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('', $event->getResponse()->headers->get('Surrogate-Control')); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php new file mode 100644 index 00000000..8ada8e7d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\EventListener\SessionListener; +use Symfony\Component\HttpKernel\EventListener\TestSessionListener; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * SessionListenerTest. + * + * Tests SessionListener. + * + * @author Bulat Shakirzyanov + */ +class TestSessionListenerTest extends TestCase +{ + /** + * @var TestSessionListener + */ + private $listener; + + /** + * @var SessionInterface + */ + private $session; + + protected function setUp() + { + $this->listener = $this->getMockForAbstractClass('Symfony\Component\HttpKernel\EventListener\AbstractTestSessionListener'); + $this->session = $this->getSession(); + } + + public function testShouldSaveMasterRequestSession() + { + $this->sessionHasBeenStarted(); + $this->sessionMustBeSaved(); + + $this->filterResponse(new Request()); + } + + public function testShouldNotSaveSubRequestSession() + { + $this->sessionMustNotBeSaved(); + + $this->filterResponse(new Request(), HttpKernelInterface::SUB_REQUEST); + } + + public function testDoesNotDeleteCookieIfUsingSessionLifetime() + { + $this->sessionHasBeenStarted(); + + $params = session_get_cookie_params(); + session_set_cookie_params(0, $params['path'], $params['domain'], $params['secure'], $params['httponly']); + + $response = $this->filterResponse(new Request(), HttpKernelInterface::MASTER_REQUEST); + $cookies = $response->headers->getCookies(); + + $this->assertEquals(0, reset($cookies)->getExpiresTime()); + } + + public function testUnstartedSessionIsNotSave() + { + $this->sessionHasNotBeenStarted(); + $this->sessionMustNotBeSaved(); + + $this->filterResponse(new Request()); + } + + public function testDoesNotImplementServiceSubscriberInterface() + { + $this->assertTrue(interface_exists(ServiceSubscriberInterface::class)); + $this->assertTrue(class_exists(SessionListener::class)); + $this->assertTrue(class_exists(TestSessionListener::class)); + $this->assertFalse(is_subclass_of(SessionListener::class, ServiceSubscriberInterface::class), 'Implementing ServiceSubscriberInterface would create a dep on the DI component, which eg Silex cannot afford'); + $this->assertFalse(is_subclass_of(TestSessionListener::class, ServiceSubscriberInterface::class, 'Implementing ServiceSubscriberInterface would create a dep on the DI component, which eg Silex cannot afford')); + } + + private function filterResponse(Request $request, $type = HttpKernelInterface::MASTER_REQUEST) + { + $request->setSession($this->session); + $response = new Response(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $event = new FilterResponseEvent($kernel, $request, $type, $response); + + $this->listener->onKernelResponse($event); + + $this->assertSame($response, $event->getResponse()); + + return $response; + } + + private function sessionMustNotBeSaved() + { + $this->session->expects($this->never()) + ->method('save'); + } + + private function sessionMustBeSaved() + { + $this->session->expects($this->once()) + ->method('save'); + } + + private function sessionHasBeenStarted() + { + $this->session->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(true)); + } + + private function sessionHasNotBeenStarted() + { + $this->session->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(false)); + } + + private function getSession() + { + $mock = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\Session') + ->disableOriginalConstructor() + ->getMock(); + + // set return value for getName() + $mock->expects($this->any())->method('getName')->will($this->returnValue('MOCKSESSID')); + + return $mock; + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php new file mode 100644 index 00000000..23b83317 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\EventListener\TranslatorListener; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +class TranslatorListenerTest extends TestCase +{ + private $listener; + private $translator; + private $requestStack; + + protected function setUp() + { + $this->translator = $this->getMockBuilder('Symfony\Component\Translation\TranslatorInterface')->getMock(); + $this->requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock(); + $this->listener = new TranslatorListener($this->translator, $this->requestStack); + } + + public function testLocaleIsSetInOnKernelRequest() + { + $this->translator + ->expects($this->once()) + ->method('setLocale') + ->with($this->equalTo('fr')); + + $event = new GetResponseEvent($this->createHttpKernel(), $this->createRequest('fr'), HttpKernelInterface::MASTER_REQUEST); + $this->listener->onKernelRequest($event); + } + + public function testDefaultLocaleIsUsedOnExceptionsInOnKernelRequest() + { + $this->translator + ->expects($this->at(0)) + ->method('setLocale') + ->will($this->throwException(new \InvalidArgumentException())); + $this->translator + ->expects($this->at(1)) + ->method('setLocale') + ->with($this->equalTo('en')); + + $event = new GetResponseEvent($this->createHttpKernel(), $this->createRequest('fr'), HttpKernelInterface::MASTER_REQUEST); + $this->listener->onKernelRequest($event); + } + + public function testLocaleIsSetInOnKernelFinishRequestWhenParentRequestExists() + { + $this->translator + ->expects($this->once()) + ->method('setLocale') + ->with($this->equalTo('fr')); + + $this->setMasterRequest($this->createRequest('fr')); + $event = new FinishRequestEvent($this->createHttpKernel(), $this->createRequest('de'), HttpKernelInterface::SUB_REQUEST); + $this->listener->onKernelFinishRequest($event); + } + + public function testLocaleIsNotSetInOnKernelFinishRequestWhenParentRequestDoesNotExist() + { + $this->translator + ->expects($this->never()) + ->method('setLocale'); + + $event = new FinishRequestEvent($this->createHttpKernel(), $this->createRequest('de'), HttpKernelInterface::SUB_REQUEST); + $this->listener->onKernelFinishRequest($event); + } + + public function testDefaultLocaleIsUsedOnExceptionsInOnKernelFinishRequest() + { + $this->translator + ->expects($this->at(0)) + ->method('setLocale') + ->will($this->throwException(new \InvalidArgumentException())); + $this->translator + ->expects($this->at(1)) + ->method('setLocale') + ->with($this->equalTo('en')); + + $this->setMasterRequest($this->createRequest('fr')); + $event = new FinishRequestEvent($this->createHttpKernel(), $this->createRequest('de'), HttpKernelInterface::SUB_REQUEST); + $this->listener->onKernelFinishRequest($event); + } + + private function createHttpKernel() + { + return $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + } + + private function createRequest($locale) + { + $request = new Request(); + $request->setLocale($locale); + + return $request; + } + + private function setMasterRequest($request) + { + $this->requestStack + ->expects($this->any()) + ->method('getParentRequest') + ->will($this->returnValue($request)); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/ValidateRequestListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/ValidateRequestListenerTest.php new file mode 100644 index 00000000..d0609432 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ValidateRequestListenerTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\EventListener\ValidateRequestListener; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +class ValidateRequestListenerTest extends TestCase +{ + /** + * @expectedException \Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException + */ + public function testListenerThrowsWhenMasterRequestHasInconsistentClientIps() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + + $request = new Request(); + $request->setTrustedProxies(array('1.1.1.1'), Request::HEADER_X_FORWARDED_FOR | Request::HEADER_FORWARDED); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $request->headers->set('FORWARDED', 'for=2.2.2.2'); + $request->headers->set('X_FORWARDED_FOR', '3.3.3.3'); + + $dispatcher->addListener(KernelEvents::REQUEST, array(new ValidateRequestListener(), 'onKernelRequest')); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $dispatcher->dispatch(KernelEvents::REQUEST, $event); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/AccessDeniedHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/AccessDeniedHttpExceptionTest.php new file mode 100644 index 00000000..2bfcb2bf --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/AccessDeniedHttpExceptionTest.php @@ -0,0 +1,13 @@ + 'Test')), + array(array('X-Test' => 1)), + array( + array( + array('X-Test' => 'Test'), + array('X-Test-2' => 'Test-2'), + ), + ), + ); + } + + public function testHeadersDefault() + { + $exception = $this->createException(); + $this->assertSame(array(), $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersConstructor($headers) + { + $exception = new HttpException(200, null, null, $headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = $this->createException(); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + protected function createException() + { + return new HttpException(200); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/LengthRequiredHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/LengthRequiredHttpExceptionTest.php new file mode 100644 index 00000000..462d3ca4 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/LengthRequiredHttpExceptionTest.php @@ -0,0 +1,13 @@ +assertSame(array('Allow' => 'GET, PUT'), $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = new MethodNotAllowedHttpException(array('GET')); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/NotAcceptableHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/NotAcceptableHttpExceptionTest.php new file mode 100644 index 00000000..4c0db7a3 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/NotAcceptableHttpExceptionTest.php @@ -0,0 +1,13 @@ +assertSame(array('Retry-After' => 10), $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = new ServiceUnavailableHttpException(10); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + protected function createException() + { + return new ServiceUnavailableHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php new file mode 100644 index 00000000..2079bb33 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php @@ -0,0 +1,29 @@ +assertSame(array('Retry-After' => 10), $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = new TooManyRequestsHttpException(10); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + protected function createException() + { + return new TooManyRequestsHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/UnauthorizedHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/UnauthorizedHttpExceptionTest.php new file mode 100644 index 00000000..37a0028d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/UnauthorizedHttpExceptionTest.php @@ -0,0 +1,24 @@ +assertSame(array('WWW-Authenticate' => 'Challenge'), $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = new UnauthorizedHttpException('Challenge'); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php new file mode 100644 index 00000000..760366c6 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php @@ -0,0 +1,28 @@ +setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + protected function createException() + { + return new UnprocessableEntityHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php new file mode 100644 index 00000000..d47287a1 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php @@ -0,0 +1,23 @@ +setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + protected function createException($headers = array()) + { + return new UnsupportedMediaTypeHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/123/Kernel123.php b/vendor/symfony/http-kernel/Tests/Fixtures/123/Kernel123.php new file mode 100644 index 00000000..b6cf1cba --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/123/Kernel123.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\_123; + +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; + +class Kernel123 extends Kernel +{ + public function registerBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } + + public function getCacheDir() + { + return sys_get_temp_dir().'/'.Kernel::VERSION.'/kernel123/cache/'.$this->environment; + } + + public function getLogDir() + { + return sys_get_temp_dir().'/'.Kernel::VERSION.'/kernel123/logs'; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/hide.txt b/vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/hide.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/Resources/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/Resources/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/bar.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/bar.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Bundle2Bundle/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle2Bundle/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/hide.txt b/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/hide.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Controller/BasicTypesController.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/BasicTypesController.php new file mode 100644 index 00000000..e8e0b603 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/BasicTypesController.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\Controller; + +class BasicTypesController +{ + public function action(string $foo, int $bar, float $baz) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingRequest.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingRequest.php new file mode 100644 index 00000000..9b4754b4 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingRequest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\Controller; + +use Symfony\Component\HttpFoundation\Request; + +class ExtendingRequest extends Request +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingSession.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingSession.php new file mode 100644 index 00000000..9fa54ee8 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingSession.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\Controller; + +use Symfony\Component\HttpFoundation\Session\Session; + +class ExtendingSession extends Session +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Controller/NullableController.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/NullableController.php new file mode 100644 index 00000000..9db4df7b --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/NullableController.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\Controller; + +class NullableController +{ + public function action(?string $foo, ?\stdClass $bar, ?string $baz = 'value', $mandatory) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Controller/VariadicController.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/VariadicController.php new file mode 100644 index 00000000..c3981245 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/VariadicController.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\Controller; + +class VariadicController +{ + public function action($foo, ...$bar) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php b/vendor/symfony/http-kernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php new file mode 100644 index 00000000..867ccdce --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; + +class CloneVarDataCollector extends DataCollector +{ + private $varToClone; + + public function __construct($varToClone) + { + $this->varToClone = $varToClone; + } + + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->data = $this->cloneVar($this->varToClone); + } + + public function getData() + { + return $this->data; + } + + public function getName() + { + return 'clone_var'; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php new file mode 100644 index 00000000..c8bfd36e --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionAbsentBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class ExtensionAbsentBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php new file mode 100644 index 00000000..b43bc665 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionLoadedBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\Extension; + +class ExtensionLoadedExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php new file mode 100644 index 00000000..3af81cb0 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionLoadedBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class ExtensionLoadedBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/DependencyInjection/ExtensionNotValidExtension.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/DependencyInjection/ExtensionNotValidExtension.php new file mode 100644 index 00000000..0fd64316 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/DependencyInjection/ExtensionNotValidExtension.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionNotValidBundle\DependencyInjection; + +class ExtensionNotValidExtension +{ + public function getAlias() + { + return 'extension_not_valid'; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/ExtensionNotValidBundle.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/ExtensionNotValidBundle.php new file mode 100644 index 00000000..34e29203 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/ExtensionNotValidBundle.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionNotValidBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class ExtensionNotValidBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php new file mode 100644 index 00000000..977976b7 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\Command; + +use Symfony\Component\Console\Command\Command; + +class FooCommand extends Command +{ + protected function configure() + { + $this->setName('foo'); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.php new file mode 100644 index 00000000..10857171 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\Extension; + +class ExtensionPresentExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php new file mode 100644 index 00000000..36a7ad40 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class ExtensionPresentBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/KernelForOverrideName.php b/vendor/symfony/http-kernel/Tests/Fixtures/KernelForOverrideName.php new file mode 100644 index 00000000..a1102ab7 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/KernelForOverrideName.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures; + +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; + +class KernelForOverrideName extends Kernel +{ + protected $name = 'overridden'; + + public function registerBundles() + { + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/KernelForTest.php b/vendor/symfony/http-kernel/Tests/Fixtures/KernelForTest.php new file mode 100644 index 00000000..5fd61bbc --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/KernelForTest.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures; + +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; + +class KernelForTest extends Kernel +{ + public function getBundleMap() + { + return $this->bundleMap; + } + + public function registerBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } + + public function isBooted() + { + return $this->booted; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/KernelWithoutBundles.php b/vendor/symfony/http-kernel/Tests/Fixtures/KernelWithoutBundles.php new file mode 100644 index 00000000..cee1b09f --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/KernelWithoutBundles.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures; + +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Kernel; + +class KernelWithoutBundles extends Kernel +{ + public function registerBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } + + protected function build(ContainerBuilder $container) + { + $container->setParameter('test_executed', true); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Resources/BaseBundle/hide.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/BaseBundle/hide.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Resources/Bundle1Bundle/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/Bundle1Bundle/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Resources/ChildBundle/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/ChildBundle/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Resources/FooBundle/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/FooBundle/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/TestClient.php b/vendor/symfony/http-kernel/Tests/Fixtures/TestClient.php new file mode 100644 index 00000000..e7d60cff --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/TestClient.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures; + +use Symfony\Component\HttpKernel\Client; + +class TestClient extends Client +{ + protected function getScript($request) + { + $script = parent::getScript($request); + + $autoload = file_exists(__DIR__.'/../../vendor/autoload.php') + ? __DIR__.'/../../vendor/autoload.php' + : __DIR__.'/../../../../../../vendor/autoload.php' + ; + + $script = preg_replace('/(\->register\(\);)/', "$0\nrequire_once '$autoload';\n", $script); + + return $script; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/TestEventDispatcher.php b/vendor/symfony/http-kernel/Tests/Fixtures/TestEventDispatcher.php new file mode 100644 index 00000000..da7ef5bd --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/TestEventDispatcher.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures; + +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestEventDispatcher extends EventDispatcher implements TraceableEventDispatcherInterface +{ + public function getCalledListeners() + { + return array('foo'); + } + + public function getNotCalledListeners() + { + return array('bar'); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/EsiFragmentRendererTest.php b/vendor/symfony/http-kernel/Tests/Fragment/EsiFragmentRendererTest.php new file mode 100644 index 00000000..bfe922e2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/EsiFragmentRendererTest.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\UriSigner; + +class EsiFragmentRendererTest extends TestCase +{ + public function testRenderFallbackToInlineStrategyIfEsiNotSupported() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(true)); + $strategy->render('/', Request::create('/')); + } + + /** + * @group legacy + * @expectedDeprecation Passing non-scalar values as part of URI attributes to the ESI and SSI rendering strategies is deprecated %s. + */ + public function testRenderFallbackWithObjectAttributesIsDeprecated() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(true), new UriSigner('foo')); + $request = Request::create('/'); + $reference = new ControllerReference('main_controller', array('foo' => array('a' => array(), 'b' => new \stdClass())), array()); + $strategy->render($reference, $request); + } + + public function testRender() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'ESI/1.0'); + + $this->assertEquals('', $strategy->render('/', $request)->getContent()); + $this->assertEquals("\n", $strategy->render('/', $request, array('comment' => 'This is a comment'))->getContent()); + $this->assertEquals('', $strategy->render('/', $request, array('alt' => 'foo'))->getContent()); + } + + public function testRenderControllerReference() + { + $signer = new UriSigner('foo'); + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(), $signer); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'ESI/1.0'); + + $reference = new ControllerReference('main_controller', array(), array()); + $altReference = new ControllerReference('alt_controller', array(), array()); + + $this->assertEquals( + '', + $strategy->render($reference, $request, array('alt' => $altReference))->getContent() + ); + } + + /** + * @expectedException \LogicException + */ + public function testRenderControllerReferenceWithoutSignerThrowsException() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'ESI/1.0'); + + $strategy->render(new ControllerReference('main_controller'), $request); + } + + /** + * @expectedException \LogicException + */ + public function testRenderAltControllerReferenceWithoutSignerThrowsException() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'ESI/1.0'); + + $strategy->render('/', $request, array('alt' => new ControllerReference('alt_controller'))); + } + + private function getInlineStrategy($called = false) + { + $inline = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer')->disableOriginalConstructor()->getMock(); + + if ($called) { + $inline->expects($this->once())->method('render'); + } + + return $inline; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/FragmentHandlerTest.php b/vendor/symfony/http-kernel/Tests/Fragment/FragmentHandlerTest.php new file mode 100644 index 00000000..9c906b50 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/FragmentHandlerTest.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @group time-sensitive + */ +class FragmentHandlerTest extends TestCase +{ + private $requestStack; + + protected function setUp() + { + $this->requestStack = $this->getMockBuilder('Symfony\\Component\\HttpFoundation\\RequestStack') + ->disableOriginalConstructor() + ->getMock() + ; + $this->requestStack + ->expects($this->any()) + ->method('getCurrentRequest') + ->will($this->returnValue(Request::create('/'))) + ; + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRenderWhenRendererDoesNotExist() + { + $handler = new FragmentHandler($this->requestStack); + $handler->render('/', 'foo'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRenderWithUnknownRenderer() + { + $handler = $this->getHandler($this->returnValue(new Response('foo'))); + + $handler->render('/', 'bar'); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Error when rendering "http://localhost/" (Status code is 404). + */ + public function testDeliverWithUnsuccessfulResponse() + { + $handler = $this->getHandler($this->returnValue(new Response('foo', 404))); + + $handler->render('/', 'foo'); + } + + public function testRender() + { + $handler = $this->getHandler($this->returnValue(new Response('foo')), array('/', Request::create('/'), array('foo' => 'foo', 'ignore_errors' => true))); + + $this->assertEquals('foo', $handler->render('/', 'foo', array('foo' => 'foo'))); + } + + protected function getHandler($returnValue, $arguments = array()) + { + $renderer = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface')->getMock(); + $renderer + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue('foo')) + ; + $e = $renderer + ->expects($this->any()) + ->method('render') + ->will($returnValue) + ; + + if ($arguments) { + call_user_func_array(array($e, 'with'), $arguments); + } + + $handler = new FragmentHandler($this->requestStack); + $handler->addRenderer($renderer); + + return $handler; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/HIncludeFragmentRendererTest.php b/vendor/symfony/http-kernel/Tests/Fragment/HIncludeFragmentRendererTest.php new file mode 100644 index 00000000..1be052e5 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/HIncludeFragmentRendererTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\HIncludeFragmentRenderer; +use Symfony\Component\HttpKernel\UriSigner; +use Symfony\Component\HttpFoundation\Request; + +class HIncludeFragmentRendererTest extends TestCase +{ + /** + * @expectedException \LogicException + */ + public function testRenderExceptionWhenControllerAndNoSigner() + { + $strategy = new HIncludeFragmentRenderer(); + $strategy->render(new ControllerReference('main_controller', array(), array()), Request::create('/')); + } + + public function testRenderWithControllerAndSigner() + { + $strategy = new HIncludeFragmentRenderer(null, new UriSigner('foo')); + + $this->assertEquals('', $strategy->render(new ControllerReference('main_controller', array(), array()), Request::create('/'))->getContent()); + } + + public function testRenderWithUri() + { + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('', $strategy->render('/foo', Request::create('/'))->getContent()); + + $strategy = new HIncludeFragmentRenderer(null, new UriSigner('foo')); + $this->assertEquals('', $strategy->render('/foo', Request::create('/'))->getContent()); + } + + public function testRenderWithDefault() + { + // only default + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default'))->getContent()); + + // only global default + $strategy = new HIncludeFragmentRenderer(null, null, 'global_default'); + $this->assertEquals('global_default', $strategy->render('/foo', Request::create('/'), array())->getContent()); + + // global default and default + $strategy = new HIncludeFragmentRenderer(null, null, 'global_default'); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default'))->getContent()); + } + + public function testRenderWithAttributesOptions() + { + // with id + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default', 'id' => 'bar'))->getContent()); + + // with attributes + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default', 'attributes' => array('p1' => 'v1', 'p2' => 'v2')))->getContent()); + + // with id & attributes + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default', 'id' => 'bar', 'attributes' => array('p1' => 'v1', 'p2' => 'v2')))->getContent()); + } + + public function testRenderWithDefaultText() + { + $engine = $this->getMockBuilder('Symfony\\Component\\Templating\\EngineInterface')->getMock(); + $engine->expects($this->once()) + ->method('exists') + ->with('default') + ->will($this->throwException(new \InvalidArgumentException())); + + // only default + $strategy = new HIncludeFragmentRenderer($engine); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default'))->getContent()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/InlineFragmentRendererTest.php b/vendor/symfony/http-kernel/Tests/Fragment/InlineFragmentRendererTest.php new file mode 100644 index 00000000..18e55a5b --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/InlineFragmentRendererTest.php @@ -0,0 +1,250 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class InlineFragmentRendererTest extends TestCase +{ + public function testRender() + { + $strategy = new InlineFragmentRenderer($this->getKernel($this->returnValue(new Response('foo')))); + + $this->assertEquals('foo', $strategy->render('/', Request::create('/'))->getContent()); + } + + public function testRenderWithControllerReference() + { + $strategy = new InlineFragmentRenderer($this->getKernel($this->returnValue(new Response('foo')))); + + $this->assertEquals('foo', $strategy->render(new ControllerReference('main_controller', array(), array()), Request::create('/'))->getContent()); + } + + public function testRenderWithObjectsAsAttributes() + { + $object = new \stdClass(); + + $subRequest = Request::create('/_fragment?_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dmain_controller'); + $subRequest->attributes->replace(array('object' => $object, '_format' => 'html', '_controller' => 'main_controller', '_locale' => 'en')); + $subRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $subRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($subRequest)); + + $this->assertSame('foo', $strategy->render(new ControllerReference('main_controller', array('object' => $object), array()), Request::create('/'))->getContent()); + } + + /** + * @group legacy + */ + public function testRenderWithObjectsAsAttributesPassedAsObjectsInTheControllerLegacy() + { + $resolver = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver')->setMethods(array('getController'))->getMock(); + $resolver + ->expects($this->once()) + ->method('getController') + ->will($this->returnValue(function (\stdClass $object, Bar $object1) { + return new Response($object1->getBar()); + })) + ; + + $kernel = new HttpKernel(new EventDispatcher(), $resolver, new RequestStack()); + $renderer = new InlineFragmentRenderer($kernel); + + $response = $renderer->render(new ControllerReference('main_controller', array('object' => new \stdClass(), 'object1' => new Bar()), array()), Request::create('/')); + $this->assertEquals('bar', $response->getContent()); + } + + /** + * @group legacy + */ + public function testRenderWithObjectsAsAttributesPassedAsObjectsInTheController() + { + $resolver = $this->getMockBuilder(ControllerResolverInterface::class)->getMock(); + $resolver + ->expects($this->once()) + ->method('getController') + ->will($this->returnValue(function (\stdClass $object, Bar $object1) { + return new Response($object1->getBar()); + })) + ; + + $kernel = new HttpKernel(new EventDispatcher(), $resolver, new RequestStack(), new ArgumentResolver()); + $renderer = new InlineFragmentRenderer($kernel); + + $response = $renderer->render(new ControllerReference('main_controller', array('object' => new \stdClass(), 'object1' => new Bar()), array()), Request::create('/')); + $this->assertEquals('bar', $response->getContent()); + } + + public function testRenderWithTrustedHeaderDisabled() + { + Request::setTrustedProxies(array(), 0); + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest(Request::create('/'))); + $this->assertSame('foo', $strategy->render('/', Request::create('/'))->getContent()); + + Request::setTrustedProxies(array(), -1); + } + + /** + * @expectedException \RuntimeException + */ + public function testRenderExceptionNoIgnoreErrors() + { + $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); + $dispatcher->expects($this->never())->method('dispatch'); + + $strategy = new InlineFragmentRenderer($this->getKernel($this->throwException(new \RuntimeException('foo'))), $dispatcher); + + $this->assertEquals('foo', $strategy->render('/', Request::create('/'))->getContent()); + } + + public function testRenderExceptionIgnoreErrors() + { + $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); + $dispatcher->expects($this->once())->method('dispatch')->with(KernelEvents::EXCEPTION); + + $strategy = new InlineFragmentRenderer($this->getKernel($this->throwException(new \RuntimeException('foo'))), $dispatcher); + + $this->assertEmpty($strategy->render('/', Request::create('/'), array('ignore_errors' => true))->getContent()); + } + + public function testRenderExceptionIgnoreErrorsWithAlt() + { + $strategy = new InlineFragmentRenderer($this->getKernel($this->onConsecutiveCalls( + $this->throwException(new \RuntimeException('foo')), + $this->returnValue(new Response('bar')) + ))); + + $this->assertEquals('bar', $strategy->render('/', Request::create('/'), array('ignore_errors' => true, 'alt' => '/foo'))->getContent()); + } + + private function getKernel($returnValue) + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $kernel + ->expects($this->any()) + ->method('handle') + ->will($returnValue) + ; + + return $kernel; + } + + public function testExceptionInSubRequestsDoesNotMangleOutputBuffers() + { + $controllerResolver = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface')->getMock(); + $controllerResolver + ->expects($this->once()) + ->method('getController') + ->will($this->returnValue(function () { + ob_start(); + echo 'bar'; + throw new \RuntimeException(); + })) + ; + + $argumentResolver = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface')->getMock(); + $argumentResolver + ->expects($this->once()) + ->method('getArguments') + ->will($this->returnValue(array())) + ; + + $kernel = new HttpKernel(new EventDispatcher(), $controllerResolver, new RequestStack(), $argumentResolver); + $renderer = new InlineFragmentRenderer($kernel); + + // simulate a main request with output buffering + ob_start(); + echo 'Foo'; + + // simulate a sub-request with output buffering and an exception + $renderer->render('/', Request::create('/'), array('ignore_errors' => true)); + + $this->assertEquals('Foo', ob_get_clean()); + } + + public function testESIHeaderIsKeptInSubrequest() + { + $expectedSubRequest = Request::create('/'); + $expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + + if (Request::HEADER_X_FORWARDED_FOR & Request::getTrustedHeaderSet()) { + $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + } + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $strategy->render('/', $request); + } + + public function testESIHeaderIsKeptInSubrequestWithTrustedHeaderDisabled() + { + Request::setTrustedProxies(array(), 0); + + $this->testESIHeaderIsKeptInSubrequest(); + + Request::setTrustedProxies(array(), -1); + } + + public function testHeadersPossiblyResultingIn304AreNotAssignedToSubrequest() + { + $expectedSubRequest = Request::create('/'); + if (Request::HEADER_X_FORWARDED_FOR & Request::getTrustedHeaderSet()) { + $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + } + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); + $request = Request::create('/', 'GET', array(), array(), array(), array('HTTP_IF_MODIFIED_SINCE' => 'Fri, 01 Jan 2016 00:00:00 GMT', 'HTTP_IF_NONE_MATCH' => '*')); + $strategy->render('/', $request); + } + + /** + * Creates a Kernel expecting a request equals to $request + * Allows delta in comparison in case REQUEST_TIME changed by 1 second. + */ + private function getKernelExpectingRequest(Request $request, $strict = false) + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $kernel + ->expects($this->once()) + ->method('handle') + ->with($this->equalTo($request, 1)) + ->willReturn(new Response('foo')); + + return $kernel; + } +} + +class Bar +{ + public $bar = 'bar'; + + public function getBar() + { + return $this->bar; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/RoutableFragmentRendererTest.php b/vendor/symfony/http-kernel/Tests/Fragment/RoutableFragmentRendererTest.php new file mode 100644 index 00000000..3a040ded --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/RoutableFragmentRendererTest.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +class RoutableFragmentRendererTest extends TestCase +{ + /** + * @dataProvider getGenerateFragmentUriData + */ + public function testGenerateFragmentUri($uri, $controller) + { + $this->assertEquals($uri, $this->callGenerateFragmentUriMethod($controller, Request::create('/'))); + } + + /** + * @dataProvider getGenerateFragmentUriData + */ + public function testGenerateAbsoluteFragmentUri($uri, $controller) + { + $this->assertEquals('http://localhost'.$uri, $this->callGenerateFragmentUriMethod($controller, Request::create('/'), true)); + } + + public function getGenerateFragmentUriData() + { + return array( + array('/_fragment?_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array(), array())), + array('/_fragment?_path=_format%3Dxml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array('_format' => 'xml'), array())), + array('/_fragment?_path=foo%3Dfoo%26_format%3Djson%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array('foo' => 'foo', '_format' => 'json'), array())), + array('/_fragment?bar=bar&_path=foo%3Dfoo%26_format%3Dhtml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array('foo' => 'foo'), array('bar' => 'bar'))), + array('/_fragment?foo=foo&_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array(), array('foo' => 'foo'))), + array('/_fragment?_path=foo%255B0%255D%3Dfoo%26foo%255B1%255D%3Dbar%26_format%3Dhtml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array('foo' => array('foo', 'bar')), array())), + ); + } + + public function testGenerateFragmentUriWithARequest() + { + $request = Request::create('/'); + $request->attributes->set('_format', 'json'); + $request->setLocale('fr'); + $controller = new ControllerReference('controller', array(), array()); + + $this->assertEquals('/_fragment?_path=_format%3Djson%26_locale%3Dfr%26_controller%3Dcontroller', $this->callGenerateFragmentUriMethod($controller, $request)); + } + + /** + * @expectedException \LogicException + * @dataProvider getGenerateFragmentUriDataWithNonScalar + */ + public function testGenerateFragmentUriWithNonScalar($controller) + { + $this->callGenerateFragmentUriMethod($controller, Request::create('/')); + } + + public function getGenerateFragmentUriDataWithNonScalar() + { + return array( + array(new ControllerReference('controller', array('foo' => new Foo(), 'bar' => 'bar'), array())), + array(new ControllerReference('controller', array('foo' => array('foo' => 'foo'), 'bar' => array('bar' => new Foo())), array())), + ); + } + + private function callGenerateFragmentUriMethod(ControllerReference $reference, Request $request, $absolute = false) + { + $renderer = $this->getMockForAbstractClass('Symfony\Component\HttpKernel\Fragment\RoutableFragmentRenderer'); + $r = new \ReflectionObject($renderer); + $m = $r->getMethod('generateFragmentUri'); + $m->setAccessible(true); + + return $m->invoke($renderer, $reference, $request, $absolute); + } +} + +class Foo +{ + public $foo; + + public function getFoo() + { + return $this->foo; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/SsiFragmentRendererTest.php b/vendor/symfony/http-kernel/Tests/Fragment/SsiFragmentRendererTest.php new file mode 100644 index 00000000..b537625f --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/SsiFragmentRendererTest.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\SsiFragmentRenderer; +use Symfony\Component\HttpKernel\HttpCache\Ssi; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\UriSigner; + +class SsiFragmentRendererTest extends TestCase +{ + public function testRenderFallbackToInlineStrategyIfSsiNotSupported() + { + $strategy = new SsiFragmentRenderer(new Ssi(), $this->getInlineStrategy(true)); + $strategy->render('/', Request::create('/')); + } + + public function testRender() + { + $strategy = new SsiFragmentRenderer(new Ssi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'SSI/1.0'); + + $this->assertEquals('', $strategy->render('/', $request)->getContent()); + $this->assertEquals('', $strategy->render('/', $request, array('comment' => 'This is a comment'))->getContent(), 'Strategy options should not impact the ssi include tag'); + } + + public function testRenderControllerReference() + { + $signer = new UriSigner('foo'); + $strategy = new SsiFragmentRenderer(new Ssi(), $this->getInlineStrategy(), $signer); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'SSI/1.0'); + + $reference = new ControllerReference('main_controller', array(), array()); + $altReference = new ControllerReference('alt_controller', array(), array()); + + $this->assertEquals( + '', + $strategy->render($reference, $request, array('alt' => $altReference))->getContent() + ); + } + + /** + * @expectedException \LogicException + */ + public function testRenderControllerReferenceWithoutSignerThrowsException() + { + $strategy = new SsiFragmentRenderer(new Ssi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'SSI/1.0'); + + $strategy->render(new ControllerReference('main_controller'), $request); + } + + /** + * @expectedException \LogicException + */ + public function testRenderAltControllerReferenceWithoutSignerThrowsException() + { + $strategy = new SsiFragmentRenderer(new Ssi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'SSI/1.0'); + + $strategy->render('/', $request, array('alt' => new ControllerReference('alt_controller'))); + } + + private function getInlineStrategy($called = false) + { + $inline = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer')->disableOriginalConstructor()->getMock(); + + if ($called) { + $inline->expects($this->once())->method('render'); + } + + return $inline; + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/EsiTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/EsiTest.php new file mode 100644 index 00000000..a8662b2a --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/EsiTest.php @@ -0,0 +1,248 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class EsiTest extends TestCase +{ + public function testHasSurrogateEsiCapability() + { + $esi = new Esi(); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $this->assertTrue($esi->hasSurrogateCapability($request)); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'foobar'); + $this->assertFalse($esi->hasSurrogateCapability($request)); + + $request = Request::create('/'); + $this->assertFalse($esi->hasSurrogateCapability($request)); + } + + public function testAddSurrogateEsiCapability() + { + $esi = new Esi(); + + $request = Request::create('/'); + $esi->addSurrogateCapability($request); + $this->assertEquals('symfony="ESI/1.0"', $request->headers->get('Surrogate-Capability')); + + $esi->addSurrogateCapability($request); + $this->assertEquals('symfony="ESI/1.0", symfony="ESI/1.0"', $request->headers->get('Surrogate-Capability')); + } + + public function testAddSurrogateControl() + { + $esi = new Esi(); + + $response = new Response('foo '); + $esi->addSurrogateControl($response); + $this->assertEquals('content="ESI/1.0"', $response->headers->get('Surrogate-Control')); + + $response = new Response('foo'); + $esi->addSurrogateControl($response); + $this->assertEquals('', $response->headers->get('Surrogate-Control')); + } + + public function testNeedsEsiParsing() + { + $esi = new Esi(); + + $response = new Response(); + $response->headers->set('Surrogate-Control', 'content="ESI/1.0"'); + $this->assertTrue($esi->needsParsing($response)); + + $response = new Response(); + $this->assertFalse($esi->needsParsing($response)); + } + + public function testRenderIncludeTag() + { + $esi = new Esi(); + + $this->assertEquals('', $esi->renderIncludeTag('/', '/alt', true)); + $this->assertEquals('', $esi->renderIncludeTag('/', '/alt', false)); + $this->assertEquals('', $esi->renderIncludeTag('/')); + $this->assertEquals(''."\n".'', $esi->renderIncludeTag('/', '/alt', true, 'some comment')); + } + + public function testProcessDoesNothingIfContentTypeIsNotHtml() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response(); + $response->headers->set('Content-Type', 'text/plain'); + $esi->process($request, $response); + + $this->assertFalse($response->headers->has('x-body-eval')); + } + + public function testMultilineEsiRemoveTagsAreRemoved() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response(' www.example.com Keep this'."\n www.example.com And this"); + $esi->process($request, $response); + + $this->assertEquals(' Keep this And this', $response->getContent()); + } + + public function testCommentTagsAreRemoved() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response(' Keep this'); + $esi->process($request, $response); + + $this->assertEquals(' Keep this', $response->getContent()); + } + + public function testProcess() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('foo '); + $esi->process($request, $response); + + $this->assertEquals('foo surrogate->handle($this, \'...\', \'alt\', true) ?>'."\n", $response->getContent()); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + + $response = new Response('foo '); + $esi->process($request, $response); + + $this->assertEquals('foo surrogate->handle($this, \'foo\\\'\', \'bar\\\'\', true) ?>'."\n", $response->getContent()); + + $response = new Response('foo '); + $esi->process($request, $response); + + $this->assertEquals('foo surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent()); + + $response = new Response('foo '); + $esi->process($request, $response); + + $this->assertEquals('foo surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent()); + } + + public function testProcessEscapesPhpTags() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response(''); + $esi->process($request, $response); + + $this->assertEquals('php cript language=php>', $response->getContent()); + } + + /** + * @expectedException \RuntimeException + */ + public function testProcessWhenNoSrcInAnEsi() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('foo '); + $esi->process($request, $response); + } + + public function testProcessRemoveSurrogateControlHeader() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('foo '); + $response->headers->set('Surrogate-Control', 'content="ESI/1.0"'); + $esi->process($request, $response); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + + $response->headers->set('Surrogate-Control', 'no-store, content="ESI/1.0"'); + $esi->process($request, $response); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + $this->assertEquals('no-store', $response->headers->get('surrogate-control')); + + $response->headers->set('Surrogate-Control', 'content="ESI/1.0", no-store'); + $esi->process($request, $response); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + $this->assertEquals('no-store', $response->headers->get('surrogate-control')); + } + + public function testHandle() + { + $esi = new Esi(); + $cache = $this->getCache(Request::create('/'), new Response('foo')); + $this->assertEquals('foo', $esi->handle($cache, '/', '/alt', true)); + } + + /** + * @expectedException \RuntimeException + */ + public function testHandleWhenResponseIsNot200() + { + $esi = new Esi(); + $response = new Response('foo'); + $response->setStatusCode(404); + $cache = $this->getCache(Request::create('/'), $response); + $esi->handle($cache, '/', '/alt', false); + } + + public function testHandleWhenResponseIsNot200AndErrorsAreIgnored() + { + $esi = new Esi(); + $response = new Response('foo'); + $response->setStatusCode(404); + $cache = $this->getCache(Request::create('/'), $response); + $this->assertEquals('', $esi->handle($cache, '/', '/alt', true)); + } + + public function testHandleWhenResponseIsNot200AndAltIsPresent() + { + $esi = new Esi(); + $response1 = new Response('foo'); + $response1->setStatusCode(404); + $response2 = new Response('bar'); + $cache = $this->getCache(Request::create('/'), array($response1, $response2)); + $this->assertEquals('bar', $esi->handle($cache, '/', '/alt', false)); + } + + protected function getCache($request, $response) + { + $cache = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpCache\HttpCache')->setMethods(array('getRequest', 'handle'))->disableOriginalConstructor()->getMock(); + $cache->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($request)) + ; + if (is_array($response)) { + $cache->expects($this->any()) + ->method('handle') + ->will(call_user_func_array(array($this, 'onConsecutiveCalls'), $response)) + ; + } else { + $cache->expects($this->any()) + ->method('handle') + ->will($this->returnValue($response)) + ; + } + + return $cache; + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTest.php new file mode 100644 index 00000000..a24380c4 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTest.php @@ -0,0 +1,1367 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * @group time-sensitive + */ +class HttpCacheTest extends HttpCacheTestCase +{ + public function testTerminateDelegatesTerminationOnlyForTerminableInterface() + { + $storeMock = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface') + ->disableOriginalConstructor() + ->getMock(); + + // does not implement TerminableInterface + $kernel = new TestKernel(); + $httpCache = new HttpCache($kernel, $storeMock); + $httpCache->terminate(Request::create('/'), new Response()); + + $this->assertFalse($kernel->terminateCalled, 'terminate() is never called if the kernel class does not implement TerminableInterface'); + + // implements TerminableInterface + $kernelMock = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Kernel') + ->disableOriginalConstructor() + ->setMethods(array('terminate', 'registerBundles', 'registerContainerConfiguration')) + ->getMock(); + + $kernelMock->expects($this->once()) + ->method('terminate'); + + $kernel = new HttpCache($kernelMock, $storeMock); + $kernel->terminate(Request::create('/'), new Response()); + } + + public function testPassesOnNonGetHeadRequests() + { + $this->setNextResponse(200); + $this->request('POST', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertTraceContains('pass'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testInvalidatesOnPostPutDeleteRequests() + { + foreach (array('post', 'put', 'delete') as $method) { + $this->setNextResponse(200); + $this->request($method, '/'); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertTraceContains('invalidate'); + $this->assertTraceContains('pass'); + } + } + + public function testDoesNotCacheWithAuthorizationRequestHeaderAndNonPublicResponse() + { + $this->setNextResponse(200, array('ETag' => '"Foo"')); + $this->request('GET', '/', array('HTTP_AUTHORIZATION' => 'basic foobarbaz')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertEquals('private', $this->response->headers->get('Cache-Control')); + + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testDoesCacheWithAuthorizationRequestHeaderAndPublicResponse() + { + $this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '"Foo"')); + $this->request('GET', '/', array('HTTP_AUTHORIZATION' => 'basic foobarbaz')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertTrue($this->response->headers->has('Age')); + $this->assertEquals('public', $this->response->headers->get('Cache-Control')); + } + + public function testDoesNotCacheWithCookieHeaderAndNonPublicResponse() + { + $this->setNextResponse(200, array('ETag' => '"Foo"')); + $this->request('GET', '/', array(), array('foo' => 'bar')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertEquals('private', $this->response->headers->get('Cache-Control')); + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testDoesNotCacheRequestsWithACookieHeader() + { + $this->setNextResponse(200); + $this->request('GET', '/', array(), array('foo' => 'bar')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertEquals('private', $this->response->headers->get('Cache-Control')); + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testRespondsWith304WhenIfModifiedSinceMatchesLastModified() + { + $time = \DateTime::createFromFormat('U', time()); + + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Last-Modified' => $time->format(DATE_RFC2822), 'Content-Type' => 'text/plain'), 'Hello World'); + $this->request('GET', '/', array('HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + + $this->assertHttpKernelIsCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->headers->get('Content-Type')); + $this->assertEmpty($this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testRespondsWith304WhenIfNoneMatchMatchesETag() + { + $this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '12345', 'Content-Type' => 'text/plain'), 'Hello World'); + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345')); + + $this->assertHttpKernelIsCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->headers->get('Content-Type')); + $this->assertTrue($this->response->headers->has('ETag')); + $this->assertEmpty($this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testRespondsWith304OnlyIfIfNoneMatchAndIfModifiedSinceBothMatch() + { + $time = \DateTime::createFromFormat('U', time()); + + $this->setNextResponse(200, array(), '', function ($request, $response) use ($time) { + $response->setStatusCode(200); + $response->headers->set('ETag', '12345'); + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + $response->headers->set('Content-Type', 'text/plain'); + $response->setContent('Hello World'); + }); + + // only ETag matches + $t = \DateTime::createFromFormat('U', time() - 3600); + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345', 'HTTP_IF_MODIFIED_SINCE' => $t->format(DATE_RFC2822))); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + + // only Last-Modified matches + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '1234', 'HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + + // Both matches + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345', 'HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + } + + public function testIncrementsMaxAgeWhenNoDateIsSpecifiedEventWhenUsingETag() + { + $this->setNextResponse( + 200, + array( + 'ETag' => '1234', + 'Cache-Control' => 'public, s-maxage=60', + ) + ); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + sleep(2); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertEquals(2, $this->response->headers->get('Age')); + } + + public function testValidatesPrivateResponsesCachedOnTheClient() + { + $this->setNextResponse(200, array(), '', function ($request, $response) { + $etags = preg_split('/\s*,\s*/', $request->headers->get('IF_NONE_MATCH')); + if ($request->cookies->has('authenticated')) { + $response->headers->set('Cache-Control', 'private, no-store'); + $response->setETag('"private tag"'); + if (in_array('"private tag"', $etags)) { + $response->setStatusCode(304); + } else { + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'text/plain'); + $response->setContent('private data'); + } + } else { + $response->headers->set('Cache-Control', 'public'); + $response->setETag('"public tag"'); + if (in_array('"public tag"', $etags)) { + $response->setStatusCode(304); + } else { + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'text/plain'); + $response->setContent('public data'); + } + } + }); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('"public tag"', $this->response->headers->get('ETag')); + $this->assertEquals('public data', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $this->request('GET', '/', array(), array('authenticated' => '')); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('"private tag"', $this->response->headers->get('ETag')); + $this->assertEquals('private data', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('invalid'); + $this->assertTraceNotContains('store'); + } + + public function testStoresResponsesWhenNoCacheRequestDirectivePresent() + { + $time = \DateTime::createFromFormat('U', time() + 5); + + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + + $this->assertHttpKernelIsCalled(); + $this->assertTraceContains('store'); + $this->assertTrue($this->response->headers->has('Age')); + } + + public function testReloadsResponsesWhenCacheHitsButNoCacheRequestDirectivePresentWhenAllowReloadIsSetTrue() + { + $count = 0; + + $this->setNextResponse(200, array('Cache-Control' => 'public, max-age=10000'), '', function ($request, $response) use (&$count) { + ++$count; + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_reload'] = true; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Goodbye World', $this->response->getContent()); + $this->assertTraceContains('reload'); + $this->assertTraceContains('store'); + } + + public function testDoesNotReloadResponsesWhenAllowReloadIsSetFalseDefault() + { + $count = 0; + + $this->setNextResponse(200, array('Cache-Control' => 'public, max-age=10000'), '', function ($request, $response) use (&$count) { + ++$count; + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_reload'] = false; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('reload'); + + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('reload'); + } + + public function testRevalidatesFreshCacheEntryWhenMaxAgeRequestDirectiveIsExceededWhenAllowRevalidateOptionIsSetTrue() + { + $count = 0; + + $this->setNextResponse(200, array(), '', function ($request, $response) use (&$count) { + ++$count; + $response->headers->set('Cache-Control', 'public, max-age=10000'); + $response->setETag($count); + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_revalidate'] = true; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'max-age=0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Goodbye World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('invalid'); + $this->assertTraceContains('store'); + } + + public function testDoesNotRevalidateFreshCacheEntryWhenEnableRevalidateOptionIsSetFalseDefault() + { + $count = 0; + + $this->setNextResponse(200, array(), '', function ($request, $response) use (&$count) { + ++$count; + $response->headers->set('Cache-Control', 'public, max-age=10000'); + $response->setETag($count); + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_revalidate'] = false; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'max-age=0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('stale'); + $this->assertTraceNotContains('invalid'); + $this->assertTraceContains('fresh'); + + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'max-age=0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('stale'); + $this->assertTraceNotContains('invalid'); + $this->assertTraceContains('fresh'); + } + + public function testFetchesResponseFromBackendWhenCacheMisses() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('miss'); + $this->assertTrue($this->response->headers->has('Age')); + } + + public function testDoesNotCacheSomeStatusCodeResponses() + { + foreach (array_merge(range(201, 202), range(204, 206), range(303, 305), range(400, 403), range(405, 409), range(411, 417), range(500, 505)) as $code) { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse($code, array('Expires' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals($code, $this->response->getStatusCode()); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + } + + public function testDoesNotCacheResponsesWithExplicitNoStoreDirective() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822), 'Cache-Control' => 'no-store')); + + $this->request('GET', '/'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testDoesNotCacheResponsesWithoutFreshnessInformationOrAValidator() + { + $this->setNextResponse(); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceNotContains('store'); + } + + public function testCachesResponsesWithExplicitNoCacheDirective() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822), 'Cache-Control' => 'public, no-cache')); + + $this->request('GET', '/'); + $this->assertTraceContains('store'); + $this->assertTrue($this->response->headers->has('Age')); + } + + public function testCachesResponsesWithAnExpirationHeader() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + } + + public function testCachesResponsesWithAMaxAgeDirective() + { + $this->setNextResponse(200, array('Cache-Control' => 'public, max-age=5')); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + } + + public function testCachesResponsesWithASMaxAgeDirective() + { + $this->setNextResponse(200, array('Cache-Control' => 's-maxage=5')); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + } + + public function testCachesResponsesWithALastModifiedValidatorButNoFreshnessInformation() + { + $time = \DateTime::createFromFormat('U', time()); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Last-Modified' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testCachesResponsesWithAnETagValidatorButNoFreshnessInformation() + { + $this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '"123456"')); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testHitsCachedResponsesWithExpiresHeader() + { + $time1 = \DateTime::createFromFormat('U', time() - 5); + $time2 = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Date' => $time1->format(DATE_RFC2822), 'Expires' => $time2->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2); + $this->assertTrue($this->response->headers->get('Age') > 0); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testHitsCachedResponseWithMaxAgeDirective() + { + $time = \DateTime::createFromFormat('U', time() - 5); + $this->setNextResponse(200, array('Date' => $time->format(DATE_RFC2822), 'Cache-Control' => 'public, max-age=10')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2); + $this->assertTrue($this->response->headers->get('Age') > 0); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testDegradationWhenCacheLocked() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Skips on windows to avoid permissions issues.'); + } + + $this->cacheConfig['stale_while_revalidate'] = 10; + + // The prescence of Last-Modified makes this cacheable (because Response::isValidateable() then). + $this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=5', 'Last-Modified' => 'some while ago'), 'Old response'); + $this->request('GET', '/'); // warm the cache + + // Now, lock the cache + $concurrentRequest = Request::create('/', 'GET'); + $this->store->lock($concurrentRequest); + + /* + * After 10s, the cached response has become stale. Yet, we're still within the "stale_while_revalidate" + * timeout so we may serve the stale response. + */ + sleep(10); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('stale-while-revalidate'); + $this->assertEquals('Old response', $this->response->getContent()); + + /* + * Another 10s later, stale_while_revalidate is over. Resort to serving the old response, but + * do so with a "server unavailable" message. + */ + sleep(10); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(503, $this->response->getStatusCode()); + $this->assertEquals('Old response', $this->response->getContent()); + } + + public function testHitsCachedResponseWithSMaxAgeDirective() + { + $time = \DateTime::createFromFormat('U', time() - 5); + $this->setNextResponse(200, array('Date' => $time->format(DATE_RFC2822), 'Cache-Control' => 's-maxage=10, max-age=0')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2); + $this->assertTrue($this->response->headers->get('Age') > 0); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testAssignsDefaultTtlWhenResponseHasNoFreshnessInformation() + { + $this->setNextResponse(); + + $this->cacheConfig['default_ttl'] = 10; + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=10/', $this->response->headers->get('Cache-Control')); + + $this->cacheConfig['default_ttl'] = 10; + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=10/', $this->response->headers->get('Cache-Control')); + } + + public function testAssignsDefaultTtlWhenResponseHasNoFreshnessInformationAndAfterTtlWasExpired() + { + $this->setNextResponse(); + + $this->cacheConfig['default_ttl'] = 2; + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + + // expires the cache + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + $tmp = unserialize($values[0]); + $time = \DateTime::createFromFormat('U', time() - 5); + $tmp[0][1]['date'] = $time->format(DATE_RFC2822); + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('save'); + $m->setAccessible(true); + $m->invoke($this->store, 'md'.hash('sha256', 'http://localhost/'), serialize($tmp)); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('invalid'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + + $this->setNextResponse(); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + } + + public function testAssignsDefaultTtlWhenResponseHasNoFreshnessInformationAndAfterTtlWasExpiredWithStatus304() + { + $this->setNextResponse(); + + $this->cacheConfig['default_ttl'] = 2; + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + // expires the cache + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + $tmp = unserialize($values[0]); + $time = \DateTime::createFromFormat('U', time() - 5); + $tmp[0][1]['date'] = $time->format(DATE_RFC2822); + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('save'); + $m->setAccessible(true); + $m->invoke($this->store, 'md'.hash('sha256', 'http://localhost/'), serialize($tmp)); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('valid'); + $this->assertTraceContains('store'); + $this->assertTraceNotContains('miss'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + } + + public function testDoesNotAssignDefaultTtlWhenResponseHasMustRevalidateDirective() + { + $this->setNextResponse(200, array('Cache-Control' => 'must-revalidate')); + + $this->cacheConfig['default_ttl'] = 10; + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertNotRegExp('/s-maxage/', $this->response->headers->get('Cache-Control')); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testFetchesFullResponseWhenCacheStaleAndNoValidatorsPresent() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + + // build initial request + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertNotNull($this->response->headers->get('Age')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + // go in and play around with the cached metadata directly ... + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + $tmp = unserialize($values[0]); + $time = \DateTime::createFromFormat('U', time()); + $tmp[0][1]['expires'] = $time->format(DATE_RFC2822); + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('save'); + $m->setAccessible(true); + $m->invoke($this->store, 'md'.hash('sha256', 'http://localhost/'), serialize($tmp)); + + // build subsequent request; should be found but miss due to freshness + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue($this->response->headers->get('Age') <= 1); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('stale'); + $this->assertTraceNotContains('fresh'); + $this->assertTraceNotContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testValidatesCachedResponsesWithLastModifiedAndNoFreshnessInformation() + { + $time = \DateTime::createFromFormat('U', time()); + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time) { + $response->headers->set('Cache-Control', 'public'); + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + if ($time->format(DATE_RFC2822) == $request->headers->get('IF_MODIFIED_SINCE')) { + $response->setStatusCode(304); + $response->setContent(''); + } + }); + + // build initial request + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Last-Modified')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertTraceNotContains('stale'); + + // build subsequent request; should be found but miss due to freshness + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Last-Modified')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTrue($this->response->headers->get('Age') <= 1); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('valid'); + $this->assertTraceContains('store'); + $this->assertTraceNotContains('miss'); + } + + public function testValidatesCachedResponsesUseSameHttpMethod() + { + $test = $this; + + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($test) { + $test->assertSame('OPTIONS', $request->getMethod()); + }); + + // build initial request + $this->request('OPTIONS', '/'); + + // build subsequent request + $this->request('OPTIONS', '/'); + } + + public function testValidatesCachedResponsesWithETagAndNoFreshnessInformation() + { + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) { + $response->headers->set('Cache-Control', 'public'); + $response->headers->set('ETag', '"12345"'); + if ($response->getETag() == $request->headers->get('IF_NONE_MATCH')) { + $response->setStatusCode(304); + $response->setContent(''); + } + }); + + // build initial request + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('ETag')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + // build subsequent request; should be found but miss due to freshness + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('ETag')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTrue($this->response->headers->get('Age') <= 1); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('valid'); + $this->assertTraceContains('store'); + $this->assertTraceNotContains('miss'); + } + + public function testServesResponseWhileFreshAndRevalidatesWithLastModifiedInformation() + { + $time = \DateTime::createFromFormat('U', time()); + + $this->setNextResponse(200, array(), 'Hello World', function (Request $request, Response $response) use ($time) { + $response->setSharedMaxAge(10); + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + }); + + // prime the cache + $this->request('GET', '/'); + + // next request before s-maxage has expired: Serve from cache + // without hitting the backend + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + sleep(15); // expire the cache + + $this->setNextResponse(304, array(), '', function (Request $request, Response $response) use ($time) { + $this->assertEquals($time->format(DATE_RFC2822), $request->headers->get('IF_MODIFIED_SINCE')); + }); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('valid'); + } + + public function testReplacesCachedResponsesWhenValidationResultsInNon304Response() + { + $time = \DateTime::createFromFormat('U', time()); + $count = 0; + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time, &$count) { + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + $response->headers->set('Cache-Control', 'public'); + switch (++$count) { + case 1: + $response->setContent('first response'); + break; + case 2: + $response->setContent('second response'); + break; + case 3: + $response->setContent(''); + $response->setStatusCode(304); + break; + } + }); + + // first request should fetch from backend and store in cache + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('first response', $this->response->getContent()); + + // second request is validated, is invalid, and replaces cached entry + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('second response', $this->response->getContent()); + + // third response is validated, valid, and returns cached entry + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('second response', $this->response->getContent()); + + $this->assertEquals(3, $count); + } + + public function testPassesHeadRequestsThroughDirectlyOnPass() + { + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) { + $response->setContent(''); + $response->setStatusCode(200); + $this->assertEquals('HEAD', $request->getMethod()); + }); + + $this->request('HEAD', '/', array('HTTP_EXPECT' => 'something ...')); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('', $this->response->getContent()); + } + + public function testUsesCacheToRespondToHeadRequestsWhenFresh() + { + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) { + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->setContent('Hello World'); + $response->setStatusCode(200); + $this->assertNotEquals('HEAD', $request->getMethod()); + }); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('HEAD', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->getContent()); + $this->assertEquals(strlen('Hello World'), $this->response->headers->get('Content-Length')); + } + + public function testSendsNoContentWhenFresh() + { + $time = \DateTime::createFromFormat('U', time()); + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time) { + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + }); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/', array('HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->getContent()); + } + + public function testInvalidatesCachedResponsesOnPost() + { + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) { + if ('GET' == $request->getMethod()) { + $response->setStatusCode(200); + $response->headers->set('Cache-Control', 'public, max-age=500'); + $response->setContent('Hello World'); + } elseif ('POST' == $request->getMethod()) { + $response->setStatusCode(303); + $response->headers->set('Location', '/'); + $response->headers->remove('Cache-Control'); + $response->setContent(''); + } + }); + + // build initial request to enter into the cache + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + // make sure it is valid + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + // now POST to same URL + $this->request('POST', '/helloworld'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('/', $this->response->headers->get('Location')); + $this->assertTraceContains('invalidate'); + $this->assertTraceContains('pass'); + $this->assertEquals('', $this->response->getContent()); + + // now make sure it was actually invalidated + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('invalid'); + $this->assertTraceContains('store'); + } + + public function testServesFromCacheWhenHeadersMatch() + { + $count = 0; + $this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count) { + $response->headers->set('Vary', 'Accept User-Agent Foo'); + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->headers->set('X-Response-Count', ++$count); + $response->setContent($request->headers->get('USER_AGENT')); + }); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + } + + public function testStoresMultipleResponsesWhenHeadersDiffer() + { + $count = 0; + $this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count) { + $response->headers->set('Vary', 'Accept User-Agent Foo'); + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->headers->set('X-Response-Count', ++$count); + $response->setContent($request->headers->get('USER_AGENT')); + }); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertEquals(1, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Bob/2.0', $this->response->getContent()); + $this->assertEquals(2, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertTraceContains('fresh'); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertEquals(1, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0')); + $this->assertTraceContains('fresh'); + $this->assertEquals('Bob/2.0', $this->response->getContent()); + $this->assertEquals(2, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_USER_AGENT' => 'Bob/2.0')); + $this->assertTraceContains('miss'); + $this->assertEquals('Bob/2.0', $this->response->getContent()); + $this->assertEquals(3, $this->response->headers->get('X-Response-Count')); + } + + public function testShouldCatchExceptions() + { + $this->catchExceptions(); + + $this->setNextResponse(); + $this->request('GET', '/'); + + $this->assertExceptionsAreCaught(); + } + + public function testShouldCatchExceptionsWhenReloadingAndNoCacheRequest() + { + $this->catchExceptions(); + + $this->setNextResponse(); + $this->cacheConfig['allow_reload'] = true; + $this->request('GET', '/', array(), array(), false, array('Pragma' => 'no-cache')); + + $this->assertExceptionsAreCaught(); + } + + public function testShouldNotCatchExceptions() + { + $this->catchExceptions(false); + + $this->setNextResponse(); + $this->request('GET', '/'); + + $this->assertExceptionsAreNotCaught(); + } + + public function testEsiCacheSendsTheLowestTtl() + { + $responses = array( + array( + 'status' => 200, + 'body' => ' ', + 'headers' => array( + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array('Cache-Control' => 's-maxage=300'), + ), + array( + 'status' => 200, + 'body' => 'My name is Bobby.', + 'headers' => array('Cache-Control' => 's-maxage=100'), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent()); + + // check for 100 or 99 as the test can be executed after a second change + $this->assertTrue(in_array($this->response->getTtl(), array(99, 100))); + } + + public function testEsiCacheForceValidation() + { + $responses = array( + array( + 'status' => 200, + 'body' => ' ', + 'headers' => array( + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array('ETag' => 'foobar'), + ), + array( + 'status' => 200, + 'body' => 'My name is Bobby.', + 'headers' => array('Cache-Control' => 's-maxage=100'), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent()); + $this->assertNull($this->response->getTtl()); + $this->assertTrue($this->response->mustRevalidate()); + $this->assertTrue($this->response->headers->hasCacheControlDirective('private')); + $this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache')); + } + + public function testEsiRecalculateContentLengthHeader() + { + $responses = array( + array( + 'status' => 200, + 'body' => '', + 'headers' => array( + 'Content-Length' => 26, + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array(), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals('Hello World!', $this->response->getContent()); + $this->assertEquals(12, $this->response->headers->get('Content-Length')); + } + + public function testClientIpIsAlwaysLocalhostForForwardedRequests() + { + $this->setNextResponse(); + $this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1')); + + $this->assertEquals('127.0.0.1', $this->kernel->getBackendRequest()->server->get('REMOTE_ADDR')); + } + + /** + * @dataProvider getTrustedProxyData + */ + public function testHttpCacheIsSetAsATrustedProxy(array $existing, array $expected) + { + Request::setTrustedProxies($existing, Request::HEADER_X_FORWARDED_ALL); + + $this->setNextResponse(); + $this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1')); + + $this->assertEquals($expected, Request::getTrustedProxies()); + } + + public function getTrustedProxyData() + { + return array( + array(array(), array('127.0.0.1')), + array(array('10.0.0.2'), array('10.0.0.2', '127.0.0.1')), + array(array('10.0.0.2', '127.0.0.1'), array('10.0.0.2', '127.0.0.1')), + ); + } + + /** + * @dataProvider getXForwardedForData + */ + public function testXForwarderForHeaderForForwardedRequests($xForwardedFor, $expected) + { + $this->setNextResponse(); + $server = array('REMOTE_ADDR' => '10.0.0.1'); + if (false !== $xForwardedFor) { + $server['HTTP_X_FORWARDED_FOR'] = $xForwardedFor; + } + $this->request('GET', '/', $server); + + $this->assertEquals($expected, $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For')); + } + + public function getXForwardedForData() + { + return array( + array(false, '10.0.0.1'), + array('10.0.0.2', '10.0.0.2, 10.0.0.1'), + array('10.0.0.2, 10.0.0.3', '10.0.0.2, 10.0.0.3, 10.0.0.1'), + ); + } + + public function testXForwarderForHeaderForPassRequests() + { + $this->setNextResponse(); + $server = array('REMOTE_ADDR' => '10.0.0.1'); + $this->request('POST', '/', $server); + + $this->assertEquals('10.0.0.1', $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For')); + } + + public function testEsiCacheRemoveValidationHeadersIfEmbeddedResponses() + { + $time = \DateTime::createFromFormat('U', time()); + + $responses = array( + array( + 'status' => 200, + 'body' => '', + 'headers' => array( + 'Surrogate-Control' => 'content="ESI/1.0"', + 'ETag' => 'hey', + 'Last-Modified' => $time->format(DATE_RFC2822), + ), + ), + array( + 'status' => 200, + 'body' => 'Hey!', + 'headers' => array(), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertNull($this->response->getETag()); + $this->assertNull($this->response->getLastModified()); + } + + public function testDoesNotCacheOptionsRequest() + { + $this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=60'), 'get'); + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + + $this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=60'), 'options'); + $this->request('OPTIONS', '/'); + $this->assertHttpKernelIsCalled(); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertSame('get', $this->response->getContent()); + } +} + +class TestKernel implements HttpKernelInterface +{ + public $terminateCalled = false; + + public function terminate(Request $request, Response $response) + { + $this->terminateCalled = true; + } + + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTestCase.php b/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTestCase.php new file mode 100644 index 00000000..ed5c690d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTestCase.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpKernel\HttpCache\Store; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +class HttpCacheTestCase extends TestCase +{ + protected $kernel; + protected $cache; + protected $caches; + protected $cacheConfig; + protected $request; + protected $response; + protected $responses; + protected $catch; + protected $esi; + + /** + * @var Store + */ + protected $store; + + protected function setUp() + { + $this->kernel = null; + + $this->cache = null; + $this->esi = null; + $this->caches = array(); + $this->cacheConfig = array(); + + $this->request = null; + $this->response = null; + $this->responses = array(); + + $this->catch = false; + + $this->clearDirectory(sys_get_temp_dir().'/http_cache'); + } + + protected function tearDown() + { + if ($this->cache) { + $this->cache->getStore()->cleanup(); + } + $this->kernel = null; + $this->cache = null; + $this->caches = null; + $this->request = null; + $this->response = null; + $this->responses = null; + $this->cacheConfig = null; + $this->catch = null; + $this->esi = null; + + $this->clearDirectory(sys_get_temp_dir().'/http_cache'); + } + + public function assertHttpKernelIsCalled() + { + $this->assertTrue($this->kernel->hasBeenCalled()); + } + + public function assertHttpKernelIsNotCalled() + { + $this->assertFalse($this->kernel->hasBeenCalled()); + } + + public function assertResponseOk() + { + $this->assertEquals(200, $this->response->getStatusCode()); + } + + public function assertTraceContains($trace) + { + $traces = $this->cache->getTraces(); + $traces = current($traces); + + $this->assertRegExp('/'.$trace.'/', implode(', ', $traces)); + } + + public function assertTraceNotContains($trace) + { + $traces = $this->cache->getTraces(); + $traces = current($traces); + + $this->assertNotRegExp('/'.$trace.'/', implode(', ', $traces)); + } + + public function assertExceptionsAreCaught() + { + $this->assertTrue($this->kernel->isCatchingExceptions()); + } + + public function assertExceptionsAreNotCaught() + { + $this->assertFalse($this->kernel->isCatchingExceptions()); + } + + public function request($method, $uri = '/', $server = array(), $cookies = array(), $esi = false, $headers = array()) + { + if (null === $this->kernel) { + throw new \LogicException('You must call setNextResponse() before calling request().'); + } + + $this->kernel->reset(); + + $this->store = new Store(sys_get_temp_dir().'/http_cache'); + + $this->cacheConfig['debug'] = true; + + $this->esi = $esi ? new Esi() : null; + $this->cache = new HttpCache($this->kernel, $this->store, $this->esi, $this->cacheConfig); + $this->request = Request::create($uri, $method, array(), $cookies, array(), $server); + $this->request->headers->add($headers); + + $this->response = $this->cache->handle($this->request, HttpKernelInterface::MASTER_REQUEST, $this->catch); + + $this->responses[] = $this->response; + } + + public function getMetaStorageValues() + { + $values = array(); + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(sys_get_temp_dir().'/http_cache/md', \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + $values[] = file_get_contents($file); + } + + return $values; + } + + // A basic response with 200 status code and a tiny body. + public function setNextResponse($statusCode = 200, array $headers = array(), $body = 'Hello World', \Closure $customizer = null) + { + $this->kernel = new TestHttpKernel($body, $statusCode, $headers, $customizer); + } + + public function setNextResponses($responses) + { + $this->kernel = new TestMultipleHttpKernel($responses); + } + + public function catchExceptions($catch = true) + { + $this->catch = $catch; + } + + public static function clearDirectory($directory) + { + if (!is_dir($directory)) { + return; + } + + $fp = opendir($directory); + while (false !== $file = readdir($fp)) { + if (!in_array($file, array('.', '..'))) { + if (is_link($directory.'/'.$file)) { + unlink($directory.'/'.$file); + } elseif (is_dir($directory.'/'.$file)) { + self::clearDirectory($directory.'/'.$file); + rmdir($directory.'/'.$file); + } else { + unlink($directory.'/'.$file); + } + } + } + + closedir($fp); + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/ResponseCacheStrategyTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/ResponseCacheStrategyTest.php new file mode 100644 index 00000000..5e4c3222 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/ResponseCacheStrategyTest.php @@ -0,0 +1,222 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\ResponseCacheStrategy; + +class ResponseCacheStrategyTest extends TestCase +{ + public function testMinimumSharedMaxAgeWins() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $response1 = new Response(); + $response1->setSharedMaxAge(60); + $cacheStrategy->add($response1); + + $response2 = new Response(); + $response2->setSharedMaxAge(3600); + $cacheStrategy->add($response2); + + $response = new Response(); + $response->setSharedMaxAge(86400); + $cacheStrategy->update($response); + + $this->assertSame('60', $response->headers->getCacheControlDirective('s-maxage')); + } + + public function testSharedMaxAgeNotSetIfNotSetInAnyEmbeddedRequest() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $response1 = new Response(); + $response1->setSharedMaxAge(60); + $cacheStrategy->add($response1); + + $response2 = new Response(); + $cacheStrategy->add($response2); + + $response = new Response(); + $response->setSharedMaxAge(86400); + $cacheStrategy->update($response); + + $this->assertFalse($response->headers->hasCacheControlDirective('s-maxage')); + } + + public function testSharedMaxAgeNotSetIfNotSetInMasterRequest() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $response1 = new Response(); + $response1->setSharedMaxAge(60); + $cacheStrategy->add($response1); + + $response2 = new Response(); + $response2->setSharedMaxAge(3600); + $cacheStrategy->add($response2); + + $response = new Response(); + $cacheStrategy->update($response); + + $this->assertFalse($response->headers->hasCacheControlDirective('s-maxage')); + } + + public function testMasterResponseNotCacheableWhenEmbeddedResponseRequiresValidation() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $embeddedResponse = new Response(); + $embeddedResponse->setLastModified(new \DateTime()); + $cacheStrategy->add($embeddedResponse); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('no-cache')); + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('must-revalidate')); + $this->assertFalse($masterResponse->isFresh()); + } + + public function testValidationOnMasterResponseIsNotPossibleWhenItContainsEmbeddedResponses() + { + $cacheStrategy = new ResponseCacheStrategy(); + + // This master response uses the "validation" model + $masterResponse = new Response(); + $masterResponse->setLastModified(new \DateTime()); + $masterResponse->setEtag('foo'); + + // Embedded response uses "expiry" model + $embeddedResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); + $cacheStrategy->add($embeddedResponse); + + $cacheStrategy->update($masterResponse); + + $this->assertFalse($masterResponse->isValidateable()); + $this->assertFalse($masterResponse->headers->has('Last-Modified')); + $this->assertFalse($masterResponse->headers->has('ETag')); + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('no-cache')); + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('must-revalidate')); + } + + public function testMasterResponseWithValidationIsUnchangedWhenThereIsNoEmbeddedResponse() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setLastModified(new \DateTime()); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->isValidateable()); + } + + public function testMasterResponseWithExpirationIsUnchangedWhenThereIsNoEmbeddedResponse() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->isFresh()); + } + + public function testMasterResponseIsNotCacheableWhenEmbeddedResponseIsNotCacheable() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); // Public, cacheable + + /* This response has no validation or expiration information. + That makes it uncacheable, it is always stale. + (It does *not* make this private, though.) */ + $embeddedResponse = new Response(); + $this->assertFalse($embeddedResponse->isFresh()); // not fresh, as no lifetime is provided + + $cacheStrategy->add($embeddedResponse); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('no-cache')); + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('must-revalidate')); + $this->assertFalse($masterResponse->isFresh()); + } + + public function testEmbeddingPrivateResponseMakesMainResponsePrivate() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); // public, cacheable + + // The embedded response might for example contain per-user data that remains valid for 60 seconds + $embeddedResponse = new Response(); + $embeddedResponse->setPrivate(); + $embeddedResponse->setMaxAge(60); // this would implicitly set "private" as well, but let's be explicit + + $cacheStrategy->add($embeddedResponse); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('private')); + // Not sure if we should pass "max-age: 60" in this case, as long as the response is private and + // that's the more conservative of both the master and embedded response...? + } + + public function testResponseIsExiprableWhenEmbeddedResponseCombinesExpiryAndValidation() + { + /* When "expiration wins over validation" (https://symfony.com/doc/current/http_cache/validation.html) + * and both the main and embedded response provide s-maxage, then the more restricting value of both + * should be fine, regardless of whether the embedded response can be validated later on or must be + * completely regenerated. + */ + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); + + $embeddedResponse = new Response(); + $embeddedResponse->setSharedMaxAge(60); + $embeddedResponse->setEtag('foo'); + + $cacheStrategy->add($embeddedResponse); + $cacheStrategy->update($masterResponse); + + $this->assertSame('60', $masterResponse->headers->getCacheControlDirective('s-maxage')); + } + + public function testResponseIsExpirableButNotValidateableWhenMasterResponseCombinesExpirationAndValidation() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); + $masterResponse->setEtag('foo'); + $masterResponse->setLastModified(new \DateTime()); + + $embeddedResponse = new Response(); + $embeddedResponse->setSharedMaxAge(60); + + $cacheStrategy->add($embeddedResponse); + $cacheStrategy->update($masterResponse); + + $this->assertSame('60', $masterResponse->headers->getCacheControlDirective('s-maxage')); + $this->assertFalse($masterResponse->isValidateable()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/SsiTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/SsiTest.php new file mode 100644 index 00000000..1079d37a --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/SsiTest.php @@ -0,0 +1,215 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\Ssi; + +class SsiTest extends TestCase +{ + public function testHasSurrogateSsiCapability() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'abc="SSI/1.0"'); + $this->assertTrue($ssi->hasSurrogateCapability($request)); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'foobar'); + $this->assertFalse($ssi->hasSurrogateCapability($request)); + + $request = Request::create('/'); + $this->assertFalse($ssi->hasSurrogateCapability($request)); + } + + public function testAddSurrogateSsiCapability() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $ssi->addSurrogateCapability($request); + $this->assertEquals('symfony="SSI/1.0"', $request->headers->get('Surrogate-Capability')); + + $ssi->addSurrogateCapability($request); + $this->assertEquals('symfony="SSI/1.0", symfony="SSI/1.0"', $request->headers->get('Surrogate-Capability')); + } + + public function testAddSurrogateControl() + { + $ssi = new Ssi(); + + $response = new Response('foo '); + $ssi->addSurrogateControl($response); + $this->assertEquals('content="SSI/1.0"', $response->headers->get('Surrogate-Control')); + + $response = new Response('foo'); + $ssi->addSurrogateControl($response); + $this->assertEquals('', $response->headers->get('Surrogate-Control')); + } + + public function testNeedsSsiParsing() + { + $ssi = new Ssi(); + + $response = new Response(); + $response->headers->set('Surrogate-Control', 'content="SSI/1.0"'); + $this->assertTrue($ssi->needsParsing($response)); + + $response = new Response(); + $this->assertFalse($ssi->needsParsing($response)); + } + + public function testRenderIncludeTag() + { + $ssi = new Ssi(); + + $this->assertEquals('', $ssi->renderIncludeTag('/', '/alt', true)); + $this->assertEquals('', $ssi->renderIncludeTag('/', '/alt', false)); + $this->assertEquals('', $ssi->renderIncludeTag('/')); + } + + public function testProcessDoesNothingIfContentTypeIsNotHtml() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $response = new Response(); + $response->headers->set('Content-Type', 'text/plain'); + $ssi->process($request, $response); + + $this->assertFalse($response->headers->has('x-body-eval')); + } + + public function testProcess() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $response = new Response('foo '); + $ssi->process($request, $response); + + $this->assertEquals('foo surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent()); + $this->assertEquals('SSI', $response->headers->get('x-body-eval')); + + $response = new Response('foo '); + $ssi->process($request, $response); + + $this->assertEquals("foo surrogate->handle(\$this, 'foo\\'', '', false) ?>"."\n", $response->getContent()); + } + + public function testProcessEscapesPhpTags() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $response = new Response(''); + $ssi->process($request, $response); + + $this->assertEquals('php cript language=php>', $response->getContent()); + } + + /** + * @expectedException \RuntimeException + */ + public function testProcessWhenNoSrcInAnSsi() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $response = new Response('foo '); + $ssi->process($request, $response); + } + + public function testProcessRemoveSurrogateControlHeader() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $response = new Response('foo '); + $response->headers->set('Surrogate-Control', 'content="SSI/1.0"'); + $ssi->process($request, $response); + $this->assertEquals('SSI', $response->headers->get('x-body-eval')); + + $response->headers->set('Surrogate-Control', 'no-store, content="SSI/1.0"'); + $ssi->process($request, $response); + $this->assertEquals('SSI', $response->headers->get('x-body-eval')); + $this->assertEquals('no-store', $response->headers->get('surrogate-control')); + + $response->headers->set('Surrogate-Control', 'content="SSI/1.0", no-store'); + $ssi->process($request, $response); + $this->assertEquals('SSI', $response->headers->get('x-body-eval')); + $this->assertEquals('no-store', $response->headers->get('surrogate-control')); + } + + public function testHandle() + { + $ssi = new Ssi(); + $cache = $this->getCache(Request::create('/'), new Response('foo')); + $this->assertEquals('foo', $ssi->handle($cache, '/', '/alt', true)); + } + + /** + * @expectedException \RuntimeException + */ + public function testHandleWhenResponseIsNot200() + { + $ssi = new Ssi(); + $response = new Response('foo'); + $response->setStatusCode(404); + $cache = $this->getCache(Request::create('/'), $response); + $ssi->handle($cache, '/', '/alt', false); + } + + public function testHandleWhenResponseIsNot200AndErrorsAreIgnored() + { + $ssi = new Ssi(); + $response = new Response('foo'); + $response->setStatusCode(404); + $cache = $this->getCache(Request::create('/'), $response); + $this->assertEquals('', $ssi->handle($cache, '/', '/alt', true)); + } + + public function testHandleWhenResponseIsNot200AndAltIsPresent() + { + $ssi = new Ssi(); + $response1 = new Response('foo'); + $response1->setStatusCode(404); + $response2 = new Response('bar'); + $cache = $this->getCache(Request::create('/'), array($response1, $response2)); + $this->assertEquals('bar', $ssi->handle($cache, '/', '/alt', false)); + } + + protected function getCache($request, $response) + { + $cache = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpCache\HttpCache')->setMethods(array('getRequest', 'handle'))->disableOriginalConstructor()->getMock(); + $cache->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($request)) + ; + if (is_array($response)) { + $cache->expects($this->any()) + ->method('handle') + ->will(call_user_func_array(array($this, 'onConsecutiveCalls'), $response)) + ; + } else { + $cache->expects($this->any()) + ->method('handle') + ->will($this->returnValue($response)) + ; + } + + return $cache; + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/StoreTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/StoreTest.php new file mode 100644 index 00000000..cef01916 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/StoreTest.php @@ -0,0 +1,301 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\Store; + +class StoreTest extends TestCase +{ + protected $request; + protected $response; + + /** + * @var Store + */ + protected $store; + + protected function setUp() + { + $this->request = Request::create('/'); + $this->response = new Response('hello world', 200, array()); + + HttpCacheTestCase::clearDirectory(sys_get_temp_dir().'/http_cache'); + + $this->store = new Store(sys_get_temp_dir().'/http_cache'); + } + + protected function tearDown() + { + $this->store = null; + $this->request = null; + $this->response = null; + + HttpCacheTestCase::clearDirectory(sys_get_temp_dir().'/http_cache'); + } + + public function testReadsAnEmptyArrayWithReadWhenNothingCachedAtKey() + { + $this->assertEmpty($this->getStoreMetadata('/nothing')); + } + + public function testUnlockFileThatDoesExist() + { + $cacheKey = $this->storeSimpleEntry(); + $this->store->lock($this->request); + + $this->assertTrue($this->store->unlock($this->request)); + } + + public function testUnlockFileThatDoesNotExist() + { + $this->assertFalse($this->store->unlock($this->request)); + } + + public function testRemovesEntriesForKeyWithPurge() + { + $request = Request::create('/foo'); + $this->store->write($request, new Response('foo')); + + $metadata = $this->getStoreMetadata($request); + $this->assertNotEmpty($metadata); + + $this->assertTrue($this->store->purge('/foo')); + $this->assertEmpty($this->getStoreMetadata($request)); + + // cached content should be kept after purging + $path = $this->store->getPath($metadata[0][1]['x-content-digest'][0]); + $this->assertTrue(is_file($path)); + + $this->assertFalse($this->store->purge('/bar')); + } + + public function testStoresACacheEntry() + { + $cacheKey = $this->storeSimpleEntry(); + + $this->assertNotEmpty($this->getStoreMetadata($cacheKey)); + } + + public function testSetsTheXContentDigestResponseHeaderBeforeStoring() + { + $cacheKey = $this->storeSimpleEntry(); + $entries = $this->getStoreMetadata($cacheKey); + list($req, $res) = $entries[0]; + + $this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $res['x-content-digest'][0]); + } + + public function testFindsAStoredEntryWithLookup() + { + $this->storeSimpleEntry(); + $response = $this->store->lookup($this->request); + + $this->assertNotNull($response); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + } + + public function testDoesNotFindAnEntryWithLookupWhenNoneExists() + { + $request = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + + $this->assertNull($this->store->lookup($request)); + } + + public function testCanonizesUrlsForCacheKeys() + { + $this->storeSimpleEntry($path = '/test?x=y&p=q'); + $hitsReq = Request::create($path); + $missReq = Request::create('/test?p=x'); + + $this->assertNotNull($this->store->lookup($hitsReq)); + $this->assertNull($this->store->lookup($missReq)); + } + + public function testDoesNotFindAnEntryWithLookupWhenTheBodyDoesNotExist() + { + $this->storeSimpleEntry(); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $path = $this->getStorePath($this->response->headers->get('X-Content-Digest')); + @unlink($path); + $this->assertNull($this->store->lookup($this->request)); + } + + public function testRestoresResponseHeadersProperlyWithLookup() + { + $this->storeSimpleEntry(); + $response = $this->store->lookup($this->request); + + $this->assertEquals($response->headers->all(), array_merge(array('content-length' => 4, 'x-body-file' => array($this->getStorePath($response->headers->get('X-Content-Digest')))), $this->response->headers->all())); + } + + public function testRestoresResponseContentFromEntityStoreWithLookup() + { + $this->storeSimpleEntry(); + $response = $this->store->lookup($this->request); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test')), $response->getContent()); + } + + public function testInvalidatesMetaAndEntityStoreEntriesWithInvalidate() + { + $this->storeSimpleEntry(); + $this->store->invalidate($this->request); + $response = $this->store->lookup($this->request); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + $this->assertFalse($response->isFresh()); + } + + public function testSucceedsQuietlyWhenInvalidateCalledWithNoMatchingEntries() + { + $req = Request::create('/test'); + $this->store->invalidate($req); + $this->assertNull($this->store->lookup($this->request)); + } + + public function testDoesNotReturnEntriesThatVaryWithLookup() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam')); + $res = new Response('test', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req1, $res); + + $this->assertNull($this->store->lookup($req2)); + } + + public function testDoesNotReturnEntriesThatSlightlyVaryWithLookup() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bam')); + $res = new Response('test', 200, array('Vary' => array('Foo', 'Bar'))); + $this->store->write($req1, $res); + + $this->assertNull($this->store->lookup($req2)); + } + + public function testStoresMultipleResponsesForEachVaryCombination() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $res1 = new Response('test 1', 200, array('Vary' => 'Foo Bar')); + $key = $this->store->write($req1, $res1); + + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam')); + $res2 = new Response('test 2', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req2, $res2); + + $req3 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Baz', 'HTTP_BAR' => 'Boom')); + $res3 = new Response('test 3', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req3, $res3); + + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 3')), $this->store->lookup($req3)->getContent()); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 2')), $this->store->lookup($req2)->getContent()); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->getContent()); + + $this->assertCount(3, $this->getStoreMetadata($key)); + } + + public function testOverwritesNonVaryingResponseWithStore() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $res1 = new Response('test 1', 200, array('Vary' => 'Foo Bar')); + $key = $this->store->write($req1, $res1); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->getContent()); + + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam')); + $res2 = new Response('test 2', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req2, $res2); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 2')), $this->store->lookup($req2)->getContent()); + + $req3 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $res3 = new Response('test 3', 200, array('Vary' => 'Foo Bar')); + $key = $this->store->write($req3, $res3); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 3')), $this->store->lookup($req3)->getContent()); + + $this->assertCount(2, $this->getStoreMetadata($key)); + } + + public function testLocking() + { + $req = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $this->assertTrue($this->store->lock($req)); + + $path = $this->store->lock($req); + $this->assertTrue($this->store->isLocked($req)); + + $this->store->unlock($req); + $this->assertFalse($this->store->isLocked($req)); + } + + public function testPurgeHttps() + { + $request = Request::create('https://example.com/foo'); + $this->store->write($request, new Response('foo')); + + $this->assertNotEmpty($this->getStoreMetadata($request)); + + $this->assertTrue($this->store->purge('https://example.com/foo')); + $this->assertEmpty($this->getStoreMetadata($request)); + } + + public function testPurgeHttpAndHttps() + { + $requestHttp = Request::create('https://example.com/foo'); + $this->store->write($requestHttp, new Response('foo')); + + $requestHttps = Request::create('http://example.com/foo'); + $this->store->write($requestHttps, new Response('foo')); + + $this->assertNotEmpty($this->getStoreMetadata($requestHttp)); + $this->assertNotEmpty($this->getStoreMetadata($requestHttps)); + + $this->assertTrue($this->store->purge('http://example.com/foo')); + $this->assertEmpty($this->getStoreMetadata($requestHttp)); + $this->assertEmpty($this->getStoreMetadata($requestHttps)); + } + + protected function storeSimpleEntry($path = null, $headers = array()) + { + if (null === $path) { + $path = '/test'; + } + + $this->request = Request::create($path, 'get', array(), array(), array(), $headers); + $this->response = new Response('test', 200, array('Cache-Control' => 'max-age=420')); + + return $this->store->write($this->request, $this->response); + } + + protected function getStoreMetadata($key) + { + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('getMetadata'); + $m->setAccessible(true); + + if ($key instanceof Request) { + $m1 = $r->getMethod('getCacheKey'); + $m1->setAccessible(true); + $key = $m1->invoke($this->store, $key); + } + + return $m->invoke($this->store, $key); + } + + protected function getStorePath($key) + { + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('getPath'); + $m->setAccessible(true); + + return $m->invoke($this->store, $key); + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/TestHttpKernel.php b/vendor/symfony/http-kernel/Tests/HttpCache/TestHttpKernel.php new file mode 100644 index 00000000..946c7a31 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/TestHttpKernel.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestHttpKernel extends HttpKernel implements ControllerResolverInterface, ArgumentResolverInterface +{ + protected $body; + protected $status; + protected $headers; + protected $called = false; + protected $customizer; + protected $catch = false; + protected $backendRequest; + + public function __construct($body, $status, $headers, \Closure $customizer = null) + { + $this->body = $body; + $this->status = $status; + $this->headers = $headers; + $this->customizer = $customizer; + + parent::__construct(new EventDispatcher(), $this, null, $this); + } + + public function getBackendRequest() + { + return $this->backendRequest; + } + + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false) + { + $this->catch = $catch; + $this->backendRequest = $request; + + return parent::handle($request, $type, $catch); + } + + public function isCatchingExceptions() + { + return $this->catch; + } + + public function getController(Request $request) + { + return array($this, 'callController'); + } + + public function getArguments(Request $request, $controller) + { + return array($request); + } + + public function callController(Request $request) + { + $this->called = true; + + $response = new Response($this->body, $this->status, $this->headers); + + if (null !== $customizer = $this->customizer) { + $customizer($request, $response); + } + + return $response; + } + + public function hasBeenCalled() + { + return $this->called; + } + + public function reset() + { + $this->called = false; + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/TestMultipleHttpKernel.php b/vendor/symfony/http-kernel/Tests/HttpCache/TestMultipleHttpKernel.php new file mode 100644 index 00000000..926d8daf --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/TestMultipleHttpKernel.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestMultipleHttpKernel extends HttpKernel implements ControllerResolverInterface, ArgumentResolverInterface +{ + protected $bodies = array(); + protected $statuses = array(); + protected $headers = array(); + protected $called = false; + protected $backendRequest; + + public function __construct($responses) + { + foreach ($responses as $response) { + $this->bodies[] = $response['body']; + $this->statuses[] = $response['status']; + $this->headers[] = $response['headers']; + } + + parent::__construct(new EventDispatcher(), $this, null, $this); + } + + public function getBackendRequest() + { + return $this->backendRequest; + } + + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false) + { + $this->backendRequest = $request; + + return parent::handle($request, $type, $catch); + } + + public function getController(Request $request) + { + return array($this, 'callController'); + } + + public function getArguments(Request $request, $controller) + { + return array($request); + } + + public function callController(Request $request) + { + $this->called = true; + + $response = new Response(array_shift($this->bodies), array_shift($this->statuses), array_shift($this->headers)); + + return $response; + } + + public function hasBeenCalled() + { + return $this->called; + } + + public function reset() + { + $this->called = false; + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpKernelTest.php b/vendor/symfony/http-kernel/Tests/HttpKernelTest.php new file mode 100644 index 00000000..7aed26aa --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpKernelTest.php @@ -0,0 +1,403 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class HttpKernelTest extends TestCase +{ + /** + * @expectedException \RuntimeException + */ + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsTrue() + { + $kernel = $this->getHttpKernel(new EventDispatcher(), function () { throw new \RuntimeException(); }); + + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); + } + + /** + * @expectedException \RuntimeException + */ + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsFalseAndNoListenerIsRegistered() + { + $kernel = $this->getHttpKernel(new EventDispatcher(), function () { throw new \RuntimeException(); }); + + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, false); + } + + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsTrueWithAHandlingListener() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) { + $event->setResponse(new Response($event->getException()->getMessage())); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { throw new \RuntimeException('foo'); }); + $response = $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); + + $this->assertEquals('500', $response->getStatusCode()); + $this->assertEquals('foo', $response->getContent()); + } + + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsTrueWithANonHandlingListener() + { + $exception = new \RuntimeException(); + + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) { + // should set a response, but does not + }); + + $kernel = $this->getHttpKernel($dispatcher, function () use ($exception) { throw $exception; }); + + try { + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); + $this->fail('LogicException expected'); + } catch (\RuntimeException $e) { + $this->assertSame($exception, $e); + } + } + + public function testHandleExceptionWithARedirectionResponse() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) { + $event->setResponse(new RedirectResponse('/login', 301)); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { throw new AccessDeniedHttpException(); }); + $response = $kernel->handle(new Request()); + + $this->assertEquals('301', $response->getStatusCode()); + $this->assertEquals('/login', $response->headers->get('Location')); + } + + public function testHandleHttpException() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) { + $event->setResponse(new Response($event->getException()->getMessage())); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { throw new MethodNotAllowedHttpException(array('POST')); }); + $response = $kernel->handle(new Request()); + + $this->assertEquals('405', $response->getStatusCode()); + $this->assertEquals('POST', $response->headers->get('Allow')); + } + + /** + * @group legacy + * @dataProvider getStatusCodes + */ + public function testLegacyHandleWhenAnExceptionIsHandledWithASpecificStatusCode($responseStatusCode, $expectedStatusCode) + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) use ($responseStatusCode, $expectedStatusCode) { + $event->setResponse(new Response('', $responseStatusCode, array('X-Status-Code' => $expectedStatusCode))); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { throw new \RuntimeException(); }); + $response = $kernel->handle(new Request()); + + $this->assertEquals($expectedStatusCode, $response->getStatusCode()); + $this->assertFalse($response->headers->has('X-Status-Code')); + } + + public function getStatusCodes() + { + return array( + array(200, 404), + array(404, 200), + array(301, 200), + array(500, 200), + ); + } + + /** + * @dataProvider getSpecificStatusCodes + */ + public function testHandleWhenAnExceptionIsHandledWithASpecificStatusCode($expectedStatusCode) + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function (GetResponseForExceptionEvent $event) use ($expectedStatusCode) { + $event->allowCustomResponseCode(); + $event->setResponse(new Response('', $expectedStatusCode)); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { throw new \RuntimeException(); }); + $response = $kernel->handle(new Request()); + + $this->assertEquals($expectedStatusCode, $response->getStatusCode()); + } + + public function getSpecificStatusCodes() + { + return array( + array(200), + array(302), + array(403), + ); + } + + public function testHandleWhenAListenerReturnsAResponse() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::REQUEST, function ($event) { + $event->setResponse(new Response('hello')); + }); + + $kernel = $this->getHttpKernel($dispatcher); + + $this->assertEquals('hello', $kernel->handle(new Request())->getContent()); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testHandleWhenNoControllerIsFound() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, false); + + $kernel->handle(new Request()); + } + + public function testHandleWhenTheControllerIsAClosure() + { + $response = new Response('foo'); + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, function () use ($response) { return $response; }); + + $this->assertSame($response, $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAnObjectWithInvoke() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, new Controller()); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAFunction() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, 'Symfony\Component\HttpKernel\Tests\controller_func'); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAnArray() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, array(new Controller(), 'controller')); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAStaticArray() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, array('Symfony\Component\HttpKernel\Tests\Controller', 'staticcontroller')); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + /** + * @expectedException \LogicException + */ + public function testHandleWhenTheControllerDoesNotReturnAResponse() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, function () { return 'foo'; }); + + $kernel->handle(new Request()); + } + + public function testHandleWhenTheControllerDoesNotReturnAResponseButAViewIsRegistered() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::VIEW, function ($event) { + $event->setResponse(new Response($event->getControllerResult())); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { return 'foo'; }); + + $this->assertEquals('foo', $kernel->handle(new Request())->getContent()); + } + + public function testHandleWithAResponseListener() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::RESPONSE, function ($event) { + $event->setResponse(new Response('foo')); + }); + $kernel = $this->getHttpKernel($dispatcher); + + $this->assertEquals('foo', $kernel->handle(new Request())->getContent()); + } + + public function testHandleAllowChangingControllerArguments() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::CONTROLLER_ARGUMENTS, function (FilterControllerArgumentsEvent $event) { + $event->setArguments(array('foo')); + }); + + $kernel = $this->getHttpKernel($dispatcher, function ($content) { return new Response($content); }); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleAllowChangingControllerAndArguments() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::CONTROLLER_ARGUMENTS, function (FilterControllerArgumentsEvent $event) { + $oldController = $event->getController(); + $oldArguments = $event->getArguments(); + + $newController = function ($id) use ($oldController, $oldArguments) { + $response = call_user_func_array($oldController, $oldArguments); + + $response->headers->set('X-Id', $id); + + return $response; + }; + + $event->setController($newController); + $event->setArguments(array('bar')); + }); + + $kernel = $this->getHttpKernel($dispatcher, function ($content) { return new Response($content); }, null, array('foo')); + + $this->assertResponseEquals(new Response('foo', 200, array('X-Id' => 'bar')), $kernel->handle(new Request())); + } + + public function testTerminate() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher); + $dispatcher->addListener(KernelEvents::TERMINATE, function ($event) use (&$called, &$capturedKernel, &$capturedRequest, &$capturedResponse) { + $called = true; + $capturedKernel = $event->getKernel(); + $capturedRequest = $event->getRequest(); + $capturedResponse = $event->getResponse(); + }); + + $kernel->terminate($request = Request::create('/'), $response = new Response()); + $this->assertTrue($called); + $this->assertEquals($kernel, $capturedKernel); + $this->assertEquals($request, $capturedRequest); + $this->assertEquals($response, $capturedResponse); + } + + public function testVerifyRequestStackPushPopDuringHandle() + { + $request = new Request(); + + $stack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->setMethods(array('push', 'pop'))->getMock(); + $stack->expects($this->at(0))->method('push')->with($this->equalTo($request)); + $stack->expects($this->at(1))->method('pop'); + + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, null, $stack); + + $kernel->handle($request, HttpKernelInterface::MASTER_REQUEST); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + */ + public function testInconsistentClientIpsOnMasterRequests() + { + $request = new Request(); + $request->setTrustedProxies(array('1.1.1.1'), Request::HEADER_X_FORWARDED_FOR | Request::HEADER_FORWARDED); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $request->headers->set('FORWARDED', 'for=2.2.2.2'); + $request->headers->set('X_FORWARDED_FOR', '3.3.3.3'); + + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::REQUEST, function ($event) { + $event->getRequest()->getClientIp(); + }); + + $kernel = $this->getHttpKernel($dispatcher); + $kernel->handle($request, $kernel::MASTER_REQUEST, false); + } + + private function getHttpKernel(EventDispatcherInterface $eventDispatcher, $controller = null, RequestStack $requestStack = null, array $arguments = array()) + { + if (null === $controller) { + $controller = function () { return new Response('Hello'); }; + } + + $controllerResolver = $this->getMockBuilder(ControllerResolverInterface::class)->getMock(); + $controllerResolver + ->expects($this->any()) + ->method('getController') + ->will($this->returnValue($controller)); + + $argumentResolver = $this->getMockBuilder(ArgumentResolverInterface::class)->getMock(); + $argumentResolver + ->expects($this->any()) + ->method('getArguments') + ->will($this->returnValue($arguments)); + + return new HttpKernel($eventDispatcher, $controllerResolver, $requestStack, $argumentResolver); + } + + private function assertResponseEquals(Response $expected, Response $actual) + { + $expected->setDate($actual->getDate()); + $this->assertEquals($expected, $actual); + } +} + +class Controller +{ + public function __invoke() + { + return new Response('foo'); + } + + public function controller() + { + return new Response('foo'); + } + + public static function staticController() + { + return new Response('foo'); + } +} + +function controller_func() +{ + return new Response('foo'); +} diff --git a/vendor/symfony/http-kernel/Tests/KernelTest.php b/vendor/symfony/http-kernel/Tests/KernelTest.php new file mode 100644 index 00000000..052e5766 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/KernelTest.php @@ -0,0 +1,907 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\HttpKernel\Config\EnvParametersResource; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest; +use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForOverrideName; +use Symfony\Component\HttpKernel\Tests\Fixtures\KernelWithoutBundles; + +class KernelTest extends TestCase +{ + public static function tearDownAfterClass() + { + $fs = new Filesystem(); + $fs->remove(__DIR__.'/Fixtures/cache'); + } + + public function testConstructor() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $this->assertEquals($env, $kernel->getEnvironment()); + $this->assertEquals($debug, $kernel->isDebug()); + $this->assertFalse($kernel->isBooted()); + $this->assertLessThanOrEqual(microtime(true), $kernel->getStartTime()); + $this->assertNull($kernel->getContainer()); + } + + public function testClone() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $clone = clone $kernel; + + $this->assertEquals($env, $clone->getEnvironment()); + $this->assertEquals($debug, $clone->isDebug()); + $this->assertFalse($clone->isBooted()); + $this->assertLessThanOrEqual(microtime(true), $clone->getStartTime()); + $this->assertNull($clone->getContainer()); + } + + public function testBootInitializesBundlesAndContainer() + { + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer')); + $kernel->expects($this->once()) + ->method('initializeBundles'); + $kernel->expects($this->once()) + ->method('initializeContainer'); + + $kernel->boot(); + } + + public function testBootSetsTheContainerToTheBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle')->getMock(); + $bundle->expects($this->once()) + ->method('setContainer'); + + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer', 'getBundles')); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + $kernel->boot(); + } + + public function testBootSetsTheBootedFlagToTrue() + { + // use test kernel to access isBooted() + $kernel = $this->getKernelForTest(array('initializeBundles', 'initializeContainer')); + $kernel->boot(); + + $this->assertTrue($kernel->isBooted()); + } + + /** + * @group legacy + */ + public function testClassCacheIsLoaded() + { + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer', 'doLoadClassCache')); + $kernel->loadClassCache('name', '.extension'); + $kernel->expects($this->once()) + ->method('doLoadClassCache') + ->with('name', '.extension'); + + $kernel->boot(); + } + + public function testClassCacheIsNotLoadedByDefault() + { + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer', 'doLoadClassCache')); + $kernel->expects($this->never()) + ->method('doLoadClassCache'); + + $kernel->boot(); + } + + /** + * @group legacy + */ + public function testClassCacheIsNotLoadedWhenKernelIsNotBooted() + { + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer', 'doLoadClassCache')); + $kernel->loadClassCache(); + $kernel->expects($this->never()) + ->method('doLoadClassCache'); + } + + public function testEnvParametersResourceIsAdded() + { + $container = new ContainerBuilder(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getContainerBuilder', 'prepareContainer', 'getCacheDir', 'getLogDir')) + ->getMock(); + $kernel->expects($this->any()) + ->method('getContainerBuilder') + ->will($this->returnValue($container)); + $kernel->expects($this->any()) + ->method('prepareContainer') + ->will($this->returnValue(null)); + $kernel->expects($this->any()) + ->method('getCacheDir') + ->will($this->returnValue(sys_get_temp_dir())); + $kernel->expects($this->any()) + ->method('getLogDir') + ->will($this->returnValue(sys_get_temp_dir())); + + $reflection = new \ReflectionClass(get_class($kernel)); + $method = $reflection->getMethod('buildContainer'); + $method->setAccessible(true); + $method->invoke($kernel); + + $found = false; + foreach ($container->getResources() as $resource) { + if ($resource instanceof EnvParametersResource) { + $found = true; + break; + } + } + + $this->assertTrue($found); + } + + public function testBootKernelSeveralTimesOnlyInitializesBundlesOnce() + { + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer')); + $kernel->expects($this->once()) + ->method('initializeBundles'); + + $kernel->boot(); + $kernel->boot(); + } + + public function testShutdownCallsShutdownOnAllBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle')->getMock(); + $bundle->expects($this->once()) + ->method('shutdown'); + + $kernel = $this->getKernel(array(), array($bundle)); + + $kernel->boot(); + $kernel->shutdown(); + } + + public function testShutdownGivesNullContainerToAllBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle')->getMock(); + $bundle->expects($this->at(3)) + ->method('setContainer') + ->with(null); + + $kernel = $this->getKernel(array('getBundles')); + $kernel->expects($this->any()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + $kernel->boot(); + $kernel->shutdown(); + } + + public function testHandleCallsHandleOnHttpKernel() + { + $type = HttpKernelInterface::MASTER_REQUEST; + $catch = true; + $request = new Request(); + + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->getMock(); + $httpKernelMock + ->expects($this->once()) + ->method('handle') + ->with($request, $type, $catch); + + $kernel = $this->getKernel(array('getHttpKernel')); + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->handle($request, $type, $catch); + } + + public function testHandleBootsTheKernel() + { + $type = HttpKernelInterface::MASTER_REQUEST; + $catch = true; + $request = new Request(); + + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->getMock(); + + $kernel = $this->getKernel(array('getHttpKernel', 'boot')); + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->expects($this->once()) + ->method('boot'); + + $kernel->handle($request, $type, $catch); + } + + public function testStripComments() + { + $source = <<<'EOF' +assertEquals($expected, $output); + } + + public function testGetRootDir() + { + $kernel = new KernelForTest('test', true); + + $this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures', realpath($kernel->getRootDir())); + } + + public function testGetName() + { + $kernel = new KernelForTest('test', true); + + $this->assertEquals('Fixtures', $kernel->getName()); + } + + public function testOverrideGetName() + { + $kernel = new KernelForOverrideName('test', true); + + $this->assertEquals('overridden', $kernel->getName()); + } + + public function testSerialize() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $expected = serialize(array($env, $debug)); + $this->assertEquals($expected, $kernel->serialize()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLocateResourceThrowsExceptionWhenNameIsNotValid() + { + $this->getKernel()->locateResource('Foo'); + } + + /** + * @expectedException \RuntimeException + */ + public function testLocateResourceThrowsExceptionWhenNameIsUnsafe() + { + $this->getKernel()->locateResource('@FooBundle/../bar'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLocateResourceThrowsExceptionWhenBundleDoesNotExist() + { + $this->getKernel()->locateResource('@FooBundle/config/routing.xml'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLocateResourceThrowsExceptionWhenResourceDoesNotExist() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle')))) + ; + + $kernel->locateResource('@Bundle1Bundle/config/routing.xml'); + } + + public function testLocateResourceReturnsTheFirstThatMatches() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle')))) + ; + + $this->assertEquals(__DIR__.'/Fixtures/Bundle1Bundle/foo.txt', $kernel->locateResource('@Bundle1Bundle/foo.txt')); + } + + public function testLocateResourceReturnsTheFirstThatMatchesWithParent() + { + $parent = $this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle'); + $child = $this->getBundle(__DIR__.'/Fixtures/Bundle2Bundle'); + + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->exactly(2)) + ->method('getBundle') + ->will($this->returnValue(array($child, $parent))) + ; + + $this->assertEquals(__DIR__.'/Fixtures/Bundle2Bundle/foo.txt', $kernel->locateResource('@ParentAABundle/foo.txt')); + $this->assertEquals(__DIR__.'/Fixtures/Bundle1Bundle/bar.txt', $kernel->locateResource('@ParentAABundle/bar.txt')); + } + + public function testLocateResourceReturnsAllMatches() + { + $parent = $this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle'); + $child = $this->getBundle(__DIR__.'/Fixtures/Bundle2Bundle'); + + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($child, $parent))) + ; + + $this->assertEquals(array( + __DIR__.'/Fixtures/Bundle2Bundle/foo.txt', + __DIR__.'/Fixtures/Bundle1Bundle/foo.txt', ), + $kernel->locateResource('@Bundle1Bundle/foo.txt', null, false)); + } + + public function testLocateResourceReturnsAllMatchesBis() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array( + $this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle'), + $this->getBundle(__DIR__.'/Foobar'), + ))) + ; + + $this->assertEquals( + array(__DIR__.'/Fixtures/Bundle1Bundle/foo.txt'), + $kernel->locateResource('@Bundle1Bundle/foo.txt', null, false) + ); + } + + public function testLocateResourceIgnoresDirOnNonResource() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Bundle1Bundle/foo.txt', + $kernel->locateResource('@Bundle1Bundle/foo.txt', __DIR__.'/Fixtures') + ); + } + + public function testLocateResourceReturnsTheDirOneForResources() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/FooBundle', null, null, 'FooBundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Resources/FooBundle/foo.txt', + $kernel->locateResource('@FooBundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources') + ); + } + + public function testLocateResourceReturnsTheDirOneForResourcesAndBundleOnes() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle', null, null, 'Bundle1Bundle')))) + ; + + $this->assertEquals(array( + __DIR__.'/Fixtures/Resources/Bundle1Bundle/foo.txt', + __DIR__.'/Fixtures/Bundle1Bundle/Resources/foo.txt', ), + $kernel->locateResource('@Bundle1Bundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources', false) + ); + } + + public function testLocateResourceOverrideBundleAndResourcesFolders() + { + $parent = $this->getBundle(__DIR__.'/Fixtures/BaseBundle', null, 'BaseBundle', 'BaseBundle'); + $child = $this->getBundle(__DIR__.'/Fixtures/ChildBundle', 'ParentBundle', 'ChildBundle', 'ChildBundle'); + + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->exactly(4)) + ->method('getBundle') + ->will($this->returnValue(array($child, $parent))) + ; + + $this->assertEquals(array( + __DIR__.'/Fixtures/Resources/ChildBundle/foo.txt', + __DIR__.'/Fixtures/ChildBundle/Resources/foo.txt', + __DIR__.'/Fixtures/BaseBundle/Resources/foo.txt', + ), + $kernel->locateResource('@BaseBundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources', false) + ); + + $this->assertEquals( + __DIR__.'/Fixtures/Resources/ChildBundle/foo.txt', + $kernel->locateResource('@BaseBundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources') + ); + + try { + $kernel->locateResource('@BaseBundle/Resources/hide.txt', __DIR__.'/Fixtures/Resources', false); + $this->fail('Hidden resources should raise an exception when returning an array of matching paths'); + } catch (\RuntimeException $e) { + } + + try { + $kernel->locateResource('@BaseBundle/Resources/hide.txt', __DIR__.'/Fixtures/Resources', true); + $this->fail('Hidden resources should raise an exception when returning the first matching path'); + } catch (\RuntimeException $e) { + } + } + + public function testLocateResourceOnDirectories() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->exactly(2)) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/FooBundle', null, null, 'FooBundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Resources/FooBundle/', + $kernel->locateResource('@FooBundle/Resources/', __DIR__.'/Fixtures/Resources') + ); + $this->assertEquals( + __DIR__.'/Fixtures/Resources/FooBundle', + $kernel->locateResource('@FooBundle/Resources', __DIR__.'/Fixtures/Resources') + ); + + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->exactly(2)) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle', null, null, 'Bundle1Bundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Bundle1Bundle/Resources/', + $kernel->locateResource('@Bundle1Bundle/Resources/') + ); + $this->assertEquals( + __DIR__.'/Fixtures/Bundle1Bundle/Resources', + $kernel->locateResource('@Bundle1Bundle/Resources') + ); + } + + public function testInitializeBundles() + { + $parent = $this->getBundle(null, null, 'ParentABundle'); + $child = $this->getBundle(null, 'ParentABundle', 'ChildABundle'); + + // use test kernel so we can access getBundleMap() + $kernel = $this->getKernelForTest(array('registerBundles')); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($parent, $child))) + ; + $kernel->boot(); + + $map = $kernel->getBundleMap(); + $this->assertEquals(array($child, $parent), $map['ParentABundle']); + } + + public function testInitializeBundlesSupportInheritanceCascade() + { + $grandparent = $this->getBundle(null, null, 'GrandParentBBundle'); + $parent = $this->getBundle(null, 'GrandParentBBundle', 'ParentBBundle'); + $child = $this->getBundle(null, 'ParentBBundle', 'ChildBBundle'); + + // use test kernel so we can access getBundleMap() + $kernel = $this->getKernelForTest(array('registerBundles')); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($grandparent, $parent, $child))) + ; + $kernel->boot(); + + $map = $kernel->getBundleMap(); + $this->assertEquals(array($child, $parent, $grandparent), $map['GrandParentBBundle']); + $this->assertEquals(array($child, $parent), $map['ParentBBundle']); + $this->assertEquals(array($child), $map['ChildBBundle']); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Bundle "ChildCBundle" extends bundle "FooBar", which is not registered. + */ + public function testInitializeBundlesThrowsExceptionWhenAParentDoesNotExists() + { + $child = $this->getBundle(null, 'FooBar', 'ChildCBundle'); + $kernel = $this->getKernel(array(), array($child)); + $kernel->boot(); + } + + public function testInitializeBundlesSupportsArbitraryBundleRegistrationOrder() + { + $grandparent = $this->getBundle(null, null, 'GrandParentCBundle'); + $parent = $this->getBundle(null, 'GrandParentCBundle', 'ParentCBundle'); + $child = $this->getBundle(null, 'ParentCBundle', 'ChildCBundle'); + + // use test kernel so we can access getBundleMap() + $kernel = $this->getKernelForTest(array('registerBundles')); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($parent, $grandparent, $child))) + ; + $kernel->boot(); + + $map = $kernel->getBundleMap(); + $this->assertEquals(array($child, $parent, $grandparent), $map['GrandParentCBundle']); + $this->assertEquals(array($child, $parent), $map['ParentCBundle']); + $this->assertEquals(array($child), $map['ChildCBundle']); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Bundle "ParentCBundle" is directly extended by two bundles "ChildC2Bundle" and "ChildC1Bundle". + */ + public function testInitializeBundlesThrowsExceptionWhenABundleIsDirectlyExtendedByTwoBundles() + { + $parent = $this->getBundle(null, null, 'ParentCBundle'); + $child1 = $this->getBundle(null, 'ParentCBundle', 'ChildC1Bundle'); + $child2 = $this->getBundle(null, 'ParentCBundle', 'ChildC2Bundle'); + + $kernel = $this->getKernel(array(), array($parent, $child1, $child2)); + $kernel->boot(); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Trying to register two bundles with the same name "DuplicateName" + */ + public function testInitializeBundleThrowsExceptionWhenRegisteringTwoBundlesWithTheSameName() + { + $fooBundle = $this->getBundle(null, null, 'FooBundle', 'DuplicateName'); + $barBundle = $this->getBundle(null, null, 'BarBundle', 'DuplicateName'); + + $kernel = $this->getKernel(array(), array($fooBundle, $barBundle)); + $kernel->boot(); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Bundle "CircularRefBundle" can not extend itself. + */ + public function testInitializeBundleThrowsExceptionWhenABundleExtendsItself() + { + $circularRef = $this->getBundle(null, 'CircularRefBundle', 'CircularRefBundle'); + + $kernel = $this->getKernel(array(), array($circularRef)); + $kernel->boot(); + } + + public function testTerminateReturnsSilentlyIfKernelIsNotBooted() + { + $kernel = $this->getKernel(array('getHttpKernel')); + $kernel->expects($this->never()) + ->method('getHttpKernel'); + + $kernel->terminate(Request::create('/'), new Response()); + } + + public function testTerminateDelegatesTerminationOnlyForTerminableInterface() + { + // does not implement TerminableInterface + $httpKernel = new TestKernel(); + + $kernel = $this->getKernel(array('getHttpKernel')); + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->willReturn($httpKernel); + + $kernel->boot(); + $kernel->terminate(Request::create('/'), new Response()); + + $this->assertFalse($httpKernel->terminateCalled, 'terminate() is never called if the kernel class does not implement TerminableInterface'); + + // implements TerminableInterface + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->setMethods(array('terminate')) + ->getMock(); + + $httpKernelMock + ->expects($this->once()) + ->method('terminate'); + + $kernel = $this->getKernel(array('getHttpKernel')); + $kernel->expects($this->exactly(2)) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->boot(); + $kernel->terminate(Request::create('/'), new Response()); + } + + public function testKernelWithoutBundles() + { + $kernel = new KernelWithoutBundles('test', true); + $kernel->boot(); + + $this->assertTrue($kernel->getContainer()->getParameter('test_executed')); + } + + public function testKernelRootDirNameStartingWithANumber() + { + $dir = __DIR__.'/Fixtures/123'; + require_once $dir.'/Kernel123.php'; + $kernel = new \Symfony\Component\HttpKernel\Tests\Fixtures\_123\Kernel123('dev', true); + $this->assertEquals('_123', $kernel->getName()); + } + + /** + * @group legacy + * @expectedDeprecation The Symfony\Component\HttpKernel\Kernel::getEnvParameters() method is deprecated as of 3.3 and will be removed in 4.0. Use the %cenv()%c syntax to get the value of any environment variable from configuration files instead. + * @expectedDeprecation The support of special environment variables that start with SYMFONY__ (such as "SYMFONY__FOO__BAR") is deprecated as of 3.3 and will be removed in 4.0. Use the %cenv()%c syntax instead to get the value of environment variables in configuration files. + */ + public function testSymfonyEnvironmentVariables() + { + $_SERVER['SYMFONY__FOO__BAR'] = 'baz'; + + $kernel = $this->getKernel(); + $method = new \ReflectionMethod($kernel, 'getEnvParameters'); + $method->setAccessible(true); + + $envParameters = $method->invoke($kernel); + $this->assertSame('baz', $envParameters['foo.bar']); + + unset($_SERVER['SYMFONY__FOO__BAR']); + } + + public function testProjectDirExtension() + { + $kernel = new CustomProjectDirKernel('test', true); + $kernel->boot(); + + $this->assertSame('foo', $kernel->getProjectDir()); + $this->assertSame('foo', $kernel->getContainer()->getParameter('kernel.project_dir')); + } + + /** + * Returns a mock for the BundleInterface. + * + * @return BundleInterface + */ + protected function getBundle($dir = null, $parent = null, $className = null, $bundleName = null) + { + $bundle = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Bundle\BundleInterface') + ->setMethods(array('getPath', 'getParent', 'getName')) + ->disableOriginalConstructor() + ; + + if ($className) { + $bundle->setMockClassName($className); + } + + $bundle = $bundle->getMockForAbstractClass(); + + $bundle + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue(null === $bundleName ? get_class($bundle) : $bundleName)) + ; + + $bundle + ->expects($this->any()) + ->method('getPath') + ->will($this->returnValue($dir)) + ; + + $bundle + ->expects($this->any()) + ->method('getParent') + ->will($this->returnValue($parent)) + ; + + return $bundle; + } + + /** + * Returns a mock for the abstract kernel. + * + * @param array $methods Additional methods to mock (besides the abstract ones) + * @param array $bundles Bundles to register + * + * @return Kernel + */ + protected function getKernel(array $methods = array(), array $bundles = array()) + { + $methods[] = 'registerBundles'; + + $kernel = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Kernel') + ->setMethods($methods) + ->setConstructorArgs(array('test', false)) + ->getMockForAbstractClass() + ; + $kernel->expects($this->any()) + ->method('registerBundles') + ->will($this->returnValue($bundles)) + ; + $p = new \ReflectionProperty($kernel, 'rootDir'); + $p->setAccessible(true); + $p->setValue($kernel, __DIR__.'/Fixtures'); + + return $kernel; + } + + protected function getKernelForTest(array $methods = array()) + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->setConstructorArgs(array('test', false)) + ->setMethods($methods) + ->getMock(); + $p = new \ReflectionProperty($kernel, 'rootDir'); + $p->setAccessible(true); + $p->setValue($kernel, __DIR__.'/Fixtures'); + + return $kernel; + } +} + +class TestKernel implements HttpKernelInterface +{ + public $terminateCalled = false; + + public function terminate() + { + $this->terminateCalled = true; + } + + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + } +} + +class CustomProjectDirKernel extends Kernel +{ + private $baseDir; + + public function __construct() + { + parent::__construct('test', false); + + $this->baseDir = 'foo'; + } + + public function registerBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } + + public function getProjectDir() + { + return $this->baseDir; + } + + public function getRootDir() + { + return __DIR__.'/Fixtures'; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Logger.php b/vendor/symfony/http-kernel/Tests/Logger.php new file mode 100644 index 00000000..63c70bf6 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Logger.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Psr\Log\LoggerInterface; + +class Logger implements LoggerInterface +{ + protected $logs; + + public function __construct() + { + $this->clear(); + } + + public function getLogs($level = false) + { + return false === $level ? $this->logs : $this->logs[$level]; + } + + public function clear() + { + $this->logs = array( + 'emergency' => array(), + 'alert' => array(), + 'critical' => array(), + 'error' => array(), + 'warning' => array(), + 'notice' => array(), + 'info' => array(), + 'debug' => array(), + ); + } + + public function log($level, $message, array $context = array()) + { + $this->logs[$level][] = $message; + } + + public function emergency($message, array $context = array()) + { + $this->log('emergency', $message, $context); + } + + public function alert($message, array $context = array()) + { + $this->log('alert', $message, $context); + } + + public function critical($message, array $context = array()) + { + $this->log('critical', $message, $context); + } + + public function error($message, array $context = array()) + { + $this->log('error', $message, $context); + } + + public function warning($message, array $context = array()) + { + $this->log('warning', $message, $context); + } + + public function notice($message, array $context = array()) + { + $this->log('notice', $message, $context); + } + + public function info($message, array $context = array()) + { + $this->log('info', $message, $context); + } + + public function debug($message, array $context = array()) + { + $this->log('debug', $message, $context); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Profiler/FileProfilerStorageTest.php b/vendor/symfony/http-kernel/Tests/Profiler/FileProfilerStorageTest.php new file mode 100644 index 00000000..99ff2075 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Profiler/FileProfilerStorageTest.php @@ -0,0 +1,350 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Profiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage; +use Symfony\Component\HttpKernel\Profiler\Profile; + +class FileProfilerStorageTest extends TestCase +{ + private $tmpDir; + private $storage; + + protected function setUp() + { + $this->tmpDir = sys_get_temp_dir().'/sf2_profiler_file_storage'; + if (is_dir($this->tmpDir)) { + self::cleanDir(); + } + $this->storage = new FileProfilerStorage('file:'.$this->tmpDir); + $this->storage->purge(); + } + + protected function tearDown() + { + self::cleanDir(); + } + + public function testStore() + { + for ($i = 0; $i < 10; ++$i) { + $profile = new Profile('token_'.$i); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar'); + $profile->setMethod('GET'); + $this->storage->write($profile); + } + $this->assertCount(10, $this->storage->find('127.0.0.1', 'http://foo.bar', 20, 'GET'), '->write() stores data in the storage'); + } + + public function testChildren() + { + $parentProfile = new Profile('token_parent'); + $parentProfile->setIp('127.0.0.1'); + $parentProfile->setUrl('http://foo.bar/parent'); + + $childProfile = new Profile('token_child'); + $childProfile->setIp('127.0.0.1'); + $childProfile->setUrl('http://foo.bar/child'); + + $parentProfile->addChild($childProfile); + + $this->storage->write($parentProfile); + $this->storage->write($childProfile); + + // Load them from storage + $parentProfile = $this->storage->read('token_parent'); + $childProfile = $this->storage->read('token_child'); + + // Check child has link to parent + $this->assertNotNull($childProfile->getParent()); + $this->assertEquals($parentProfile->getToken(), $childProfile->getParentToken()); + + // Check parent has child + $children = $parentProfile->getChildren(); + $this->assertCount(1, $children); + $this->assertEquals($childProfile->getToken(), $children[0]->getToken()); + } + + public function testStoreSpecialCharsInUrl() + { + // The storage accepts special characters in URLs (Even though URLs are not + // supposed to contain them) + $profile = new Profile('simple_quote'); + $profile->setUrl('http://foo.bar/\''); + $this->storage->write($profile); + $this->assertTrue(false !== $this->storage->read('simple_quote'), '->write() accepts single quotes in URL'); + + $profile = new Profile('double_quote'); + $profile->setUrl('http://foo.bar/"'); + $this->storage->write($profile); + $this->assertTrue(false !== $this->storage->read('double_quote'), '->write() accepts double quotes in URL'); + + $profile = new Profile('backslash'); + $profile->setUrl('http://foo.bar/\\'); + $this->storage->write($profile); + $this->assertTrue(false !== $this->storage->read('backslash'), '->write() accepts backslash in URL'); + + $profile = new Profile('comma'); + $profile->setUrl('http://foo.bar/,'); + $this->storage->write($profile); + $this->assertTrue(false !== $this->storage->read('comma'), '->write() accepts comma in URL'); + } + + public function testStoreDuplicateToken() + { + $profile = new Profile('token'); + $profile->setUrl('http://example.com/'); + + $this->assertTrue($this->storage->write($profile), '->write() returns true when the token is unique'); + + $profile->setUrl('http://example.net/'); + + $this->assertTrue($this->storage->write($profile), '->write() returns true when the token is already present in the storage'); + $this->assertEquals('http://example.net/', $this->storage->read('token')->getUrl(), '->write() overwrites the current profile data'); + + $this->assertCount(1, $this->storage->find('', '', 1000, ''), '->find() does not return the same profile twice'); + } + + public function testRetrieveByIp() + { + $profile = new Profile('token'); + $profile->setIp('127.0.0.1'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $this->assertCount(1, $this->storage->find('127.0.0.1', '', 10, 'GET'), '->find() retrieve a record by IP'); + $this->assertCount(0, $this->storage->find('127.0.%.1', '', 10, 'GET'), '->find() does not interpret a "%" as a wildcard in the IP'); + $this->assertCount(0, $this->storage->find('127.0._.1', '', 10, 'GET'), '->find() does not interpret a "_" as a wildcard in the IP'); + } + + public function testRetrieveByStatusCode() + { + $profile200 = new Profile('statuscode200'); + $profile200->setStatusCode(200); + $this->storage->write($profile200); + + $profile404 = new Profile('statuscode404'); + $profile404->setStatusCode(404); + $this->storage->write($profile404); + + $this->assertCount(1, $this->storage->find(null, null, 10, null, null, null, '200'), '->find() retrieve a record by Status code 200'); + $this->assertCount(1, $this->storage->find(null, null, 10, null, null, null, '404'), '->find() retrieve a record by Status code 404'); + } + + public function testRetrieveByUrl() + { + $profile = new Profile('simple_quote'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/\''); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $profile = new Profile('double_quote'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/"'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $profile = new Profile('backslash'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo\\bar/'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $profile = new Profile('percent'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/%'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $profile = new Profile('underscore'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/_'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $profile = new Profile('semicolon'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/;'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo.bar/\'', 10, 'GET'), '->find() accepts single quotes in URLs'); + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo.bar/"', 10, 'GET'), '->find() accepts double quotes in URLs'); + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo\\bar/', 10, 'GET'), '->find() accepts backslash in URLs'); + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo.bar/;', 10, 'GET'), '->find() accepts semicolon in URLs'); + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo.bar/%', 10, 'GET'), '->find() does not interpret a "%" as a wildcard in the URL'); + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo.bar/_', 10, 'GET'), '->find() does not interpret a "_" as a wildcard in the URL'); + } + + public function testStoreTime() + { + $dt = new \DateTime('now'); + $start = $dt->getTimestamp(); + + for ($i = 0; $i < 3; ++$i) { + $dt->modify('+1 minute'); + $profile = new Profile('time_'.$i); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar'); + $profile->setTime($dt->getTimestamp()); + $profile->setMethod('GET'); + $this->storage->write($profile); + } + + $records = $this->storage->find('', '', 3, 'GET', $start, time() + 3 * 60); + $this->assertCount(3, $records, '->find() returns all previously added records'); + $this->assertEquals($records[0]['token'], 'time_2', '->find() returns records ordered by time in descendant order'); + $this->assertEquals($records[1]['token'], 'time_1', '->find() returns records ordered by time in descendant order'); + $this->assertEquals($records[2]['token'], 'time_0', '->find() returns records ordered by time in descendant order'); + + $records = $this->storage->find('', '', 3, 'GET', $start, time() + 2 * 60); + $this->assertCount(2, $records, '->find() should return only first two of the previously added records'); + } + + public function testRetrieveByEmptyUrlAndIp() + { + for ($i = 0; $i < 5; ++$i) { + $profile = new Profile('token_'.$i); + $profile->setMethod('GET'); + $this->storage->write($profile); + } + $this->assertCount(5, $this->storage->find('', '', 10, 'GET'), '->find() returns all previously added records'); + $this->storage->purge(); + } + + public function testRetrieveByMethodAndLimit() + { + foreach (array('POST', 'GET') as $method) { + for ($i = 0; $i < 5; ++$i) { + $profile = new Profile('token_'.$i.$method); + $profile->setMethod($method); + $this->storage->write($profile); + } + } + + $this->assertCount(5, $this->storage->find('', '', 5, 'POST')); + + $this->storage->purge(); + } + + public function testPurge() + { + $profile = new Profile('token1'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://example.com/'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $this->assertTrue(false !== $this->storage->read('token1')); + $this->assertCount(1, $this->storage->find('127.0.0.1', '', 10, 'GET')); + + $profile = new Profile('token2'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://example.net/'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $this->assertTrue(false !== $this->storage->read('token2')); + $this->assertCount(2, $this->storage->find('127.0.0.1', '', 10, 'GET')); + + $this->storage->purge(); + + $this->assertEmpty($this->storage->read('token'), '->purge() removes all data stored by profiler'); + $this->assertCount(0, $this->storage->find('127.0.0.1', '', 10, 'GET'), '->purge() removes all items from index'); + } + + public function testDuplicates() + { + for ($i = 1; $i <= 5; ++$i) { + $profile = new Profile('foo'.$i); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://example.net/'); + $profile->setMethod('GET'); + + ///three duplicates + $this->storage->write($profile); + $this->storage->write($profile); + $this->storage->write($profile); + } + $this->assertCount(3, $this->storage->find('127.0.0.1', 'http://example.net/', 3, 'GET'), '->find() method returns incorrect number of entries'); + } + + public function testStatusCode() + { + $profile = new Profile('token1'); + $profile->setStatusCode(200); + $this->storage->write($profile); + + $profile = new Profile('token2'); + $profile->setStatusCode(404); + $this->storage->write($profile); + + $tokens = $this->storage->find('', '', 10, ''); + $this->assertCount(2, $tokens); + $this->assertContains($tokens[0]['status_code'], array(200, 404)); + $this->assertContains($tokens[1]['status_code'], array(200, 404)); + } + + public function testMultiRowIndexFile() + { + $iteration = 3; + for ($i = 0; $i < $iteration; ++$i) { + $profile = new Profile('token'.$i); + $profile->setIp('127.0.0.'.$i); + $profile->setUrl('http://foo.bar/'.$i); + + $this->storage->write($profile); + $this->storage->write($profile); + $this->storage->write($profile); + } + + $handle = fopen($this->tmpDir.'/index.csv', 'r'); + for ($i = 0; $i < $iteration; ++$i) { + $row = fgetcsv($handle); + $this->assertEquals('token'.$i, $row[0]); + $this->assertEquals('127.0.0.'.$i, $row[1]); + $this->assertEquals('http://foo.bar/'.$i, $row[3]); + } + $this->assertFalse(fgetcsv($handle)); + } + + public function testReadLineFromFile() + { + $r = new \ReflectionMethod($this->storage, 'readLineFromFile'); + + $r->setAccessible(true); + + $h = tmpfile(); + + fwrite($h, "line1\n\n\nline2\n"); + fseek($h, 0, SEEK_END); + + $this->assertEquals('line2', $r->invoke($this->storage, $h)); + $this->assertEquals('line1', $r->invoke($this->storage, $h)); + } + + protected function cleanDir() + { + $flags = \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveDirectoryIterator($this->tmpDir, $flags); + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); + + foreach ($iterator as $file) { + if (is_file($file)) { + unlink($file); + } + } + } +} diff --git a/vendor/symfony/http-kernel/Tests/Profiler/ProfilerTest.php b/vendor/symfony/http-kernel/Tests/Profiler/ProfilerTest.php new file mode 100644 index 00000000..1a6f5463 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Profiler/ProfilerTest.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Profiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector; +use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage; +use Symfony\Component\HttpKernel\Profiler\Profiler; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class ProfilerTest extends TestCase +{ + private $tmp; + private $storage; + + public function testCollect() + { + $request = new Request(); + $request->query->set('foo', 'bar'); + $response = new Response('', 204); + $collector = new RequestDataCollector(); + + $profiler = new Profiler($this->storage); + $profiler->add($collector); + $profile = $profiler->collect($request, $response); + $profiler->saveProfile($profile); + + $this->assertSame(204, $profile->getStatusCode()); + $this->assertSame('GET', $profile->getMethod()); + $this->assertSame('bar', $profile->getCollector('request')->getRequestQuery()->all()['foo']->getValue()); + } + + public function testFindWorksWithDates() + { + $profiler = new Profiler($this->storage); + + $this->assertCount(0, $profiler->find(null, null, null, null, '7th April 2014', '9th April 2014')); + } + + public function testFindWorksWithTimestamps() + { + $profiler = new Profiler($this->storage); + + $this->assertCount(0, $profiler->find(null, null, null, null, '1396828800', '1397001600')); + } + + public function testFindWorksWithInvalidDates() + { + $profiler = new Profiler($this->storage); + + $this->assertCount(0, $profiler->find(null, null, null, null, 'some string', '')); + } + + public function testFindWorksWithStatusCode() + { + $profiler = new Profiler($this->storage); + + $this->assertCount(0, $profiler->find(null, null, null, null, null, null, '204')); + } + + protected function setUp() + { + $this->tmp = tempnam(sys_get_temp_dir(), 'sf2_profiler'); + if (file_exists($this->tmp)) { + @unlink($this->tmp); + } + + $this->storage = new FileProfilerStorage('file:'.$this->tmp); + $this->storage->purge(); + } + + protected function tearDown() + { + if (null !== $this->storage) { + $this->storage->purge(); + $this->storage = null; + + @unlink($this->tmp); + } + } +} diff --git a/vendor/symfony/http-kernel/Tests/TestHttpKernel.php b/vendor/symfony/http-kernel/Tests/TestHttpKernel.php new file mode 100644 index 00000000..3ec59272 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/TestHttpKernel.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestHttpKernel extends HttpKernel implements ControllerResolverInterface, ArgumentResolverInterface +{ + public function __construct() + { + parent::__construct(new EventDispatcher(), $this, null, $this); + } + + public function getController(Request $request) + { + return array($this, 'callController'); + } + + public function getArguments(Request $request, $controller) + { + return array($request); + } + + public function callController(Request $request) + { + return new Response('Request: '.$request->getRequestUri()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/UriSignerTest.php b/vendor/symfony/http-kernel/Tests/UriSignerTest.php new file mode 100644 index 00000000..253a4c91 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/UriSignerTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\UriSigner; + +class UriSignerTest extends TestCase +{ + public function testSign() + { + $signer = new UriSigner('foobar'); + + $this->assertContains('?_hash=', $signer->sign('http://example.com/foo')); + $this->assertContains('&_hash=', $signer->sign('http://example.com/foo?foo=bar')); + } + + public function testCheck() + { + $signer = new UriSigner('foobar'); + + $this->assertFalse($signer->check('http://example.com/foo?_hash=foo')); + $this->assertFalse($signer->check('http://example.com/foo?foo=bar&_hash=foo')); + $this->assertFalse($signer->check('http://example.com/foo?foo=bar&_hash=foo&bar=foo')); + + $this->assertTrue($signer->check($signer->sign('http://example.com/foo'))); + $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar'))); + $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&0=integer'))); + + $this->assertTrue($signer->sign('http://example.com/foo?foo=bar&bar=foo') === $signer->sign('http://example.com/foo?bar=foo&foo=bar')); + } + + public function testCheckWithDifferentArgSeparator() + { + $this->iniSet('arg_separator.output', '&'); + $signer = new UriSigner('foobar'); + + $this->assertSame( + 'http://example.com/foo?baz=bay&foo=bar&_hash=rIOcC%2FF3DoEGo%2FvnESjSp7uU9zA9S%2F%2BOLhxgMexoPUM%3D', + $signer->sign('http://example.com/foo?foo=bar&baz=bay') + ); + $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay'))); + } + + public function testCheckWithDifferentParameter() + { + $signer = new UriSigner('foobar', 'qux'); + + $this->assertSame( + 'http://example.com/foo?baz=bay&foo=bar&qux=rIOcC%2FF3DoEGo%2FvnESjSp7uU9zA9S%2F%2BOLhxgMexoPUM%3D', + $signer->sign('http://example.com/foo?foo=bar&baz=bay') + ); + $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay'))); + } +} diff --git a/vendor/symfony/http-kernel/UriSigner.php b/vendor/symfony/http-kernel/UriSigner.php new file mode 100644 index 00000000..6f4f8865 --- /dev/null +++ b/vendor/symfony/http-kernel/UriSigner.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +/** + * Signs URIs. + * + * @author Fabien Potencier + */ +class UriSigner +{ + private $secret; + private $parameter; + + /** + * Constructor. + * + * @param string $secret A secret + * @param string $parameter Query string parameter to use + */ + public function __construct($secret, $parameter = '_hash') + { + $this->secret = $secret; + $this->parameter = $parameter; + } + + /** + * Signs a URI. + * + * The given URI is signed by adding the query string parameter + * which value depends on the URI and the secret. + * + * @param string $uri A URI to sign + * + * @return string The signed URI + */ + public function sign($uri) + { + $url = parse_url($uri); + if (isset($url['query'])) { + parse_str($url['query'], $params); + } else { + $params = array(); + } + + $uri = $this->buildUrl($url, $params); + + return $uri.(false === strpos($uri, '?') ? '?' : '&').$this->parameter.'='.$this->computeHash($uri); + } + + /** + * Checks that a URI contains the correct hash. + * + * The query string parameter must be the last one + * (as it is generated that way by the sign() method, it should + * never be a problem). + * + * @param string $uri A signed URI + * + * @return bool True if the URI is signed correctly, false otherwise + */ + public function check($uri) + { + $url = parse_url($uri); + if (isset($url['query'])) { + parse_str($url['query'], $params); + } else { + $params = array(); + } + + if (empty($params[$this->parameter])) { + return false; + } + + $hash = urlencode($params[$this->parameter]); + unset($params[$this->parameter]); + + return $this->computeHash($this->buildUrl($url, $params)) === $hash; + } + + private function computeHash($uri) + { + return urlencode(base64_encode(hash_hmac('sha256', $uri, $this->secret, true))); + } + + private function buildUrl(array $url, array $params = array()) + { + ksort($params, SORT_STRING); + $url['query'] = http_build_query($params, '', '&'); + + $scheme = isset($url['scheme']) ? $url['scheme'].'://' : ''; + $host = isset($url['host']) ? $url['host'] : ''; + $port = isset($url['port']) ? ':'.$url['port'] : ''; + $user = isset($url['user']) ? $url['user'] : ''; + $pass = isset($url['pass']) ? ':'.$url['pass'] : ''; + $pass = ($user || $pass) ? "$pass@" : ''; + $path = isset($url['path']) ? $url['path'] : ''; + $query = isset($url['query']) && $url['query'] ? '?'.$url['query'] : ''; + $fragment = isset($url['fragment']) ? '#'.$url['fragment'] : ''; + + return $scheme.$user.$pass.$host.$port.$path.$query.$fragment; + } +} diff --git a/vendor/symfony/http-kernel/composer.json b/vendor/symfony/http-kernel/composer.json new file mode 100644 index 00000000..cc543c6d --- /dev/null +++ b/vendor/symfony/http-kernel/composer.json @@ -0,0 +1,70 @@ +{ + "name": "symfony/http-kernel", + "type": "library", + "description": "Symfony HttpKernel 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": "^5.5.9|>=7.0.8", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "~3.3", + "symfony/debug": "~2.8|~3.0", + "psr/log": "~1.0" + }, + "require-dev": { + "symfony/browser-kit": "~2.8|~3.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/css-selector": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/dom-crawler": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0", + "symfony/routing": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~3.3", + "psr/cache": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/var-dumper": "<3.3", + "twig/twig": "<1.34|<2.4,>=2" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\HttpKernel\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/vendor/symfony/http-kernel/phpunit.xml.dist b/vendor/symfony/http-kernel/phpunit.xml.dist new file mode 100644 index 00000000..e0de769d --- /dev/null +++ b/vendor/symfony/http-kernel/phpunit.xml.dist @@ -0,0 +1,40 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + + + + + + Symfony\Component\HttpFoundation + + + + + diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 00000000..39fa189d --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014-2016 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-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 00000000..97e8c9b4 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,664 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_convert_case - Perform case folding on a string + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within anothers + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + const MB_CASE_FOLD = PHP_INT_MAX; + + private static $encodingList = array('ASCII', 'UTF-8'); + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + private static $caseFold = array( + array('µ','ſ',"\xCD\x85",'ς',"\xCF\x90","\xCF\x91","\xCF\x95","\xCF\x96","\xCF\xB0","\xCF\xB1","\xCF\xB5","\xE1\xBA\x9B","\xE1\xBE\xBE"), + array('μ','s','ι', 'σ','β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1",'ι'), + ); + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) + { + $vars = array(&$a, &$b, &$c, &$d, &$e, &$f); + + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + if ('' === $s .= '') { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (MB_CASE_TITLE == $mode) { + $s = preg_replace_callback('/\b\p{Ll}/u', array(__CLASS__, 'title_case_upper'), $s); + $s = preg_replace_callback('/\B[\p{Lu}\p{Lt}]+/u', array(__CLASS__, 'title_case_lower'), $s); + } else { + if (MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + + $i = 0; + $len = strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) { + self::$internalEncoding = $encoding; + + return true; + } + + return false; + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($lang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $lang; + + return true; + } + + return false; + } + + public static function mb_list_encodings() + { + return array('UTF-8'); + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return array('utf8'); + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var); + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strlen($s); + } + + return @iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + if ('' === $needle .= '') { + trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING); + + return false; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = iconv_strrpos($haystack, $needle, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (0 === strcasecmp($c, 'none')) { + return true; + } + + return null !== $c ? false : 'none'; + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return iconv_substr($s, $start, $length, $encoding).''; + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrchr($haystack, $needle, $part); + } + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = array( + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ); + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + 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 = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + $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; + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback($m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case_lower($s) + { + return self::mb_convert_case($s[0], MB_CASE_LOWER, 'UTF-8'); + } + + private static function title_case_upper($s) + { + return self::mb_convert_case($s[0], MB_CASE_UPPER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 00000000..342e8286 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](http://php.net/mbstring) 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-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 00000000..3ca16416 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1101 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', +); + +$result =& $data; +unset($data); + +return $result; diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php new file mode 100644 index 00000000..ec942212 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php @@ -0,0 +1,1109 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ᾈ', + 'ᾁ' => 'ᾉ', + 'ᾂ' => 'ᾊ', + 'ᾃ' => 'ᾋ', + 'ᾄ' => 'ᾌ', + 'ᾅ' => 'ᾍ', + 'ᾆ' => 'ᾎ', + 'ᾇ' => 'ᾏ', + 'ᾐ' => 'ᾘ', + 'ᾑ' => 'ᾙ', + 'ᾒ' => 'ᾚ', + 'ᾓ' => 'ᾛ', + 'ᾔ' => 'ᾜ', + 'ᾕ' => 'ᾝ', + 'ᾖ' => 'ᾞ', + 'ᾗ' => 'ᾟ', + 'ᾠ' => 'ᾨ', + 'ᾡ' => 'ᾩ', + 'ᾢ' => 'ᾪ', + 'ᾣ' => 'ᾫ', + 'ᾤ' => 'ᾬ', + 'ᾥ' => 'ᾭ', + 'ᾦ' => 'ᾮ', + 'ᾧ' => 'ᾯ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ᾼ', + 'ι' => 'Ι', + 'ῃ' => 'ῌ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ῼ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', +); + +$result =& $data; +unset($data); + +return $result; diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 00000000..33722910 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_strlen')) { + define('MB_CASE_UPPER', 0); + define('MB_CASE_LOWER', 1); + define('MB_CASE_TITLE', 2); + + function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); } + function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); } + function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); } + function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); } + function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); } + function mb_language($lang = null) { return p\Mbstring::mb_language($lang); } + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } + function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); } + function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); } + function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); } + function mb_parse_str($s, &$result = array()) { parse_str($s, $result); } + function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); } + function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); } + function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); } + function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); } + function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); } + function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); } + function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); } + function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); } + function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); } + function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); } + function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); } + function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); } + function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); } + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } + function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); } + function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); } + function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); } + function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); } + function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); } + function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); } +} +if (!function_exists('mb_chr')) { + function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); } + function mb_chr($code, $enc = null) { return p\Mbstring::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-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 00000000..e184b210 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "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\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + } +} diff --git a/vendor/symfony/routing/.gitignore b/vendor/symfony/routing/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/routing/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/routing/Annotation/Route.php b/vendor/symfony/routing/Annotation/Route.php new file mode 100644 index 00000000..e3d51570 --- /dev/null +++ b/vendor/symfony/routing/Annotation/Route.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Annotation; + +/** + * Annotation class for @Route(). + * + * @Annotation + * @Target({"CLASS", "METHOD"}) + * + * @author Fabien Potencier + */ +class Route +{ + private $path; + private $name; + private $requirements = array(); + private $options = array(); + private $defaults = array(); + private $host; + private $methods = array(); + private $schemes = array(); + private $condition; + + /** + * Constructor. + * + * @param array $data An array of key/value parameters + * + * @throws \BadMethodCallException + */ + public function __construct(array $data) + { + if (isset($data['value'])) { + $data['path'] = $data['value']; + unset($data['value']); + } + + foreach ($data as $key => $value) { + $method = 'set'.str_replace('_', '', $key); + if (!method_exists($this, $method)) { + throw new \BadMethodCallException(sprintf('Unknown property "%s" on annotation "%s".', $key, get_class($this))); + } + $this->$method($value); + } + } + + public function setPath($path) + { + $this->path = $path; + } + + public function getPath() + { + return $this->path; + } + + public function setHost($pattern) + { + $this->host = $pattern; + } + + public function getHost() + { + return $this->host; + } + + public function setName($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + + public function setRequirements($requirements) + { + $this->requirements = $requirements; + } + + public function getRequirements() + { + return $this->requirements; + } + + public function setOptions($options) + { + $this->options = $options; + } + + public function getOptions() + { + return $this->options; + } + + public function setDefaults($defaults) + { + $this->defaults = $defaults; + } + + public function getDefaults() + { + return $this->defaults; + } + + public function setSchemes($schemes) + { + $this->schemes = is_array($schemes) ? $schemes : array($schemes); + } + + public function getSchemes() + { + return $this->schemes; + } + + public function setMethods($methods) + { + $this->methods = is_array($methods) ? $methods : array($methods); + } + + public function getMethods() + { + return $this->methods; + } + + public function setCondition($condition) + { + $this->condition = $condition; + } + + public function getCondition() + { + return $this->condition; + } +} diff --git a/vendor/symfony/routing/CHANGELOG.md b/vendor/symfony/routing/CHANGELOG.md new file mode 100644 index 00000000..d04581f4 --- /dev/null +++ b/vendor/symfony/routing/CHANGELOG.md @@ -0,0 +1,219 @@ +CHANGELOG +========= + +3.3.0 +----- + + * [DEPRECATION] Class parameters have been deprecated and will be removed in 4.0. + * router.options.generator_class + * router.options.generator_base_class + * router.options.generator_dumper_class + * router.options.matcher_class + * router.options.matcher_base_class + * router.options.matcher_dumper_class + * router.options.matcher.cache_class + * router.options.generator.cache_class + +3.2.0 +----- + + * Added support for `bool`, `int`, `float`, `string`, `list` and `map` defaults in XML configurations. + * Added support for UTF-8 requirements + +2.8.0 +----- + + * allowed specifying a directory to recursively load all routing configuration files it contains + * Added ObjectRouteLoader and ServiceRouteLoader that allow routes to be loaded + by calling a method on an object/service. + * [DEPRECATION] Deprecated the hardcoded value for the `$referenceType` argument of the `UrlGeneratorInterface::generate` method. + Use the constants defined in the `UrlGeneratorInterface` instead. + + Before: + + ```php + $router->generate('blog_show', array('slug' => 'my-blog-post'), true); + ``` + + After: + + ```php + use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + + $router->generate('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL); + ``` + +2.5.0 +----- + + * [DEPRECATION] The `ApacheMatcherDumper` and `ApacheUrlMatcher` were deprecated and + will be removed in Symfony 3.0, since the performance gains were minimal and + it's hard to replicate the behaviour of PHP implementation. + +2.3.0 +----- + + * added RequestContext::getQueryString() + +2.2.0 +----- + + * [DEPRECATION] Several route settings have been renamed (the old ones will be removed in 3.0): + + * The `pattern` setting for a route has been deprecated in favor of `path` + * The `_scheme` and `_method` requirements have been moved to the `schemes` and `methods` settings + + Before: + + ```yaml + article_edit: + pattern: /article/{id} + requirements: { '_method': 'POST|PUT', '_scheme': 'https', 'id': '\d+' } + ``` + + ```xml + + POST|PUT + https + \d+ + + ``` + + ```php + $route = new Route(); + $route->setPattern('/article/{id}'); + $route->setRequirement('_method', 'POST|PUT'); + $route->setRequirement('_scheme', 'https'); + ``` + + After: + + ```yaml + article_edit: + path: /article/{id} + methods: [POST, PUT] + schemes: https + requirements: { 'id': '\d+' } + ``` + + ```xml + + \d+ + + ``` + + ```php + $route = new Route(); + $route->setPath('/article/{id}'); + $route->setMethods(array('POST', 'PUT')); + $route->setSchemes('https'); + ``` + + * [BC BREAK] RouteCollection does not behave like a tree structure anymore but as + a flat array of Routes. So when using PHP to build the RouteCollection, you must + make sure to add routes to the sub-collection before adding it to the parent + collection (this is not relevant when using YAML or XML for Route definitions). + + Before: + + ```php + $rootCollection = new RouteCollection(); + $subCollection = new RouteCollection(); + $rootCollection->addCollection($subCollection); + $subCollection->add('foo', new Route('/foo')); + ``` + + After: + + ```php + $rootCollection = new RouteCollection(); + $subCollection = new RouteCollection(); + $subCollection->add('foo', new Route('/foo')); + $rootCollection->addCollection($subCollection); + ``` + + Also one must call `addCollection` from the bottom to the top hierarchy. + So the correct sequence is the following (and not the reverse): + + ```php + $childCollection->addCollection($grandchildCollection); + $rootCollection->addCollection($childCollection); + ``` + + * [DEPRECATION] The methods `RouteCollection::getParent()` and `RouteCollection::getRoot()` + have been deprecated and will be removed in Symfony 2.3. + * [BC BREAK] Misusing the `RouteCollection::addPrefix` method to add defaults, requirements + or options without adding a prefix is not supported anymore. So if you called `addPrefix` + with an empty prefix or `/` only (both have no relevance), like + `addPrefix('', $defaultsArray, $requirementsArray, $optionsArray)` + you need to use the new dedicated methods `addDefaults($defaultsArray)`, + `addRequirements($requirementsArray)` or `addOptions($optionsArray)` instead. + * [DEPRECATION] The `$options` parameter to `RouteCollection::addPrefix()` has been deprecated + because adding options has nothing to do with adding a path prefix. If you want to add options + to all child routes of a RouteCollection, you can use `addOptions()`. + * [DEPRECATION] The method `RouteCollection::getPrefix()` has been deprecated + because it suggested that all routes in the collection would have this prefix, which is + not necessarily true. On top of that, since there is no tree structure anymore, this method + is also useless. Don't worry about performance, prefix optimization for matching is still done + in the dumper, which was also improved in 2.2.0 to find even more grouping possibilities. + * [DEPRECATION] `RouteCollection::addCollection(RouteCollection $collection)` should now only be + used with a single parameter. The other params `$prefix`, `$default`, `$requirements` and `$options` + will still work, but have been deprecated. The `addPrefix` method should be used for this + use-case instead. + Before: `$parentCollection->addCollection($collection, '/prefix', array(...), array(...))` + After: + ```php + $collection->addPrefix('/prefix', array(...), array(...)); + $parentCollection->addCollection($collection); + ``` + * added support for the method default argument values when defining a @Route + * Adjacent placeholders without separator work now, e.g. `/{x}{y}{z}.{_format}`. + * Characters that function as separator between placeholders are now whitelisted + to fix routes with normal text around a variable, e.g. `/prefix{var}suffix`. + * [BC BREAK] The default requirement of a variable has been changed slightly. + Previously it disallowed the previous and the next char around a variable. Now + it disallows the slash (`/`) and the next char. Using the previous char added + no value and was problematic because the route `/index.{_format}` would be + matched by `/index.ht/ml`. + * The default requirement now uses possessive quantifiers when possible which + improves matching performance by up to 20% because it prevents backtracking + when it's not needed. + * The ConfigurableRequirementsInterface can now also be used to disable the requirements + check on URL generation completely by calling `setStrictRequirements(null)`. It + improves performance in production environment as you should know that params always + pass the requirements (otherwise it would break your link anyway). + * There is no restriction on the route name anymore. So non-alphanumeric characters + are now also allowed. + * [BC BREAK] `RouteCompilerInterface::compile(Route $route)` was made static + (only relevant if you implemented your own RouteCompiler). + * Added possibility to generate relative paths and network paths in the UrlGenerator, e.g. + "../parent-file" and "//example.com/dir/file". The third parameter in + `UrlGeneratorInterface::generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)` + now accepts more values and you should use the constants defined in `UrlGeneratorInterface` for + claritiy. The old method calls with a Boolean parameter will continue to work because they + equal the signature using the constants. + +2.1.0 +----- + + * added RequestMatcherInterface + * added RequestContext::fromRequest() + * the UrlMatcher does not throw a \LogicException anymore when the required + scheme is not the current one + * added TraceableUrlMatcher + * added the possibility to define options, default values and requirements + for placeholders in prefix, including imported routes + * added RouterInterface::getRouteCollection + * [BC BREAK] the UrlMatcher urldecodes the route parameters only once, they + were decoded twice before. Note that the `urldecode()` calls have been + changed for a single `rawurldecode()` in order to support `+` for input + paths. + * added RouteCollection::getRoot method to retrieve the root of a + RouteCollection tree + * [BC BREAK] made RouteCollection::setParent private which could not have + been used anyway without creating inconsistencies + * [BC BREAK] RouteCollection::remove also removes a route from parent + collections (not only from its children) + * added ConfigurableRequirementsInterface that allows to disable exceptions + (and generate empty URLs instead) when generating a route with an invalid + parameter value diff --git a/vendor/symfony/routing/CompiledRoute.php b/vendor/symfony/routing/CompiledRoute.php new file mode 100644 index 00000000..f52e3aa0 --- /dev/null +++ b/vendor/symfony/routing/CompiledRoute.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * CompiledRoutes are returned by the RouteCompiler class. + * + * @author Fabien Potencier + */ +class CompiledRoute implements \Serializable +{ + private $variables; + private $tokens; + private $staticPrefix; + private $regex; + private $pathVariables; + private $hostVariables; + private $hostRegex; + private $hostTokens; + + /** + * Constructor. + * + * @param string $staticPrefix The static prefix of the compiled route + * @param string $regex The regular expression to use to match this route + * @param array $tokens An array of tokens to use to generate URL for this route + * @param array $pathVariables An array of path variables + * @param string|null $hostRegex Host regex + * @param array $hostTokens Host tokens + * @param array $hostVariables An array of host variables + * @param array $variables An array of variables (variables defined in the path and in the host patterns) + */ + public function __construct($staticPrefix, $regex, array $tokens, array $pathVariables, $hostRegex = null, array $hostTokens = array(), array $hostVariables = array(), array $variables = array()) + { + $this->staticPrefix = (string) $staticPrefix; + $this->regex = $regex; + $this->tokens = $tokens; + $this->pathVariables = $pathVariables; + $this->hostRegex = $hostRegex; + $this->hostTokens = $hostTokens; + $this->hostVariables = $hostVariables; + $this->variables = $variables; + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array( + 'vars' => $this->variables, + 'path_prefix' => $this->staticPrefix, + 'path_regex' => $this->regex, + 'path_tokens' => $this->tokens, + 'path_vars' => $this->pathVariables, + 'host_regex' => $this->hostRegex, + 'host_tokens' => $this->hostTokens, + 'host_vars' => $this->hostVariables, + )); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + if (\PHP_VERSION_ID >= 70000) { + $data = unserialize($serialized, array('allowed_classes' => false)); + } else { + $data = unserialize($serialized); + } + + $this->variables = $data['vars']; + $this->staticPrefix = $data['path_prefix']; + $this->regex = $data['path_regex']; + $this->tokens = $data['path_tokens']; + $this->pathVariables = $data['path_vars']; + $this->hostRegex = $data['host_regex']; + $this->hostTokens = $data['host_tokens']; + $this->hostVariables = $data['host_vars']; + } + + /** + * Returns the static prefix. + * + * @return string The static prefix + */ + public function getStaticPrefix() + { + return $this->staticPrefix; + } + + /** + * Returns the regex. + * + * @return string The regex + */ + public function getRegex() + { + return $this->regex; + } + + /** + * Returns the host regex. + * + * @return string|null The host regex or null + */ + public function getHostRegex() + { + return $this->hostRegex; + } + + /** + * Returns the tokens. + * + * @return array The tokens + */ + public function getTokens() + { + return $this->tokens; + } + + /** + * Returns the host tokens. + * + * @return array The tokens + */ + public function getHostTokens() + { + return $this->hostTokens; + } + + /** + * Returns the variables. + * + * @return array The variables + */ + public function getVariables() + { + return $this->variables; + } + + /** + * Returns the path variables. + * + * @return array The variables + */ + public function getPathVariables() + { + return $this->pathVariables; + } + + /** + * Returns the host variables. + * + * @return array The variables + */ + public function getHostVariables() + { + return $this->hostVariables; + } +} diff --git a/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php b/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php new file mode 100644 index 00000000..73a8f851 --- /dev/null +++ b/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\DependencyInjection; + +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; + +/** + * Adds tagged routing.loader services to routing.resolver service. + * + * @author Fabien Potencier + */ +class RoutingResolverPass implements CompilerPassInterface +{ + private $resolverServiceId; + private $loaderTag; + + public function __construct($resolverServiceId = 'routing.resolver', $loaderTag = 'routing.loader') + { + $this->resolverServiceId = $resolverServiceId; + $this->loaderTag = $loaderTag; + } + + public function process(ContainerBuilder $container) + { + if (false === $container->hasDefinition($this->resolverServiceId)) { + return; + } + + $definition = $container->getDefinition($this->resolverServiceId); + + foreach ($container->findTaggedServiceIds($this->loaderTag, true) as $id => $attributes) { + $definition->addMethodCall('addLoader', array(new Reference($id))); + } + } +} diff --git a/vendor/symfony/routing/Exception/ExceptionInterface.php b/vendor/symfony/routing/Exception/ExceptionInterface.php new file mode 100644 index 00000000..db763621 --- /dev/null +++ b/vendor/symfony/routing/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * ExceptionInterface. + * + * @author Alexandre Salomé + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/InvalidParameterException.php b/vendor/symfony/routing/Exception/InvalidParameterException.php new file mode 100644 index 00000000..94d841f4 --- /dev/null +++ b/vendor/symfony/routing/Exception/InvalidParameterException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a parameter is not valid. + * + * @author Alexandre Salomé + */ +class InvalidParameterException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/MethodNotAllowedException.php b/vendor/symfony/routing/Exception/MethodNotAllowedException.php new file mode 100644 index 00000000..f684c749 --- /dev/null +++ b/vendor/symfony/routing/Exception/MethodNotAllowedException.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * The resource was found but the request method is not allowed. + * + * This exception should trigger an HTTP 405 response in your application code. + * + * @author Kris Wallsmith + */ +class MethodNotAllowedException extends \RuntimeException implements ExceptionInterface +{ + /** + * @var array + */ + protected $allowedMethods = array(); + + public function __construct(array $allowedMethods, $message = null, $code = 0, \Exception $previous = null) + { + $this->allowedMethods = array_map('strtoupper', $allowedMethods); + + parent::__construct($message, $code, $previous); + } + + /** + * Gets the allowed HTTP methods. + * + * @return array + */ + public function getAllowedMethods() + { + return $this->allowedMethods; + } +} diff --git a/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php b/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php new file mode 100644 index 00000000..57f3a40d --- /dev/null +++ b/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a route cannot be generated because of missing + * mandatory parameters. + * + * @author Alexandre Salomé + */ +class MissingMandatoryParametersException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/ResourceNotFoundException.php b/vendor/symfony/routing/Exception/ResourceNotFoundException.php new file mode 100644 index 00000000..ccbca152 --- /dev/null +++ b/vendor/symfony/routing/Exception/ResourceNotFoundException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * The resource was not found. + * + * This exception should trigger an HTTP 404 response in your application code. + * + * @author Kris Wallsmith + */ +class ResourceNotFoundException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/RouteNotFoundException.php b/vendor/symfony/routing/Exception/RouteNotFoundException.php new file mode 100644 index 00000000..24ab0b44 --- /dev/null +++ b/vendor/symfony/routing/Exception/RouteNotFoundException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a route does not exist. + * + * @author Alexandre Salomé + */ +class RouteNotFoundException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php b/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php new file mode 100644 index 00000000..dc97b7e7 --- /dev/null +++ b/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +/** + * ConfigurableRequirementsInterface must be implemented by URL generators that + * can be configured whether an exception should be generated when the parameters + * do not match the requirements. It is also possible to disable the requirements + * check for URL generation completely. + * + * The possible configurations and use-cases: + * - setStrictRequirements(true): Throw an exception for mismatching requirements. This + * is mostly useful in development environment. + * - setStrictRequirements(false): Don't throw an exception but return null as URL for + * mismatching requirements and log the problem. Useful when you cannot control all + * params because they come from third party libs but don't want to have a 404 in + * production environment. It should log the mismatch so one can review it. + * - setStrictRequirements(null): Return the URL with the given parameters without + * checking the requirements at all. When generating a URL you should either trust + * your params or you validated them beforehand because otherwise it would break your + * link anyway. So in production environment you should know that params always pass + * the requirements. Thus this option allows to disable the check on URL generation for + * performance reasons (saving a preg_match for each requirement every time a URL is + * generated). + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +interface ConfigurableRequirementsInterface +{ + /** + * Enables or disables the exception on incorrect parameters. + * Passing null will deactivate the requirements check completely. + * + * @param bool|null $enabled + */ + public function setStrictRequirements($enabled); + + /** + * Returns whether to throw an exception on incorrect parameters. + * Null means the requirements check is deactivated completely. + * + * @return bool|null + */ + public function isStrictRequirements(); +} diff --git a/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php b/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php new file mode 100644 index 00000000..4739bd83 --- /dev/null +++ b/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * GeneratorDumper is the base class for all built-in generator dumpers. + * + * @author Fabien Potencier + */ +abstract class GeneratorDumper implements GeneratorDumperInterface +{ + /** + * @var RouteCollection + */ + private $routes; + + /** + * Constructor. + * + * @param RouteCollection $routes The RouteCollection to dump + */ + public function __construct(RouteCollection $routes) + { + $this->routes = $routes; + } + + /** + * {@inheritdoc} + */ + public function getRoutes() + { + return $this->routes; + } +} diff --git a/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php b/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php new file mode 100644 index 00000000..fed34723 --- /dev/null +++ b/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * GeneratorDumperInterface is the interface that all generator dumper classes must implement. + * + * @author Fabien Potencier + */ +interface GeneratorDumperInterface +{ + /** + * Dumps a set of routes to a string representation of executable code + * that can then be used to generate a URL of such a route. + * + * @param array $options An array of options + * + * @return string Executable code + */ + public function dump(array $options = array()); + + /** + * Gets the routes to dump. + * + * @return RouteCollection A RouteCollection instance + */ + public function getRoutes(); +} diff --git a/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php b/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php new file mode 100644 index 00000000..9fd35ea1 --- /dev/null +++ b/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +/** + * PhpGeneratorDumper creates a PHP class able to generate URLs for a given set of routes. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class PhpGeneratorDumper extends GeneratorDumper +{ + /** + * Dumps a set of routes to a PHP class. + * + * Available options: + * + * * class: The class name + * * base_class: The base class name + * + * @param array $options An array of options + * + * @return string A PHP class representing the generator class + */ + public function dump(array $options = array()) + { + $options = array_merge(array( + 'class' => 'ProjectUrlGenerator', + 'base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + ), $options); + + return <<context = \$context; + \$this->logger = \$logger; + if (null === self::\$declaredRoutes) { + self::\$declaredRoutes = {$this->generateDeclaredRoutes()}; + } + } + +{$this->generateGenerateMethod()} +} + +EOF; + } + + /** + * Generates PHP code representing an array of defined routes + * together with the routes properties (e.g. requirements). + * + * @return string PHP code + */ + private function generateDeclaredRoutes() + { + $routes = "array(\n"; + foreach ($this->getRoutes()->all() as $name => $route) { + $compiledRoute = $route->compile(); + + $properties = array(); + $properties[] = $compiledRoute->getVariables(); + $properties[] = $route->getDefaults(); + $properties[] = $route->getRequirements(); + $properties[] = $compiledRoute->getTokens(); + $properties[] = $compiledRoute->getHostTokens(); + $properties[] = $route->getSchemes(); + + $routes .= sprintf(" '%s' => %s,\n", $name, str_replace("\n", '', var_export($properties, true))); + } + $routes .= ' )'; + + return $routes; + } + + /** + * Generates PHP code representing the `generate` method that implements the UrlGeneratorInterface. + * + * @return string PHP code + */ + private function generateGenerateMethod() + { + return <<<'EOF' + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + if (!isset(self::$declaredRoutes[$name])) { + throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); + } + + list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = self::$declaredRoutes[$name]; + + return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes); + } +EOF; + } +} diff --git a/vendor/symfony/routing/Generator/UrlGenerator.php b/vendor/symfony/routing/Generator/UrlGenerator.php new file mode 100644 index 00000000..342161e1 --- /dev/null +++ b/vendor/symfony/routing/Generator/UrlGenerator.php @@ -0,0 +1,339 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Exception\InvalidParameterException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; +use Psr\Log\LoggerInterface; + +/** + * UrlGenerator can generate a URL or a path for any route in the RouteCollection + * based on the passed parameters. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInterface +{ + /** + * @var RouteCollection + */ + protected $routes; + + /** + * @var RequestContext + */ + protected $context; + + /** + * @var bool|null + */ + protected $strictRequirements = true; + + /** + * @var LoggerInterface|null + */ + protected $logger; + + /** + * This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL. + * + * PHP's rawurlencode() encodes all chars except "a-zA-Z0-9-._~" according to RFC 3986. But we want to allow some chars + * to be used in their literal form (reasons below). Other chars inside the path must of course be encoded, e.g. + * "?" and "#" (would be interpreted wrongly as query and fragment identifier), + * "'" and """ (are used as delimiters in HTML). + */ + protected $decodedChars = array( + // the slash can be used to designate a hierarchical structure and we want allow using it with this meaning + // some webservers don't allow the slash in encoded form in the path for security reasons anyway + // see http://stackoverflow.com/questions/4069002/http-400-if-2f-part-of-get-url-in-jboss + '%2F' => '/', + // the following chars are general delimiters in the URI specification but have only special meaning in the authority component + // so they can safely be used in the path in unencoded form + '%40' => '@', + '%3A' => ':', + // these chars are only sub-delimiters that have no predefined meaning and can therefore be used literally + // so URI producing applications can use these chars to delimit subcomponents in a path segment without being encoded for better readability + '%3B' => ';', + '%2C' => ',', + '%3D' => '=', + '%2B' => '+', + '%21' => '!', + '%2A' => '*', + '%7C' => '|', + ); + + /** + * Constructor. + * + * @param RouteCollection $routes A RouteCollection instance + * @param RequestContext $context The context + * @param LoggerInterface|null $logger A logger instance + */ + public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null) + { + $this->routes = $routes; + $this->context = $context; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * {@inheritdoc} + */ + public function setStrictRequirements($enabled) + { + $this->strictRequirements = null === $enabled ? null : (bool) $enabled; + } + + /** + * {@inheritdoc} + */ + public function isStrictRequirements() + { + return $this->strictRequirements; + } + + /** + * {@inheritdoc} + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + if (null === $route = $this->routes->get($name)) { + throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); + } + + // the Route has a cache of its own and is not recompiled as long as it does not get modified + $compiledRoute = $route->compile(); + + return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes()); + } + + /** + * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route + * @throws InvalidParameterException When a parameter value for a placeholder is not correct because + * it does not match the requirement + */ + protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, array $requiredSchemes = array()) + { + $variables = array_flip($variables); + $mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters); + + // all params must be given + if ($diff = array_diff_key($variables, $mergedParams)) { + throw new MissingMandatoryParametersException(sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', implode('", "', array_keys($diff)), $name)); + } + + $url = ''; + $optional = true; + $message = 'Parameter "{parameter}" for route "{route}" must match "{expected}" ("{given}" given) to generate a corresponding URL.'; + foreach ($tokens as $token) { + if ('variable' === $token[0]) { + if (!$optional || !array_key_exists($token[3], $defaults) || null !== $mergedParams[$token[3]] && (string) $mergedParams[$token[3]] !== (string) $defaults[$token[3]]) { + // check requirement + if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#'.(empty($token[4]) ? '' : 'u'), $mergedParams[$token[3]])) { + if ($this->strictRequirements) { + throw new InvalidParameterException(strtr($message, array('{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]]))); + } + + if ($this->logger) { + $this->logger->error($message, array('parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]])); + } + + return; + } + + $url = $token[1].$mergedParams[$token[3]].$url; + $optional = false; + } + } else { + // static text + $url = $token[1].$url; + $optional = false; + } + } + + if ('' === $url) { + $url = '/'; + } + + // the contexts base URL is already encoded (see Symfony\Component\HttpFoundation\Request) + $url = strtr(rawurlencode($url), $this->decodedChars); + + // the path segments "." and ".." are interpreted as relative reference when resolving a URI; see http://tools.ietf.org/html/rfc3986#section-3.3 + // so we need to encode them as they are not used for this purpose here + // otherwise we would generate a URI that, when followed by a user agent (e.g. browser), does not match this route + $url = strtr($url, array('/../' => '/%2E%2E/', '/./' => '/%2E/')); + if ('/..' === substr($url, -3)) { + $url = substr($url, 0, -2).'%2E%2E'; + } elseif ('/.' === substr($url, -2)) { + $url = substr($url, 0, -1).'%2E'; + } + + $schemeAuthority = ''; + if ($host = $this->context->getHost()) { + $scheme = $this->context->getScheme(); + + if ($requiredSchemes) { + if (!in_array($scheme, $requiredSchemes, true)) { + $referenceType = self::ABSOLUTE_URL; + $scheme = current($requiredSchemes); + } + } + + if ($hostTokens) { + $routeHost = ''; + foreach ($hostTokens as $token) { + if ('variable' === $token[0]) { + if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#i'.(empty($token[4]) ? '' : 'u'), $mergedParams[$token[3]])) { + if ($this->strictRequirements) { + throw new InvalidParameterException(strtr($message, array('{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]]))); + } + + if ($this->logger) { + $this->logger->error($message, array('parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]])); + } + + return; + } + + $routeHost = $token[1].$mergedParams[$token[3]].$routeHost; + } else { + $routeHost = $token[1].$routeHost; + } + } + + if ($routeHost !== $host) { + $host = $routeHost; + if (self::ABSOLUTE_URL !== $referenceType) { + $referenceType = self::NETWORK_PATH; + } + } + } + + if (self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) { + $port = ''; + if ('http' === $scheme && 80 != $this->context->getHttpPort()) { + $port = ':'.$this->context->getHttpPort(); + } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) { + $port = ':'.$this->context->getHttpsPort(); + } + + $schemeAuthority = self::NETWORK_PATH === $referenceType ? '//' : "$scheme://"; + $schemeAuthority .= $host.$port; + } + } + + if (self::RELATIVE_PATH === $referenceType) { + $url = self::getRelativePath($this->context->getPathInfo(), $url); + } else { + $url = $schemeAuthority.$this->context->getBaseUrl().$url; + } + + // add a query string if needed + $extra = array_udiff_assoc(array_diff_key($parameters, $variables), $defaults, function ($a, $b) { + return $a == $b ? 0 : 1; + }); + + // extract fragment + $fragment = ''; + if (isset($defaults['_fragment'])) { + $fragment = $defaults['_fragment']; + } + + if (isset($extra['_fragment'])) { + $fragment = $extra['_fragment']; + unset($extra['_fragment']); + } + + if ($extra && $query = http_build_query($extra, '', '&', PHP_QUERY_RFC3986)) { + // "/" and "?" can be left decoded for better user experience, see + // http://tools.ietf.org/html/rfc3986#section-3.4 + $url .= '?'.strtr($query, array('%2F' => '/')); + } + + if ('' !== $fragment) { + $url .= '#'.strtr(rawurlencode($fragment), array('%2F' => '/', '%3F' => '?')); + } + + return $url; + } + + /** + * Returns the target path as relative reference from the base path. + * + * Only the URIs path component (no schema, host etc.) is relevant and must be given, starting with a slash. + * Both paths must be absolute and not contain relative parts. + * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. + * Furthermore, they can be used to reduce the link size in documents. + * + * Example target paths, given a base path of "/a/b/c/d": + * - "/a/b/c/d" -> "" + * - "/a/b/c/" -> "./" + * - "/a/b/" -> "../" + * - "/a/b/c/other" -> "other" + * - "/a/x/y" -> "../../x/y" + * + * @param string $basePath The base path + * @param string $targetPath The target path + * + * @return string The relative target path + */ + public static function getRelativePath($basePath, $targetPath) + { + if ($basePath === $targetPath) { + return ''; + } + + $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); + $targetDirs = explode('/', isset($targetPath[0]) && '/' === $targetPath[0] ? substr($targetPath, 1) : $targetPath); + array_pop($sourceDirs); + $targetFile = array_pop($targetDirs); + + foreach ($sourceDirs as $i => $dir) { + if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { + unset($sourceDirs[$i], $targetDirs[$i]); + } else { + break; + } + } + + $targetDirs[] = $targetFile; + $path = str_repeat('../', count($sourceDirs)).implode('/', $targetDirs); + + // A reference to the same base directory or an empty subdirectory must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name + // (see http://tools.ietf.org/html/rfc3986#section-4.2). + return '' === $path || '/' === $path[0] + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } +} diff --git a/vendor/symfony/routing/Generator/UrlGeneratorInterface.php b/vendor/symfony/routing/Generator/UrlGeneratorInterface.php new file mode 100644 index 00000000..d6e7938e --- /dev/null +++ b/vendor/symfony/routing/Generator/UrlGeneratorInterface.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +use Symfony\Component\Routing\Exception\InvalidParameterException; +use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\RequestContextAwareInterface; + +/** + * UrlGeneratorInterface is the interface that all URL generator classes must implement. + * + * The constants in this interface define the different types of resource references that + * are declared in RFC 3986: http://tools.ietf.org/html/rfc3986 + * We are using the term "URL" instead of "URI" as this is more common in web applications + * and we do not need to distinguish them as the difference is mostly semantical and + * less technical. Generating URIs, i.e. representation-independent resource identifiers, + * is also possible. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +interface UrlGeneratorInterface extends RequestContextAwareInterface +{ + /** + * Generates an absolute URL, e.g. "http://example.com/dir/file". + */ + const ABSOLUTE_URL = 0; + + /** + * Generates an absolute path, e.g. "/dir/file". + */ + const ABSOLUTE_PATH = 1; + + /** + * Generates a relative path based on the current request path, e.g. "../parent-file". + * + * @see UrlGenerator::getRelativePath() + */ + const RELATIVE_PATH = 2; + + /** + * Generates a network path, e.g. "//example.com/dir/file". + * Such reference reuses the current scheme but specifies the host. + */ + const NETWORK_PATH = 3; + + /** + * Generates a URL or path for a specific route based on the given parameters. + * + * Parameters that reference placeholders in the route pattern will substitute them in the + * path or host. Extra params are added as query string to the URL. + * + * When the passed reference type cannot be generated for the route because it requires a different + * host or scheme than the current one, the method will return a more comprehensive reference + * that includes the required params. For example, when you call this method with $referenceType = ABSOLUTE_PATH + * but the route requires the https scheme whereas the current scheme is http, it will instead return an + * ABSOLUTE_URL with the https scheme and the current host. This makes sure the generated URL matches + * the route in any case. + * + * If there is no route with the given name, the generator must throw the RouteNotFoundException. + * + * The special parameter _fragment will be used as the document fragment suffixed to the final URL. + * + * @param string $name The name of the route + * @param mixed $parameters An array of parameters + * @param int $referenceType The type of reference to be generated (one of the constants) + * + * @return string The generated URL + * + * @throws RouteNotFoundException If the named route doesn't exist + * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route + * @throws InvalidParameterException When a parameter value for a placeholder is not correct because + * it does not match the requirement + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH); +} diff --git a/vendor/symfony/routing/LICENSE b/vendor/symfony/routing/LICENSE new file mode 100644 index 00000000..17d16a13 --- /dev/null +++ b/vendor/symfony/routing/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2017 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/routing/Loader/AnnotationClassLoader.php b/vendor/symfony/routing/Loader/AnnotationClassLoader.php new file mode 100644 index 00000000..c91a7f7d --- /dev/null +++ b/vendor/symfony/routing/Loader/AnnotationClassLoader.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Doctrine\Common\Annotations\Reader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Loader\LoaderResolverInterface; + +/** + * AnnotationClassLoader loads routing information from a PHP class and its methods. + * + * You need to define an implementation for the getRouteDefaults() method. Most of the + * time, this method should define some PHP callable to be called for the route + * (a controller in MVC speak). + * + * The @Route annotation can be set on the class (for global parameters), + * and on each method. + * + * The @Route annotation main value is the route path. The annotation also + * recognizes several parameters: requirements, options, defaults, schemes, + * methods, host, and name. The name parameter is mandatory. + * Here is an example of how you should be able to use it: + * + * /** + * * @Route("/Blog") + * * / + * class Blog + * { + * /** + * * @Route("/", name="blog_index") + * * / + * public function index() + * { + * } + * + * /** + * * @Route("/{id}", name="blog_post", requirements = {"id" = "\d+"}) + * * / + * public function show() + * { + * } + * } + * + * @author Fabien Potencier + */ +abstract class AnnotationClassLoader implements LoaderInterface +{ + /** + * @var Reader + */ + protected $reader; + + /** + * @var string + */ + protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route'; + + /** + * @var int + */ + protected $defaultRouteIndex = 0; + + /** + * Constructor. + * + * @param Reader $reader + */ + public function __construct(Reader $reader) + { + $this->reader = $reader; + } + + /** + * Sets the annotation class to read route properties from. + * + * @param string $class A fully-qualified class name + */ + public function setRouteAnnotationClass($class) + { + $this->routeAnnotationClass = $class; + } + + /** + * Loads from annotations from a class. + * + * @param string $class A class name + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When route can't be parsed + */ + public function load($class, $type = null) + { + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + } + + $class = new \ReflectionClass($class); + if ($class->isAbstract()) { + throw new \InvalidArgumentException(sprintf('Annotations from class "%s" cannot be read as it is abstract.', $class->getName())); + } + + $globals = $this->getGlobals($class); + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($class->getFileName())); + + foreach ($class->getMethods() as $method) { + $this->defaultRouteIndex = 0; + foreach ($this->reader->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $this->routeAnnotationClass) { + $this->addRoute($collection, $annot, $globals, $class, $method); + } + } + } + + if (0 === $collection->count() && $class->hasMethod('__invoke') && $annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) { + $globals['path'] = ''; + $this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke')); + } + + return $collection; + } + + protected function addRoute(RouteCollection $collection, $annot, $globals, \ReflectionClass $class, \ReflectionMethod $method) + { + $name = $annot->getName(); + if (null === $name) { + $name = $this->getDefaultRouteName($class, $method); + } + + $defaults = array_replace($globals['defaults'], $annot->getDefaults()); + foreach ($method->getParameters() as $param) { + if (false !== strpos($globals['path'].$annot->getPath(), sprintf('{%s}', $param->getName())) && !isset($defaults[$param->getName()]) && $param->isDefaultValueAvailable()) { + $defaults[$param->getName()] = $param->getDefaultValue(); + } + } + $requirements = array_replace($globals['requirements'], $annot->getRequirements()); + $options = array_replace($globals['options'], $annot->getOptions()); + $schemes = array_merge($globals['schemes'], $annot->getSchemes()); + $methods = array_merge($globals['methods'], $annot->getMethods()); + + $host = $annot->getHost(); + if (null === $host) { + $host = $globals['host']; + } + + $condition = $annot->getCondition(); + if (null === $condition) { + $condition = $globals['condition']; + } + + $route = $this->createRoute($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + + $this->configureRoute($route, $class, $method, $annot); + + $collection->add($name, $route); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || 'annotation' === $type); + } + + /** + * {@inheritdoc} + */ + public function setResolver(LoaderResolverInterface $resolver) + { + } + + /** + * {@inheritdoc} + */ + public function getResolver() + { + } + + /** + * Gets the default route name for a class method. + * + * @param \ReflectionClass $class + * @param \ReflectionMethod $method + * + * @return string + */ + protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method) + { + $name = strtolower(str_replace('\\', '_', $class->name).'_'.$method->name); + if ($this->defaultRouteIndex > 0) { + $name .= '_'.$this->defaultRouteIndex; + } + ++$this->defaultRouteIndex; + + return $name; + } + + protected function getGlobals(\ReflectionClass $class) + { + $globals = array( + 'path' => '', + 'requirements' => array(), + 'options' => array(), + 'defaults' => array(), + 'schemes' => array(), + 'methods' => array(), + 'host' => '', + 'condition' => '', + ); + + if ($annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) { + if (null !== $annot->getPath()) { + $globals['path'] = $annot->getPath(); + } + + if (null !== $annot->getRequirements()) { + $globals['requirements'] = $annot->getRequirements(); + } + + if (null !== $annot->getOptions()) { + $globals['options'] = $annot->getOptions(); + } + + if (null !== $annot->getDefaults()) { + $globals['defaults'] = $annot->getDefaults(); + } + + if (null !== $annot->getSchemes()) { + $globals['schemes'] = $annot->getSchemes(); + } + + if (null !== $annot->getMethods()) { + $globals['methods'] = $annot->getMethods(); + } + + if (null !== $annot->getHost()) { + $globals['host'] = $annot->getHost(); + } + + if (null !== $annot->getCondition()) { + $globals['condition'] = $annot->getCondition(); + } + } + + return $globals; + } + + protected function createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition) + { + return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + } + + abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot); +} diff --git a/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php b/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php new file mode 100644 index 00000000..616d01ef --- /dev/null +++ b/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Config\Resource\DirectoryResource; + +/** + * AnnotationDirectoryLoader loads routing information from annotations set + * on PHP classes and methods. + * + * @author Fabien Potencier + */ +class AnnotationDirectoryLoader extends AnnotationFileLoader +{ + /** + * Loads from annotations from a directory. + * + * @param string $path A directory path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When the directory does not exist or its routes cannot be parsed + */ + public function load($path, $type = null) + { + $dir = $this->locator->locate($path); + + $collection = new RouteCollection(); + $collection->addResource(new DirectoryResource($dir, '/\.php$/')); + $files = iterator_to_array(new \RecursiveIteratorIterator( + new \RecursiveCallbackFilterIterator( + new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + function (\SplFileInfo $current) { + return '.' !== substr($current->getBasename(), 0, 1); + } + ), + \RecursiveIteratorIterator::LEAVES_ONLY + )); + usort($files, function (\SplFileInfo $a, \SplFileInfo $b) { + return (string) $a > (string) $b ? 1 : -1; + }); + + foreach ($files as $file) { + if (!$file->isFile() || '.php' !== substr($file->getFilename(), -4)) { + continue; + } + + if ($class = $this->findClass($file)) { + $refl = new \ReflectionClass($class); + if ($refl->isAbstract()) { + continue; + } + + $collection->addCollection($this->loader->load($class, $type)); + } + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + if (!is_string($resource)) { + return false; + } + + try { + $path = $this->locator->locate($resource); + } catch (\Exception $e) { + return false; + } + + return is_dir($path) && (!$type || 'annotation' === $type); + } +} diff --git a/vendor/symfony/routing/Loader/AnnotationFileLoader.php b/vendor/symfony/routing/Loader/AnnotationFileLoader.php new file mode 100644 index 00000000..22edc867 --- /dev/null +++ b/vendor/symfony/routing/Loader/AnnotationFileLoader.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\FileLocatorInterface; + +/** + * AnnotationFileLoader loads routing information from annotations set + * on a PHP class and its methods. + * + * @author Fabien Potencier + */ +class AnnotationFileLoader extends FileLoader +{ + protected $loader; + + /** + * Constructor. + * + * @param FileLocatorInterface $locator A FileLocator instance + * @param AnnotationClassLoader $loader An AnnotationClassLoader instance + * + * @throws \RuntimeException + */ + public function __construct(FileLocatorInterface $locator, AnnotationClassLoader $loader) + { + if (!function_exists('token_get_all')) { + throw new \RuntimeException('The Tokenizer extension is required for the routing annotation loaders.'); + } + + parent::__construct($locator); + + $this->loader = $loader; + } + + /** + * Loads from annotations from a file. + * + * @param string $file A PHP file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + $collection = new RouteCollection(); + if ($class = $this->findClass($path)) { + $collection->addResource(new FileResource($path)); + $collection->addCollection($this->loader->load($class, $type)); + } + if (\PHP_VERSION_ID >= 70000) { + // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 + gc_mem_caches(); + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'annotation' === $type); + } + + /** + * Returns the full class name for the first class in the file. + * + * @param string $file A PHP file path + * + * @return string|false Full class name if found, false otherwise + */ + protected function findClass($file) + { + $class = false; + $namespace = false; + $tokens = token_get_all(file_get_contents($file)); + + if (1 === count($tokens) && T_INLINE_HTML === $tokens[0][0]) { + throw new \InvalidArgumentException(sprintf('The file "%s" does not contain PHP code. Did you forgot to add the " 0; --$j) { + if (!isset($tokens[$j][1])) { + break; + } + + if (T_DOUBLE_COLON === $tokens[$j][0]) { + $isClassConstant = true; + break; + } elseif (!in_array($tokens[$j][0], array(T_WHITESPACE, T_DOC_COMMENT, T_COMMENT))) { + break; + } + } + + if (!$isClassConstant) { + $class = true; + } + } + + if (T_NAMESPACE === $token[0]) { + $namespace = true; + } + } + + return false; + } +} diff --git a/vendor/symfony/routing/Loader/ClosureLoader.php b/vendor/symfony/routing/Loader/ClosureLoader.php new file mode 100644 index 00000000..5df9f6ae --- /dev/null +++ b/vendor/symfony/routing/Loader/ClosureLoader.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Routing\RouteCollection; + +/** + * ClosureLoader loads routes from a PHP closure. + * + * The Closure must return a RouteCollection instance. + * + * @author Fabien Potencier + */ +class ClosureLoader extends Loader +{ + /** + * Loads a Closure. + * + * @param \Closure $closure A Closure + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + */ + public function load($closure, $type = null) + { + return $closure(); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return $resource instanceof \Closure && (!$type || 'closure' === $type); + } +} diff --git a/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php b/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php new file mode 100644 index 00000000..6c162163 --- /dev/null +++ b/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\DependencyInjection; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Routing\Loader\ObjectRouteLoader; + +/** + * A route loader that executes a service to load the routes. + * + * This depends on the DependencyInjection component. + * + * @author Ryan Weaver + */ +class ServiceRouterLoader extends ObjectRouteLoader +{ + /** + * @var ContainerInterface + */ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + protected function getServiceObject($id) + { + return $this->container->get($id); + } +} diff --git a/vendor/symfony/routing/Loader/DirectoryLoader.php b/vendor/symfony/routing/Loader/DirectoryLoader.php new file mode 100644 index 00000000..4bb5b31b --- /dev/null +++ b/vendor/symfony/routing/Loader/DirectoryLoader.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Config\Resource\DirectoryResource; + +class DirectoryLoader extends FileLoader +{ + /** + * {@inheritdoc} + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + $collection = new RouteCollection(); + $collection->addResource(new DirectoryResource($path)); + + foreach (scandir($path) as $dir) { + if ('.' !== $dir[0]) { + $this->setCurrentDir($path); + $subPath = $path.'/'.$dir; + $subType = null; + + if (is_dir($subPath)) { + $subPath .= '/'; + $subType = 'directory'; + } + + $subCollection = $this->import($subPath, $subType, false, $path); + $collection->addCollection($subCollection); + } + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + // only when type is forced to directory, not to conflict with AnnotationLoader + + return 'directory' === $type; + } +} diff --git a/vendor/symfony/routing/Loader/ObjectRouteLoader.php b/vendor/symfony/routing/Loader/ObjectRouteLoader.php new file mode 100644 index 00000000..4d79b6cf --- /dev/null +++ b/vendor/symfony/routing/Loader/ObjectRouteLoader.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\RouteCollection; + +/** + * A route loader that calls a method on an object to load the routes. + * + * @author Ryan Weaver + */ +abstract class ObjectRouteLoader extends Loader +{ + /** + * Returns the object that the method will be called on to load routes. + * + * For example, if your application uses a service container, + * the $id may be a service id. + * + * @param string $id + * + * @return object + */ + abstract protected function getServiceObject($id); + + /** + * Calls the service that will load the routes. + * + * @param mixed $resource Some value that will resolve to a callable + * @param string|null $type The resource type + * + * @return RouteCollection + */ + public function load($resource, $type = null) + { + $parts = explode(':', $resource); + if (count($parts) != 2) { + throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the "service" route loader: use the format "service_name:methodName"', $resource)); + } + + $serviceString = $parts[0]; + $method = $parts[1]; + + $loaderObject = $this->getServiceObject($serviceString); + + if (!is_object($loaderObject)) { + throw new \LogicException(sprintf('%s:getServiceObject() must return an object: %s returned', get_class($this), gettype($loaderObject))); + } + + if (!method_exists($loaderObject, $method)) { + throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s"', $method, get_class($loaderObject), $resource)); + } + + $routeCollection = call_user_func(array($loaderObject, $method), $this); + + if (!$routeCollection instanceof RouteCollection) { + $type = is_object($routeCollection) ? get_class($routeCollection) : gettype($routeCollection); + + throw new \LogicException(sprintf('The %s::%s method must return a RouteCollection: %s returned', get_class($loaderObject), $method, $type)); + } + + // make the service file tracked so that if it changes, the cache rebuilds + $this->addClassResource(new \ReflectionClass($loaderObject), $routeCollection); + + return $routeCollection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return 'service' === $type; + } + + private function addClassResource(\ReflectionClass $class, RouteCollection $collection) + { + do { + if (is_file($class->getFileName())) { + $collection->addResource(new FileResource($class->getFileName())); + } + } while ($class = $class->getParentClass()); + } +} diff --git a/vendor/symfony/routing/Loader/PhpFileLoader.php b/vendor/symfony/routing/Loader/PhpFileLoader.php new file mode 100644 index 00000000..b4ba5fbc --- /dev/null +++ b/vendor/symfony/routing/Loader/PhpFileLoader.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\RouteCollection; + +/** + * PhpFileLoader loads routes from a PHP file. + * + * The file must return a RouteCollection instance. + * + * @author Fabien Potencier + */ +class PhpFileLoader extends FileLoader +{ + /** + * Loads a PHP file. + * + * @param string $file A PHP file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + $this->setCurrentDir(dirname($path)); + + $collection = self::includeFile($path, $this); + $collection->addResource(new FileResource($path)); + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'php' === $type); + } + + /** + * Safe include. Used for scope isolation. + * + * @param string $file File to include + * @param PhpFileLoader $loader the loader variable is exposed to the included file below + * + * @return RouteCollection + */ + private static function includeFile($file, PhpFileLoader $loader) + { + return include $file; + } +} diff --git a/vendor/symfony/routing/Loader/XmlFileLoader.php b/vendor/symfony/routing/Loader/XmlFileLoader.php new file mode 100644 index 00000000..8a6e8ce3 --- /dev/null +++ b/vendor/symfony/routing/Loader/XmlFileLoader.php @@ -0,0 +1,342 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Util\XmlUtils; + +/** + * XmlFileLoader loads XML routing files. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class XmlFileLoader extends FileLoader +{ + const NAMESPACE_URI = 'http://symfony.com/schema/routing'; + const SCHEME_PATH = '/schema/routing/routing-1.0.xsd'; + + /** + * Loads an XML file. + * + * @param string $file An XML file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When the file cannot be loaded or when the XML cannot be + * parsed because it does not validate against the scheme. + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + $xml = $this->loadFile($path); + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($path)); + + // process routes and imports + foreach ($xml->documentElement->childNodes as $node) { + if (!$node instanceof \DOMElement) { + continue; + } + + $this->parseNode($collection, $node, $path, $file); + } + + return $collection; + } + + /** + * Parses a node from a loaded XML file. + * + * @param RouteCollection $collection Collection to associate with the node + * @param \DOMElement $node Element to parse + * @param string $path Full path of the XML file being processed + * @param string $file Loaded file name + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseNode(RouteCollection $collection, \DOMElement $node, $path, $file) + { + if (self::NAMESPACE_URI !== $node->namespaceURI) { + return; + } + + switch ($node->localName) { + case 'route': + $this->parseRoute($collection, $node, $path); + break; + case 'import': + $this->parseImport($collection, $node, $path, $file); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path)); + } + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && 'xml' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'xml' === $type); + } + + /** + * Parses a route and adds it to the RouteCollection. + * + * @param RouteCollection $collection RouteCollection instance + * @param \DOMElement $node Element to parse that represents a Route + * @param string $path Full path of the XML file being processed + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseRoute(RouteCollection $collection, \DOMElement $node, $path) + { + if ('' === ($id = $node->getAttribute('id')) || !$node->hasAttribute('path')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have an "id" and a "path" attribute.', $path)); + } + + $schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY); + $methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY); + + list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path); + + $route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition); + $collection->add($id, $route); + } + + /** + * Parses an import and adds the routes in the resource to the RouteCollection. + * + * @param RouteCollection $collection RouteCollection instance + * @param \DOMElement $node Element to parse that represents a Route + * @param string $path Full path of the XML file being processed + * @param string $file Loaded file name + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseImport(RouteCollection $collection, \DOMElement $node, $path, $file) + { + if ('' === $resource = $node->getAttribute('resource')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "resource" attribute.', $path)); + } + + $type = $node->getAttribute('type'); + $prefix = $node->getAttribute('prefix'); + $host = $node->hasAttribute('host') ? $node->getAttribute('host') : null; + $schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null; + $methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null; + + list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path); + + $this->setCurrentDir(dirname($path)); + + $subCollection = $this->import($resource, ('' !== $type ? $type : null), false, $file); + /* @var $subCollection RouteCollection */ + $subCollection->addPrefix($prefix); + if (null !== $host) { + $subCollection->setHost($host); + } + if (null !== $condition) { + $subCollection->setCondition($condition); + } + if (null !== $schemes) { + $subCollection->setSchemes($schemes); + } + if (null !== $methods) { + $subCollection->setMethods($methods); + } + $subCollection->addDefaults($defaults); + $subCollection->addRequirements($requirements); + $subCollection->addOptions($options); + + $collection->addCollection($subCollection); + } + + /** + * Loads an XML file. + * + * @param string $file An XML file path + * + * @return \DOMDocument + * + * @throws \InvalidArgumentException When loading of XML file fails because of syntax errors + * or when the XML structure is not as expected by the scheme - + * see validate() + */ + protected function loadFile($file) + { + return XmlUtils::loadFile($file, __DIR__.static::SCHEME_PATH); + } + + /** + * Parses the config elements (default, requirement, option). + * + * @param \DOMElement $node Element to parse that contains the configs + * @param string $path Full path of the XML file being processed + * + * @return array An array with the defaults as first item, requirements as second and options as third + * + * @throws \InvalidArgumentException When the XML is invalid + */ + private function parseConfigs(\DOMElement $node, $path) + { + $defaults = array(); + $requirements = array(); + $options = array(); + $condition = null; + + foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) { + if ($node !== $n->parentNode) { + continue; + } + + switch ($n->localName) { + case 'default': + if ($this->isElementValueNull($n)) { + $defaults[$n->getAttribute('key')] = null; + } else { + $defaults[$n->getAttribute('key')] = $this->parseDefaultsConfig($n, $path); + } + + break; + case 'requirement': + $requirements[$n->getAttribute('key')] = trim($n->textContent); + break; + case 'option': + $options[$n->getAttribute('key')] = trim($n->textContent); + break; + case 'condition': + $condition = trim($n->textContent); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement", "option" or "condition".', $n->localName, $path)); + } + } + + return array($defaults, $requirements, $options, $condition); + } + + /** + * Parses the "default" elements. + * + * @param \DOMElement $element The "default" element to parse + * @param string $path Full path of the XML file being processed + * + * @return array|bool|float|int|string|null The parsed value of the "default" element + */ + private function parseDefaultsConfig(\DOMElement $element, $path) + { + if ($this->isElementValueNull($element)) { + return; + } + + // Check for existing element nodes in the default element. There can + // only be a single element inside a default element. So this element + // (if one was found) can safely be returned. + foreach ($element->childNodes as $child) { + if (!$child instanceof \DOMElement) { + continue; + } + + if (self::NAMESPACE_URI !== $child->namespaceURI) { + continue; + } + + return $this->parseDefaultNode($child, $path); + } + + // If the default element doesn't contain a nested "bool", "int", "float", + // "string", "list", or "map" element, the element contents will be treated + // as the string value of the associated default option. + return trim($element->textContent); + } + + /** + * Recursively parses the value of a "default" element. + * + * @param \DOMElement $node The node value + * @param string $path Full path of the XML file being processed + * + * @return array|bool|float|int|string The parsed value + * + * @throws \InvalidArgumentException when the XML is invalid + */ + private function parseDefaultNode(\DOMElement $node, $path) + { + if ($this->isElementValueNull($node)) { + return; + } + + switch ($node->localName) { + case 'bool': + return 'true' === trim($node->nodeValue) || '1' === trim($node->nodeValue); + case 'int': + return (int) trim($node->nodeValue); + case 'float': + return (float) trim($node->nodeValue); + case 'string': + return trim($node->nodeValue); + case 'list': + $list = array(); + + foreach ($node->childNodes as $element) { + if (!$element instanceof \DOMElement) { + continue; + } + + if (self::NAMESPACE_URI !== $element->namespaceURI) { + continue; + } + + $list[] = $this->parseDefaultNode($element, $path); + } + + return $list; + case 'map': + $map = array(); + + foreach ($node->childNodes as $element) { + if (!$element instanceof \DOMElement) { + continue; + } + + if (self::NAMESPACE_URI !== $element->namespaceURI) { + continue; + } + + $map[$element->getAttribute('key')] = $this->parseDefaultNode($element, $path); + } + + return $map; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "bool", "int", "float", "string", "list", or "map".', $node->localName, $path)); + } + } + + private function isElementValueNull(\DOMElement $element) + { + $namespaceUri = 'http://www.w3.org/2001/XMLSchema-instance'; + + if (!$element->hasAttributeNS($namespaceUri, 'nil')) { + return false; + } + + return 'true' === $element->getAttributeNS($namespaceUri, 'nil') || '1' === $element->getAttributeNS($namespaceUri, 'nil'); + } +} diff --git a/vendor/symfony/routing/Loader/YamlFileLoader.php b/vendor/symfony/routing/Loader/YamlFileLoader.php new file mode 100644 index 00000000..7ae5e84d --- /dev/null +++ b/vendor/symfony/routing/Loader/YamlFileLoader.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser as YamlParser; +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Yaml\Yaml; + +/** + * YamlFileLoader loads Yaml routing files. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class YamlFileLoader extends FileLoader +{ + private static $availableKeys = array( + 'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', + ); + private $yamlParser; + + /** + * Loads a Yaml file. + * + * @param string $file A Yaml file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + if (!stream_is_local($path)) { + throw new \InvalidArgumentException(sprintf('This is not a local file "%s".', $path)); + } + + if (!file_exists($path)) { + throw new \InvalidArgumentException(sprintf('File "%s" not found.', $path)); + } + + if (null === $this->yamlParser) { + $this->yamlParser = new YamlParser(); + } + + try { + $parsedConfig = $this->yamlParser->parse(file_get_contents($path), Yaml::PARSE_KEYS_AS_STRINGS); + } catch (ParseException $e) { + throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $path), 0, $e); + } + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($path)); + + // empty file + if (null === $parsedConfig) { + return $collection; + } + + // not an array + if (!is_array($parsedConfig)) { + throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $path)); + } + + foreach ($parsedConfig as $name => $config) { + $this->validate($config, $name, $path); + + if (isset($config['resource'])) { + $this->parseImport($collection, $config, $path, $file); + } else { + $this->parseRoute($collection, $name, $config, $path); + } + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && in_array(pathinfo($resource, PATHINFO_EXTENSION), array('yml', 'yaml'), true) && (!$type || 'yaml' === $type); + } + + /** + * Parses a route and adds it to the RouteCollection. + * + * @param RouteCollection $collection A RouteCollection instance + * @param string $name Route name + * @param array $config Route definition + * @param string $path Full path of the YAML file being processed + */ + protected function parseRoute(RouteCollection $collection, $name, array $config, $path) + { + $defaults = isset($config['defaults']) ? $config['defaults'] : array(); + $requirements = isset($config['requirements']) ? $config['requirements'] : array(); + $options = isset($config['options']) ? $config['options'] : array(); + $host = isset($config['host']) ? $config['host'] : ''; + $schemes = isset($config['schemes']) ? $config['schemes'] : array(); + $methods = isset($config['methods']) ? $config['methods'] : array(); + $condition = isset($config['condition']) ? $config['condition'] : null; + + $route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + + $collection->add($name, $route); + } + + /** + * Parses an import and adds the routes in the resource to the RouteCollection. + * + * @param RouteCollection $collection A RouteCollection instance + * @param array $config Route definition + * @param string $path Full path of the YAML file being processed + * @param string $file Loaded file name + */ + protected function parseImport(RouteCollection $collection, array $config, $path, $file) + { + $type = isset($config['type']) ? $config['type'] : null; + $prefix = isset($config['prefix']) ? $config['prefix'] : ''; + $defaults = isset($config['defaults']) ? $config['defaults'] : array(); + $requirements = isset($config['requirements']) ? $config['requirements'] : array(); + $options = isset($config['options']) ? $config['options'] : array(); + $host = isset($config['host']) ? $config['host'] : null; + $condition = isset($config['condition']) ? $config['condition'] : null; + $schemes = isset($config['schemes']) ? $config['schemes'] : null; + $methods = isset($config['methods']) ? $config['methods'] : null; + + $this->setCurrentDir(dirname($path)); + + $subCollection = $this->import($config['resource'], $type, false, $file); + /* @var $subCollection RouteCollection */ + $subCollection->addPrefix($prefix); + if (null !== $host) { + $subCollection->setHost($host); + } + if (null !== $condition) { + $subCollection->setCondition($condition); + } + if (null !== $schemes) { + $subCollection->setSchemes($schemes); + } + if (null !== $methods) { + $subCollection->setMethods($methods); + } + $subCollection->addDefaults($defaults); + $subCollection->addRequirements($requirements); + $subCollection->addOptions($options); + + $collection->addCollection($subCollection); + } + + /** + * Validates the route configuration. + * + * @param array $config A resource config + * @param string $name The config key + * @param string $path The loaded file path + * + * @throws \InvalidArgumentException If one of the provided config keys is not supported, + * something is missing or the combination is nonsense + */ + protected function validate($config, $name, $path) + { + if (!is_array($config)) { + throw new \InvalidArgumentException(sprintf('The definition of "%s" in "%s" must be a YAML array.', $name, $path)); + } + if ($extraKeys = array_diff(array_keys($config), self::$availableKeys)) { + throw new \InvalidArgumentException(sprintf( + 'The routing file "%s" contains unsupported keys for "%s": "%s". Expected one of: "%s".', + $path, $name, implode('", "', $extraKeys), implode('", "', self::$availableKeys) + )); + } + if (isset($config['resource']) && isset($config['path'])) { + throw new \InvalidArgumentException(sprintf( + 'The routing file "%s" must not specify both the "resource" key and the "path" key for "%s". Choose between an import and a route definition.', + $path, $name + )); + } + if (!isset($config['resource']) && isset($config['type'])) { + throw new \InvalidArgumentException(sprintf( + 'The "type" key for the route definition "%s" in "%s" is unsupported. It is only available for imports in combination with the "resource" key.', + $name, $path + )); + } + if (!isset($config['resource']) && !isset($config['path'])) { + throw new \InvalidArgumentException(sprintf( + 'You must define a "path" for the route "%s" in file "%s".', + $name, $path + )); + } + } +} diff --git a/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd b/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd new file mode 100644 index 00000000..92d4ae20 --- /dev/null +++ b/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php b/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php new file mode 100644 index 00000000..b24c8512 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +/** + * Collection of routes. + * + * @author Arnaud Le Blanc + * + * @internal + */ +class DumperCollection implements \IteratorAggregate +{ + /** + * @var DumperCollection|null + */ + private $parent; + + /** + * @var DumperCollection[]|DumperRoute[] + */ + private $children = array(); + + /** + * @var array + */ + private $attributes = array(); + + /** + * Returns the children routes and collections. + * + * @return self[]|DumperRoute[] + */ + public function all() + { + return $this->children; + } + + /** + * Adds a route or collection. + * + * @param DumperRoute|DumperCollection The route or collection + */ + public function add($child) + { + if ($child instanceof self) { + $child->setParent($this); + } + $this->children[] = $child; + } + + /** + * Sets children. + * + * @param array $children The children + */ + public function setAll(array $children) + { + foreach ($children as $child) { + if ($child instanceof self) { + $child->setParent($this); + } + } + $this->children = $children; + } + + /** + * Returns an iterator over the children. + * + * @return \Iterator|DumperCollection[]|DumperRoute[] The iterator + */ + public function getIterator() + { + return new \ArrayIterator($this->children); + } + + /** + * Returns the root of the collection. + * + * @return self The root collection + */ + public function getRoot() + { + return (null !== $this->parent) ? $this->parent->getRoot() : $this; + } + + /** + * Returns the parent collection. + * + * @return self|null The parent collection or null if the collection has no parent + */ + protected function getParent() + { + return $this->parent; + } + + /** + * Sets the parent collection. + * + * @param DumperCollection $parent The parent collection + */ + protected function setParent(DumperCollection $parent) + { + $this->parent = $parent; + } + + /** + * Returns true if the attribute is defined. + * + * @param string $name The attribute name + * + * @return bool true if the attribute is defined, false otherwise + */ + public function hasAttribute($name) + { + return array_key_exists($name, $this->attributes); + } + + /** + * Returns an attribute by name. + * + * @param string $name The attribute name + * @param mixed $default Default value is the attribute doesn't exist + * + * @return mixed The attribute value + */ + public function getAttribute($name, $default = null) + { + return $this->hasAttribute($name) ? $this->attributes[$name] : $default; + } + + /** + * Sets an attribute by name. + * + * @param string $name The attribute name + * @param mixed $value The attribute value + */ + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + } + + /** + * Sets multiple attributes. + * + * @param array $attributes The attributes + */ + public function setAttributes($attributes) + { + $this->attributes = $attributes; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php b/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php new file mode 100644 index 00000000..3ad08c20 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\Route; + +/** + * Container for a Route. + * + * @author Arnaud Le Blanc + * + * @internal + */ +class DumperRoute +{ + /** + * @var string + */ + private $name; + + /** + * @var Route + */ + private $route; + + /** + * Constructor. + * + * @param string $name The route name + * @param Route $route The route + */ + public function __construct($name, Route $route) + { + $this->name = $name; + $this->route = $route; + } + + /** + * Returns the route name. + * + * @return string The route name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns the route. + * + * @return Route The route + */ + public function getRoute() + { + return $this->route; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php b/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php new file mode 100644 index 00000000..52edc017 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * MatcherDumper is the abstract class for all built-in matcher dumpers. + * + * @author Fabien Potencier + */ +abstract class MatcherDumper implements MatcherDumperInterface +{ + /** + * @var RouteCollection + */ + private $routes; + + /** + * Constructor. + * + * @param RouteCollection $routes The RouteCollection to dump + */ + public function __construct(RouteCollection $routes) + { + $this->routes = $routes; + } + + /** + * {@inheritdoc} + */ + public function getRoutes() + { + return $this->routes; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php b/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php new file mode 100644 index 00000000..5e7c134b --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * MatcherDumperInterface is the interface that all matcher dumper classes must implement. + * + * @author Fabien Potencier + */ +interface MatcherDumperInterface +{ + /** + * Dumps a set of routes to a string representation of executable code + * that can then be used to match a request against these routes. + * + * @param array $options An array of options + * + * @return string Executable code + */ + public function dump(array $options = array()); + + /** + * Gets the routes to dump. + * + * @return RouteCollection A RouteCollection instance + */ + public function getRoutes(); +} diff --git a/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php b/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php new file mode 100644 index 00000000..1397bac4 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php @@ -0,0 +1,431 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; + +/** + * PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes. + * + * @author Fabien Potencier + * @author Tobias Schultze + * @author Arnaud Le Blanc + */ +class PhpMatcherDumper extends MatcherDumper +{ + private $expressionLanguage; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + private $expressionLanguageProviders = array(); + + /** + * Dumps a set of routes to a PHP class. + * + * Available options: + * + * * class: The class name + * * base_class: The base class name + * + * @param array $options An array of options + * + * @return string A PHP class representing the matcher class + */ + public function dump(array $options = array()) + { + $options = array_replace(array( + 'class' => 'ProjectUrlMatcher', + 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + ), $options); + + // trailing slash support is only enabled if we know how to redirect the user + $interfaces = class_implements($options['base_class']); + $supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']); + + return <<context = \$context; + } + +{$this->generateMatchMethod($supportsRedirections)} +} + +EOF; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * Generates the code for the match method implementing UrlMatcherInterface. + * + * @param bool $supportsRedirections Whether redirections are supported by the base class + * + * @return string Match method as PHP code + */ + private function generateMatchMethod($supportsRedirections) + { + $code = rtrim($this->compileRoutes($this->getRoutes(), $supportsRedirections), "\n"); + + return <<context; + \$request = \$this->request; + \$requestMethod = \$canonicalMethod = \$context->getMethod(); + \$scheme = \$context->getScheme(); + + if ('HEAD' === \$requestMethod) { + \$canonicalMethod = 'GET'; + } + + +$code + + throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException(); + } +EOF; + } + + /** + * Generates PHP code to match a RouteCollection with all its routes. + * + * @param RouteCollection $routes A RouteCollection instance + * @param bool $supportsRedirections Whether redirections are supported by the base class + * + * @return string PHP code + */ + private function compileRoutes(RouteCollection $routes, $supportsRedirections) + { + $fetchedHost = false; + $groups = $this->groupRoutesByHostRegex($routes); + $code = ''; + + foreach ($groups as $collection) { + if (null !== $regex = $collection->getAttribute('host_regex')) { + if (!$fetchedHost) { + $code .= " \$host = \$context->getHost();\n\n"; + $fetchedHost = true; + } + + $code .= sprintf(" if (preg_match(%s, \$host, \$hostMatches)) {\n", var_export($regex, true)); + } + + $tree = $this->buildStaticPrefixCollection($collection); + $groupCode = $this->compileStaticPrefixRoutes($tree, $supportsRedirections); + + if (null !== $regex) { + // apply extra indention at each line (except empty ones) + $groupCode = preg_replace('/^.{2,}$/m', ' $0', $groupCode); + $code .= $groupCode; + $code .= " }\n\n"; + } else { + $code .= $groupCode; + } + } + + return $code; + } + + private function buildStaticPrefixCollection(DumperCollection $collection) + { + $prefixCollection = new StaticPrefixCollection(); + + foreach ($collection as $dumperRoute) { + $prefix = $dumperRoute->getRoute()->compile()->getStaticPrefix(); + $prefixCollection->addRoute($prefix, $dumperRoute); + } + + $prefixCollection->optimizeGroups(); + + return $prefixCollection; + } + + /** + * Generates PHP code to match a tree of routes. + * + * @param StaticPrefixCollection $collection A StaticPrefixCollection instance + * @param bool $supportsRedirections Whether redirections are supported by the base class + * @param string $ifOrElseIf Either "if" or "elseif" to influence chaining. + * + * @return string PHP code + */ + private function compileStaticPrefixRoutes(StaticPrefixCollection $collection, $supportsRedirections, $ifOrElseIf = 'if') + { + $code = ''; + $prefix = $collection->getPrefix(); + + if (!empty($prefix) && '/' !== $prefix) { + $code .= sprintf(" %s (0 === strpos(\$pathinfo, %s)) {\n", $ifOrElseIf, var_export($prefix, true)); + } + + $ifOrElseIf = 'if'; + + foreach ($collection->getItems() as $route) { + if ($route instanceof StaticPrefixCollection) { + $code .= $this->compileStaticPrefixRoutes($route, $supportsRedirections, $ifOrElseIf); + $ifOrElseIf = 'elseif'; + } else { + $code .= $this->compileRoute($route[1]->getRoute(), $route[1]->getName(), $supportsRedirections, $prefix)."\n"; + $ifOrElseIf = 'if'; + } + } + + if (!empty($prefix) && '/' !== $prefix) { + $code .= " }\n\n"; + // apply extra indention at each line (except empty ones) + $code = preg_replace('/^.{2,}$/m', ' $0', $code); + } + + return $code; + } + + /** + * Compiles a single Route to PHP code used to match it against the path info. + * + * @param Route $route A Route instance + * @param string $name The name of the Route + * @param bool $supportsRedirections Whether redirections are supported by the base class + * @param string|null $parentPrefix The prefix of the parent collection used to optimize the code + * + * @return string PHP code + * + * @throws \LogicException + */ + private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null) + { + $code = ''; + $compiledRoute = $route->compile(); + $conditions = array(); + $hasTrailingSlash = false; + $matches = false; + $hostMatches = false; + $methods = $route->getMethods(); + + $supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('HEAD', $methods) || in_array('GET', $methods)); + $regex = $compiledRoute->getRegex(); + + if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P.*?)\$\1#'.(substr($regex, -1) === 'u' ? 'u' : ''), $regex, $m)) { + if ($supportsTrailingSlash && substr($m['url'], -1) === '/') { + $conditions[] = sprintf('%s === $trimmedPathinfo', var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true)); + $hasTrailingSlash = true; + } else { + $conditions[] = sprintf('%s === $pathinfo', var_export(str_replace('\\', '', $m['url']), true)); + } + } else { + if ($compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() !== $parentPrefix) { + $conditions[] = sprintf('0 === strpos($pathinfo, %s)', var_export($compiledRoute->getStaticPrefix(), true)); + } + + if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) { + $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2); + $hasTrailingSlash = true; + } + $conditions[] = sprintf('preg_match(%s, $pathinfo, $matches)', var_export($regex, true)); + + $matches = true; + } + + if ($compiledRoute->getHostVariables()) { + $hostMatches = true; + } + + if ($route->getCondition()) { + $conditions[] = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request')); + } + + $conditions = implode(' && ', $conditions); + + $code .= <<redirect(\$pathinfo.'/', '$name'); + } + + +EOF; + } + + if ($schemes = $route->getSchemes()) { + if (!$supportsRedirections) { + throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.'); + } + $schemes = str_replace("\n", '', var_export(array_flip($schemes), true)); + $code .= <<redirect(\$pathinfo, '$name', key(\$requiredSchemes)); + } + + +EOF; + } + + // optimize parameters array + if ($matches || $hostMatches) { + $vars = array(); + if ($hostMatches) { + $vars[] = '$hostMatches'; + } + if ($matches) { + $vars[] = '$matches'; + } + $vars[] = "array('_route' => '$name')"; + + $code .= sprintf( + " return \$this->mergeDefaults(array_replace(%s), %s);\n", + implode(', ', $vars), + str_replace("\n", '', var_export($route->getDefaults(), true)) + ); + } elseif ($route->getDefaults()) { + $code .= sprintf(" return %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), array('_route' => $name)), true))); + } else { + $code .= sprintf(" return array('_route' => '%s');\n", $name); + } + $code .= " }\n"; + + if ($methods) { + $code .= " $gotoname:\n"; + } + + return $code; + } + + /** + * Groups consecutive routes having the same host regex. + * + * The result is a collection of collections of routes having the same host regex. + * + * @param RouteCollection $routes A flat RouteCollection + * + * @return DumperCollection A collection with routes grouped by host regex in sub-collections + */ + private function groupRoutesByHostRegex(RouteCollection $routes) + { + $groups = new DumperCollection(); + $currentGroup = new DumperCollection(); + $currentGroup->setAttribute('host_regex', null); + $groups->add($currentGroup); + + foreach ($routes as $name => $route) { + $hostRegex = $route->compile()->getHostRegex(); + if ($currentGroup->getAttribute('host_regex') !== $hostRegex) { + $currentGroup = new DumperCollection(); + $currentGroup->setAttribute('host_regex', $hostRegex); + $groups->add($currentGroup); + } + $currentGroup->add(new DumperRoute($name, $route)); + } + + return $groups; + } + + private function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); + } + + return $this->expressionLanguage; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php b/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php new file mode 100644 index 00000000..b2bfa343 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php @@ -0,0 +1,238 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +/** + * Prefix tree of routes preserving routes order. + * + * @author Frank de Jonge + * + * @internal + */ +class StaticPrefixCollection +{ + /** + * @var string + */ + private $prefix; + + /** + * @var array[]|StaticPrefixCollection[] + */ + private $items = array(); + + /** + * @var int + */ + private $matchStart = 0; + + public function __construct($prefix = '') + { + $this->prefix = $prefix; + } + + public function getPrefix() + { + return $this->prefix; + } + + /** + * @return mixed[]|StaticPrefixCollection[] + */ + public function getItems() + { + return $this->items; + } + + /** + * Adds a route to a group. + * + * @param string $prefix + * @param mixed $route + */ + public function addRoute($prefix, $route) + { + $prefix = '/' === $prefix ? $prefix : rtrim($prefix, '/'); + $this->guardAgainstAddingNotAcceptedRoutes($prefix); + + if ($this->prefix === $prefix) { + // When a prefix is exactly the same as the base we move up the match start position. + // This is needed because otherwise routes that come afterwards have higher precedence + // than a possible regular expression, which goes against the input order sorting. + $this->items[] = array($prefix, $route); + $this->matchStart = count($this->items); + + return; + } + + foreach ($this->items as $i => $item) { + if ($i < $this->matchStart) { + continue; + } + + if ($item instanceof self && $item->accepts($prefix)) { + $item->addRoute($prefix, $route); + + return; + } + + $group = $this->groupWithItem($item, $prefix, $route); + + if ($group instanceof self) { + $this->items[$i] = $group; + + return; + } + } + + // No optimised case was found, in this case we simple add the route for possible + // grouping when new routes are added. + $this->items[] = array($prefix, $route); + } + + /** + * Tries to combine a route with another route or group. + * + * @param StaticPrefixCollection|array $item + * @param string $prefix + * @param mixed $route + * + * @return null|StaticPrefixCollection + */ + private function groupWithItem($item, $prefix, $route) + { + $itemPrefix = $item instanceof self ? $item->prefix : $item[0]; + $commonPrefix = $this->detectCommonPrefix($prefix, $itemPrefix); + + if (!$commonPrefix) { + return; + } + + $child = new self($commonPrefix); + + if ($item instanceof self) { + $child->items = array($item); + } else { + $child->addRoute($item[0], $item[1]); + } + + $child->addRoute($prefix, $route); + + return $child; + } + + /** + * Checks whether a prefix can be contained within the group. + * + * @param string $prefix + * + * @return bool Whether a prefix could belong in a given group + */ + private function accepts($prefix) + { + return '' === $this->prefix || strpos($prefix, $this->prefix) === 0; + } + + /** + * Detects whether there's a common prefix relative to the group prefix and returns it. + * + * @param string $prefix + * @param string $anotherPrefix + * + * @return false|string A common prefix, longer than the base/group prefix, or false when none available + */ + private function detectCommonPrefix($prefix, $anotherPrefix) + { + $baseLength = strlen($this->prefix); + $commonLength = $baseLength; + $end = min(strlen($prefix), strlen($anotherPrefix)); + + for ($i = $baseLength; $i <= $end; ++$i) { + if (substr($prefix, 0, $i) !== substr($anotherPrefix, 0, $i)) { + break; + } + + $commonLength = $i; + } + + $commonPrefix = rtrim(substr($prefix, 0, $commonLength), '/'); + + if (strlen($commonPrefix) > $baseLength) { + return $commonPrefix; + } + + return false; + } + + /** + * Optimizes the tree by inlining items from groups with less than 3 items. + */ + public function optimizeGroups() + { + $index = -1; + + while (isset($this->items[++$index])) { + $item = $this->items[$index]; + + if ($item instanceof self) { + $item->optimizeGroups(); + + // When a group contains only two items there's no reason to optimize because at minimum + // the amount of prefix check is 2. In this case inline the group. + if ($item->shouldBeInlined()) { + array_splice($this->items, $index, 1, $item->items); + + // Lower index to pass through the same index again after optimizing. + // The first item of the replacements might be a group needing optimization. + --$index; + } + } + } + } + + private function shouldBeInlined() + { + if (count($this->items) >= 3) { + return false; + } + + foreach ($this->items as $item) { + if ($item instanceof self) { + return true; + } + } + + foreach ($this->items as $item) { + if (is_array($item) && $item[0] === $this->prefix) { + return false; + } + } + + return true; + } + + /** + * Guards against adding incompatible prefixes in a group. + * + * @param string $prefix + * + * @throws \LogicException When a prefix does not belong in a group. + */ + private function guardAgainstAddingNotAcceptedRoutes($prefix) + { + if (!$this->accepts($prefix)) { + $message = sprintf('Could not add route with prefix %s to collection with prefix %s', $prefix, $this->prefix); + + throw new \LogicException($message); + } + } +} diff --git a/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php b/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php new file mode 100644 index 00000000..900c59fa --- /dev/null +++ b/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Route; + +/** + * @author Fabien Potencier + */ +abstract class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface +{ + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + try { + $parameters = parent::match($pathinfo); + } catch (ResourceNotFoundException $e) { + if ('/' === substr($pathinfo, -1) || !in_array($this->context->getMethod(), array('HEAD', 'GET'))) { + throw $e; + } + + try { + parent::match($pathinfo.'/'); + + return $this->redirect($pathinfo.'/', null); + } catch (ResourceNotFoundException $e2) { + throw $e; + } + } + + return $parameters; + } + + /** + * {@inheritdoc} + */ + protected function handleRouteRequirements($pathinfo, $name, Route $route) + { + // expression condition + if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) { + return array(self::REQUIREMENT_MISMATCH, null); + } + + // check HTTP scheme requirement + $scheme = $this->context->getScheme(); + $schemes = $route->getSchemes(); + if ($schemes && !$route->hasScheme($scheme)) { + return array(self::ROUTE_MATCH, $this->redirect($pathinfo, $name, current($schemes))); + } + + return array(self::REQUIREMENT_MATCH, null); + } +} diff --git a/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php b/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php new file mode 100644 index 00000000..7c27bc87 --- /dev/null +++ b/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +/** + * RedirectableUrlMatcherInterface knows how to redirect the user. + * + * @author Fabien Potencier + */ +interface RedirectableUrlMatcherInterface +{ + /** + * Redirects the user to another URL. + * + * @param string $path The path info to redirect to + * @param string $route The route name that matched + * @param string|null $scheme The URL scheme (null to keep the current one) + * + * @return array An array of parameters + */ + public function redirect($path, $route, $scheme = null); +} diff --git a/vendor/symfony/routing/Matcher/RequestMatcherInterface.php b/vendor/symfony/routing/Matcher/RequestMatcherInterface.php new file mode 100644 index 00000000..b5def3d4 --- /dev/null +++ b/vendor/symfony/routing/Matcher/RequestMatcherInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; + +/** + * RequestMatcherInterface is the interface that all request matcher classes must implement. + * + * @author Fabien Potencier + */ +interface RequestMatcherInterface +{ + /** + * Tries to match a request with a set of routes. + * + * If the matcher can not find information, it must throw one of the exceptions documented + * below. + * + * @param Request $request The request to match + * + * @return array An array of parameters + * + * @throws ResourceNotFoundException If no matching resource could be found + * @throws MethodNotAllowedException If a matching resource was found but the request method is not allowed + */ + public function matchRequest(Request $request); +} diff --git a/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php b/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php new file mode 100644 index 00000000..9085be04 --- /dev/null +++ b/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\ExceptionInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * TraceableUrlMatcher helps debug path info matching by tracing the match. + * + * @author Fabien Potencier + */ +class TraceableUrlMatcher extends UrlMatcher +{ + const ROUTE_DOES_NOT_MATCH = 0; + const ROUTE_ALMOST_MATCHES = 1; + const ROUTE_MATCHES = 2; + + protected $traces; + + public function getTraces($pathinfo) + { + $this->traces = array(); + + try { + $this->match($pathinfo); + } catch (ExceptionInterface $e) { + } + + return $this->traces; + } + + public function getTracesForRequest(Request $request) + { + $this->request = $request; + $traces = $this->getTraces($request->getPathInfo()); + $this->request = null; + + return $traces; + } + + protected function matchCollection($pathinfo, RouteCollection $routes) + { + foreach ($routes as $name => $route) { + $compiledRoute = $route->compile(); + + if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { + // does it match without any requirements? + $r = new Route($route->getPath(), $route->getDefaults(), array(), $route->getOptions()); + $cr = $r->compile(); + if (!preg_match($cr->getRegex(), $pathinfo)) { + $this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); + + continue; + } + + foreach ($route->getRequirements() as $n => $regex) { + $r = new Route($route->getPath(), $route->getDefaults(), array($n => $regex), $route->getOptions()); + $cr = $r->compile(); + + if (in_array($n, $cr->getVariables()) && !preg_match($cr->getRegex(), $pathinfo)) { + $this->addTrace(sprintf('Requirement for "%s" does not match (%s)', $n, $regex), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue 2; + } + } + + continue; + } + + // check host requirement + $hostMatches = array(); + if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { + $this->addTrace(sprintf('Host "%s" does not match the requirement ("%s")', $this->context->getHost(), $route->getHost()), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue; + } + + // check HTTP method requirement + if ($requiredMethods = $route->getMethods()) { + // HEAD and GET are equivalent as per RFC + if ('HEAD' === $method = $this->context->getMethod()) { + $method = 'GET'; + } + + if (!in_array($method, $requiredMethods)) { + $this->allow = array_merge($this->allow, $requiredMethods); + + $this->addTrace(sprintf('Method "%s" does not match any of the required methods (%s)', $this->context->getMethod(), implode(', ', $requiredMethods)), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue; + } + } + + // check condition + if ($condition = $route->getCondition()) { + if (!$this->getExpressionLanguage()->evaluate($condition, array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) { + $this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $condition), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue; + } + } + + // check HTTP scheme requirement + if ($requiredSchemes = $route->getSchemes()) { + $scheme = $this->context->getScheme(); + + if (!$route->hasScheme($scheme)) { + $this->addTrace(sprintf('Scheme "%s" does not match any of the required schemes (%s); the user will be redirected to first required scheme', $scheme, implode(', ', $requiredSchemes)), self::ROUTE_ALMOST_MATCHES, $name, $route); + + return true; + } + } + + $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route); + + return true; + } + } + + private function addTrace($log, $level = self::ROUTE_DOES_NOT_MATCH, $name = null, $route = null) + { + $this->traces[] = array( + 'log' => $log, + 'name' => $name, + 'level' => $level, + 'path' => null !== $route ? $route->getPath() : null, + ); + } +} diff --git a/vendor/symfony/routing/Matcher/UrlMatcher.php b/vendor/symfony/routing/Matcher/UrlMatcher.php new file mode 100644 index 00000000..c646723b --- /dev/null +++ b/vendor/symfony/routing/Matcher/UrlMatcher.php @@ -0,0 +1,266 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Route; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; + +/** + * UrlMatcher matches URL based on a set of routes. + * + * @author Fabien Potencier + */ +class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface +{ + const REQUIREMENT_MATCH = 0; + const REQUIREMENT_MISMATCH = 1; + const ROUTE_MATCH = 2; + + /** + * @var RequestContext + */ + protected $context; + + /** + * @var array + */ + protected $allow = array(); + + /** + * @var RouteCollection + */ + protected $routes; + + protected $request; + protected $expressionLanguage; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + protected $expressionLanguageProviders = array(); + + /** + * Constructor. + * + * @param RouteCollection $routes A RouteCollection instance + * @param RequestContext $context The context + */ + public function __construct(RouteCollection $routes, RequestContext $context) + { + $this->routes = $routes; + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + $this->allow = array(); + + if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) { + return $ret; + } + + throw 0 < count($this->allow) + ? new MethodNotAllowedException(array_unique($this->allow)) + : new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo)); + } + + /** + * {@inheritdoc} + */ + public function matchRequest(Request $request) + { + $this->request = $request; + + $ret = $this->match($request->getPathInfo()); + + $this->request = null; + + return $ret; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * Tries to match a URL with a set of routes. + * + * @param string $pathinfo The path info to be parsed + * @param RouteCollection $routes The set of routes + * + * @return array An array of parameters + * + * @throws ResourceNotFoundException If the resource could not be found + * @throws MethodNotAllowedException If the resource was found but the request method is not allowed + */ + protected function matchCollection($pathinfo, RouteCollection $routes) + { + foreach ($routes as $name => $route) { + $compiledRoute = $route->compile(); + + // check the static prefix of the URL first. Only use the more expensive preg_match when it matches + if ('' !== $compiledRoute->getStaticPrefix() && 0 !== strpos($pathinfo, $compiledRoute->getStaticPrefix())) { + continue; + } + + if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { + continue; + } + + $hostMatches = array(); + if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { + continue; + } + + // check HTTP method requirement + if ($requiredMethods = $route->getMethods()) { + // HEAD and GET are equivalent as per RFC + if ('HEAD' === $method = $this->context->getMethod()) { + $method = 'GET'; + } + + if (!in_array($method, $requiredMethods)) { + $this->allow = array_merge($this->allow, $requiredMethods); + + continue; + } + } + + $status = $this->handleRouteRequirements($pathinfo, $name, $route); + + if (self::ROUTE_MATCH === $status[0]) { + return $status[1]; + } + + if (self::REQUIREMENT_MISMATCH === $status[0]) { + continue; + } + + return $this->getAttributes($route, $name, array_replace($matches, $hostMatches)); + } + } + + /** + * Returns an array of values to use as request attributes. + * + * As this method requires the Route object, it is not available + * in matchers that do not have access to the matched Route instance + * (like the PHP and Apache matcher dumpers). + * + * @param Route $route The route we are matching against + * @param string $name The name of the route + * @param array $attributes An array of attributes from the matcher + * + * @return array An array of parameters + */ + protected function getAttributes(Route $route, $name, array $attributes) + { + $attributes['_route'] = $name; + + return $this->mergeDefaults($attributes, $route->getDefaults()); + } + + /** + * Handles specific route requirements. + * + * @param string $pathinfo The path + * @param string $name The route name + * @param Route $route The route + * + * @return array The first element represents the status, the second contains additional information + */ + protected function handleRouteRequirements($pathinfo, $name, Route $route) + { + // expression condition + if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) { + return array(self::REQUIREMENT_MISMATCH, null); + } + + // check HTTP scheme requirement + $scheme = $this->context->getScheme(); + $status = $route->getSchemes() && !$route->hasScheme($scheme) ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH; + + return array($status, null); + } + + /** + * Get merged default parameters. + * + * @param array $params The parameters + * @param array $defaults The defaults + * + * @return array Merged default parameters + */ + protected function mergeDefaults($params, $defaults) + { + foreach ($params as $key => $value) { + if (!is_int($key)) { + $defaults[$key] = $value; + } + } + + return $defaults; + } + + protected function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); + } + + return $this->expressionLanguage; + } + + /** + * @internal + */ + protected function createRequest($pathinfo) + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + return null; + } + + return Request::create($this->context->getScheme().'://'.$this->context->getHost().$this->context->getBaseUrl().$pathinfo, $this->context->getMethod(), $this->context->getParameters(), array(), array(), array( + 'SCRIPT_FILENAME' => $this->context->getBaseUrl(), + 'SCRIPT_NAME' => $this->context->getBaseUrl(), + )); + } +} diff --git a/vendor/symfony/routing/Matcher/UrlMatcherInterface.php b/vendor/symfony/routing/Matcher/UrlMatcherInterface.php new file mode 100644 index 00000000..af38662f --- /dev/null +++ b/vendor/symfony/routing/Matcher/UrlMatcherInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\RequestContextAwareInterface; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; + +/** + * UrlMatcherInterface is the interface that all URL matcher classes must implement. + * + * @author Fabien Potencier + */ +interface UrlMatcherInterface extends RequestContextAwareInterface +{ + /** + * Tries to match a URL path with a set of routes. + * + * If the matcher can not find information, it must throw one of the exceptions documented + * below. + * + * @param string $pathinfo The path info to be parsed (raw format, i.e. not urldecoded) + * + * @return array An array of parameters + * + * @throws ResourceNotFoundException If the resource could not be found + * @throws MethodNotAllowedException If the resource was found but the request method is not allowed + */ + public function match($pathinfo); +} diff --git a/vendor/symfony/routing/README.md b/vendor/symfony/routing/README.md new file mode 100644 index 00000000..88fb1fde --- /dev/null +++ b/vendor/symfony/routing/README.md @@ -0,0 +1,13 @@ +Routing Component +================= + +The Routing component maps an HTTP request to a set of configuration variables. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/routing/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/routing/RequestContext.php b/vendor/symfony/routing/RequestContext.php new file mode 100644 index 00000000..9b15cd07 --- /dev/null +++ b/vendor/symfony/routing/RequestContext.php @@ -0,0 +1,344 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\HttpFoundation\Request; + +/** + * Holds information about the current request. + * + * This class implements a fluent interface. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class RequestContext +{ + private $baseUrl; + private $pathInfo; + private $method; + private $host; + private $scheme; + private $httpPort; + private $httpsPort; + private $queryString; + + /** + * @var array + */ + private $parameters = array(); + + /** + * Constructor. + * + * @param string $baseUrl The base URL + * @param string $method The HTTP method + * @param string $host The HTTP host name + * @param string $scheme The HTTP scheme + * @param int $httpPort The HTTP port + * @param int $httpsPort The HTTPS port + * @param string $path The path + * @param string $queryString The query string + */ + public function __construct($baseUrl = '', $method = 'GET', $host = 'localhost', $scheme = 'http', $httpPort = 80, $httpsPort = 443, $path = '/', $queryString = '') + { + $this->setBaseUrl($baseUrl); + $this->setMethod($method); + $this->setHost($host); + $this->setScheme($scheme); + $this->setHttpPort($httpPort); + $this->setHttpsPort($httpsPort); + $this->setPathInfo($path); + $this->setQueryString($queryString); + } + + /** + * Updates the RequestContext information based on a HttpFoundation Request. + * + * @param Request $request A Request instance + * + * @return $this + */ + public function fromRequest(Request $request) + { + $this->setBaseUrl($request->getBaseUrl()); + $this->setPathInfo($request->getPathInfo()); + $this->setMethod($request->getMethod()); + $this->setHost($request->getHost()); + $this->setScheme($request->getScheme()); + $this->setHttpPort($request->isSecure() ? $this->httpPort : $request->getPort()); + $this->setHttpsPort($request->isSecure() ? $request->getPort() : $this->httpsPort); + $this->setQueryString($request->server->get('QUERY_STRING', '')); + + return $this; + } + + /** + * Gets the base URL. + * + * @return string The base URL + */ + public function getBaseUrl() + { + return $this->baseUrl; + } + + /** + * Sets the base URL. + * + * @param string $baseUrl The base URL + * + * @return $this + */ + public function setBaseUrl($baseUrl) + { + $this->baseUrl = $baseUrl; + + return $this; + } + + /** + * Gets the path info. + * + * @return string The path info + */ + public function getPathInfo() + { + return $this->pathInfo; + } + + /** + * Sets the path info. + * + * @param string $pathInfo The path info + * + * @return $this + */ + public function setPathInfo($pathInfo) + { + $this->pathInfo = $pathInfo; + + return $this; + } + + /** + * Gets the HTTP method. + * + * The method is always an uppercased string. + * + * @return string The HTTP method + */ + public function getMethod() + { + return $this->method; + } + + /** + * Sets the HTTP method. + * + * @param string $method The HTTP method + * + * @return $this + */ + public function setMethod($method) + { + $this->method = strtoupper($method); + + return $this; + } + + /** + * Gets the HTTP host. + * + * The host is always lowercased because it must be treated case-insensitive. + * + * @return string The HTTP host + */ + public function getHost() + { + return $this->host; + } + + /** + * Sets the HTTP host. + * + * @param string $host The HTTP host + * + * @return $this + */ + public function setHost($host) + { + $this->host = strtolower($host); + + return $this; + } + + /** + * Gets the HTTP scheme. + * + * @return string The HTTP scheme + */ + public function getScheme() + { + return $this->scheme; + } + + /** + * Sets the HTTP scheme. + * + * @param string $scheme The HTTP scheme + * + * @return $this + */ + public function setScheme($scheme) + { + $this->scheme = strtolower($scheme); + + return $this; + } + + /** + * Gets the HTTP port. + * + * @return int The HTTP port + */ + public function getHttpPort() + { + return $this->httpPort; + } + + /** + * Sets the HTTP port. + * + * @param int $httpPort The HTTP port + * + * @return $this + */ + public function setHttpPort($httpPort) + { + $this->httpPort = (int) $httpPort; + + return $this; + } + + /** + * Gets the HTTPS port. + * + * @return int The HTTPS port + */ + public function getHttpsPort() + { + return $this->httpsPort; + } + + /** + * Sets the HTTPS port. + * + * @param int $httpsPort The HTTPS port + * + * @return $this + */ + public function setHttpsPort($httpsPort) + { + $this->httpsPort = (int) $httpsPort; + + return $this; + } + + /** + * Gets the query string. + * + * @return string The query string without the "?" + */ + public function getQueryString() + { + return $this->queryString; + } + + /** + * Sets the query string. + * + * @param string $queryString The query string (after "?") + * + * @return $this + */ + public function setQueryString($queryString) + { + // string cast to be fault-tolerant, accepting null + $this->queryString = (string) $queryString; + + return $this; + } + + /** + * Returns the parameters. + * + * @return array The parameters + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Sets the parameters. + * + * @param array $parameters The parameters + * + * @return $this + */ + public function setParameters(array $parameters) + { + $this->parameters = $parameters; + + return $this; + } + + /** + * Gets a parameter value. + * + * @param string $name A parameter name + * + * @return mixed The parameter value or null if nonexistent + */ + public function getParameter($name) + { + return isset($this->parameters[$name]) ? $this->parameters[$name] : null; + } + + /** + * Checks if a parameter value is set for the given parameter. + * + * @param string $name A parameter name + * + * @return bool True if the parameter value is set, false otherwise + */ + public function hasParameter($name) + { + return array_key_exists($name, $this->parameters); + } + + /** + * Sets a parameter value. + * + * @param string $name A parameter name + * @param mixed $parameter The parameter value + * + * @return $this + */ + public function setParameter($name, $parameter) + { + $this->parameters[$name] = $parameter; + + return $this; + } +} diff --git a/vendor/symfony/routing/RequestContextAwareInterface.php b/vendor/symfony/routing/RequestContextAwareInterface.php new file mode 100644 index 00000000..ebb0ef46 --- /dev/null +++ b/vendor/symfony/routing/RequestContextAwareInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +interface RequestContextAwareInterface +{ + /** + * Sets the request context. + * + * @param RequestContext $context The context + */ + public function setContext(RequestContext $context); + + /** + * Gets the request context. + * + * @return RequestContext The context + */ + public function getContext(); +} diff --git a/vendor/symfony/routing/Route.php b/vendor/symfony/routing/Route.php new file mode 100644 index 00000000..69a7cade --- /dev/null +++ b/vendor/symfony/routing/Route.php @@ -0,0 +1,589 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * A Route describes a route and its parameters. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class Route implements \Serializable +{ + /** + * @var string + */ + private $path = '/'; + + /** + * @var string + */ + private $host = ''; + + /** + * @var array + */ + private $schemes = array(); + + /** + * @var array + */ + private $methods = array(); + + /** + * @var array + */ + private $defaults = array(); + + /** + * @var array + */ + private $requirements = array(); + + /** + * @var array + */ + private $options = array(); + + /** + * @var null|CompiledRoute + */ + private $compiled; + + /** + * @var string + */ + private $condition = ''; + + /** + * Constructor. + * + * Available options: + * + * * compiler_class: A class name able to compile this route instance (RouteCompiler by default) + * * utf8: Whether UTF-8 matching is enforced ot not + * + * @param string $path The path pattern to match + * @param array $defaults An array of default parameter values + * @param array $requirements An array of requirements for parameters (regexes) + * @param array $options An array of options + * @param string $host The host pattern to match + * @param string|array $schemes A required URI scheme or an array of restricted schemes + * @param string|array $methods A required HTTP method or an array of restricted methods + * @param string $condition A condition that should evaluate to true for the route to match + */ + public function __construct($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array(), $condition = '') + { + $this->setPath($path); + $this->setDefaults($defaults); + $this->setRequirements($requirements); + $this->setOptions($options); + $this->setHost($host); + $this->setSchemes($schemes); + $this->setMethods($methods); + $this->setCondition($condition); + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array( + 'path' => $this->path, + 'host' => $this->host, + 'defaults' => $this->defaults, + 'requirements' => $this->requirements, + 'options' => $this->options, + 'schemes' => $this->schemes, + 'methods' => $this->methods, + 'condition' => $this->condition, + 'compiled' => $this->compiled, + )); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + $data = unserialize($serialized); + $this->path = $data['path']; + $this->host = $data['host']; + $this->defaults = $data['defaults']; + $this->requirements = $data['requirements']; + $this->options = $data['options']; + $this->schemes = $data['schemes']; + $this->methods = $data['methods']; + + if (isset($data['condition'])) { + $this->condition = $data['condition']; + } + if (isset($data['compiled'])) { + $this->compiled = $data['compiled']; + } + } + + /** + * Returns the pattern for the path. + * + * @return string The path pattern + */ + public function getPath() + { + return $this->path; + } + + /** + * Sets the pattern for the path. + * + * This method implements a fluent interface. + * + * @param string $pattern The path pattern + * + * @return $this + */ + public function setPath($pattern) + { + // A pattern must start with a slash and must not have multiple slashes at the beginning because the + // generated path for this route would be confused with a network path, e.g. '//domain.com/path'. + $this->path = '/'.ltrim(trim($pattern), '/'); + $this->compiled = null; + + return $this; + } + + /** + * Returns the pattern for the host. + * + * @return string The host pattern + */ + public function getHost() + { + return $this->host; + } + + /** + * Sets the pattern for the host. + * + * This method implements a fluent interface. + * + * @param string $pattern The host pattern + * + * @return $this + */ + public function setHost($pattern) + { + $this->host = (string) $pattern; + $this->compiled = null; + + return $this; + } + + /** + * Returns the lowercased schemes this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * @return array The schemes + */ + public function getSchemes() + { + return $this->schemes; + } + + /** + * Sets the schemes (e.g. 'https') this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * This method implements a fluent interface. + * + * @param string|array $schemes The scheme or an array of schemes + * + * @return $this + */ + public function setSchemes($schemes) + { + $this->schemes = array_map('strtolower', (array) $schemes); + $this->compiled = null; + + return $this; + } + + /** + * Checks if a scheme requirement has been set. + * + * @param string $scheme + * + * @return bool true if the scheme requirement exists, otherwise false + */ + public function hasScheme($scheme) + { + return in_array(strtolower($scheme), $this->schemes, true); + } + + /** + * Returns the uppercased HTTP methods this route is restricted to. + * So an empty array means that any method is allowed. + * + * @return array The methods + */ + public function getMethods() + { + return $this->methods; + } + + /** + * Sets the HTTP methods (e.g. 'POST') this route is restricted to. + * So an empty array means that any method is allowed. + * + * This method implements a fluent interface. + * + * @param string|array $methods The method or an array of methods + * + * @return $this + */ + public function setMethods($methods) + { + $this->methods = array_map('strtoupper', (array) $methods); + $this->compiled = null; + + return $this; + } + + /** + * Returns the options. + * + * @return array The options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Sets the options. + * + * This method implements a fluent interface. + * + * @param array $options The options + * + * @return $this + */ + public function setOptions(array $options) + { + $this->options = array( + 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler', + ); + + return $this->addOptions($options); + } + + /** + * Adds options. + * + * This method implements a fluent interface. + * + * @param array $options The options + * + * @return $this + */ + public function addOptions(array $options) + { + foreach ($options as $name => $option) { + $this->options[$name] = $option; + } + $this->compiled = null; + + return $this; + } + + /** + * Sets an option value. + * + * This method implements a fluent interface. + * + * @param string $name An option name + * @param mixed $value The option value + * + * @return $this + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + $this->compiled = null; + + return $this; + } + + /** + * Get an option value. + * + * @param string $name An option name + * + * @return mixed The option value or null when not given + */ + public function getOption($name) + { + return isset($this->options[$name]) ? $this->options[$name] : null; + } + + /** + * Checks if an option has been set. + * + * @param string $name An option name + * + * @return bool true if the option is set, false otherwise + */ + public function hasOption($name) + { + return array_key_exists($name, $this->options); + } + + /** + * Returns the defaults. + * + * @return array The defaults + */ + public function getDefaults() + { + return $this->defaults; + } + + /** + * Sets the defaults. + * + * This method implements a fluent interface. + * + * @param array $defaults The defaults + * + * @return $this + */ + public function setDefaults(array $defaults) + { + $this->defaults = array(); + + return $this->addDefaults($defaults); + } + + /** + * Adds defaults. + * + * This method implements a fluent interface. + * + * @param array $defaults The defaults + * + * @return $this + */ + public function addDefaults(array $defaults) + { + foreach ($defaults as $name => $default) { + $this->defaults[$name] = $default; + } + $this->compiled = null; + + return $this; + } + + /** + * Gets a default value. + * + * @param string $name A variable name + * + * @return mixed The default value or null when not given + */ + public function getDefault($name) + { + return isset($this->defaults[$name]) ? $this->defaults[$name] : null; + } + + /** + * Checks if a default value is set for the given variable. + * + * @param string $name A variable name + * + * @return bool true if the default value is set, false otherwise + */ + public function hasDefault($name) + { + return array_key_exists($name, $this->defaults); + } + + /** + * Sets a default value. + * + * @param string $name A variable name + * @param mixed $default The default value + * + * @return $this + */ + public function setDefault($name, $default) + { + $this->defaults[$name] = $default; + $this->compiled = null; + + return $this; + } + + /** + * Returns the requirements. + * + * @return array The requirements + */ + public function getRequirements() + { + return $this->requirements; + } + + /** + * Sets the requirements. + * + * This method implements a fluent interface. + * + * @param array $requirements The requirements + * + * @return $this + */ + public function setRequirements(array $requirements) + { + $this->requirements = array(); + + return $this->addRequirements($requirements); + } + + /** + * Adds requirements. + * + * This method implements a fluent interface. + * + * @param array $requirements The requirements + * + * @return $this + */ + public function addRequirements(array $requirements) + { + foreach ($requirements as $key => $regex) { + $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); + } + $this->compiled = null; + + return $this; + } + + /** + * Returns the requirement for the given key. + * + * @param string $key The key + * + * @return string|null The regex or null when not given + */ + public function getRequirement($key) + { + return isset($this->requirements[$key]) ? $this->requirements[$key] : null; + } + + /** + * Checks if a requirement is set for the given key. + * + * @param string $key A variable name + * + * @return bool true if a requirement is specified, false otherwise + */ + public function hasRequirement($key) + { + return array_key_exists($key, $this->requirements); + } + + /** + * Sets a requirement for the given key. + * + * @param string $key The key + * @param string $regex The regex + * + * @return $this + */ + public function setRequirement($key, $regex) + { + $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); + $this->compiled = null; + + return $this; + } + + /** + * Returns the condition. + * + * @return string The condition + */ + public function getCondition() + { + return $this->condition; + } + + /** + * Sets the condition. + * + * This method implements a fluent interface. + * + * @param string $condition The condition + * + * @return $this + */ + public function setCondition($condition) + { + $this->condition = (string) $condition; + $this->compiled = null; + + return $this; + } + + /** + * Compiles the route. + * + * @return CompiledRoute A CompiledRoute instance + * + * @throws \LogicException If the Route cannot be compiled because the + * path or host pattern is invalid + * + * @see RouteCompiler which is responsible for the compilation process + */ + public function compile() + { + if (null !== $this->compiled) { + return $this->compiled; + } + + $class = $this->getOption('compiler_class'); + + return $this->compiled = $class::compile($this); + } + + private function sanitizeRequirement($key, $regex) + { + if (!is_string($regex)) { + throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" must be a string.', $key)); + } + + if ('' !== $regex && '^' === $regex[0]) { + $regex = (string) substr($regex, 1); // returns false for a single character + } + + if ('$' === substr($regex, -1)) { + $regex = substr($regex, 0, -1); + } + + if ('' === $regex) { + throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" cannot be empty.', $key)); + } + + return $regex; + } +} diff --git a/vendor/symfony/routing/RouteCollection.php b/vendor/symfony/routing/RouteCollection.php new file mode 100644 index 00000000..2ccb90f3 --- /dev/null +++ b/vendor/symfony/routing/RouteCollection.php @@ -0,0 +1,277 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * A RouteCollection represents a set of Route instances. + * + * When adding a route at the end of the collection, an existing route + * with the same name is removed first. So there can only be one route + * with a given name. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class RouteCollection implements \IteratorAggregate, \Countable +{ + /** + * @var Route[] + */ + private $routes = array(); + + /** + * @var array + */ + private $resources = array(); + + public function __clone() + { + foreach ($this->routes as $name => $route) { + $this->routes[$name] = clone $route; + } + } + + /** + * Gets the current RouteCollection as an Iterator that includes all routes. + * + * It implements \IteratorAggregate. + * + * @see all() + * + * @return \ArrayIterator|Route[] An \ArrayIterator object for iterating over routes + */ + public function getIterator() + { + return new \ArrayIterator($this->routes); + } + + /** + * Gets the number of Routes in this collection. + * + * @return int The number of routes + */ + public function count() + { + return count($this->routes); + } + + /** + * Adds a route. + * + * @param string $name The route name + * @param Route $route A Route instance + */ + public function add($name, Route $route) + { + unset($this->routes[$name]); + + $this->routes[$name] = $route; + } + + /** + * Returns all routes in this collection. + * + * @return Route[] An array of routes + */ + public function all() + { + return $this->routes; + } + + /** + * Gets a route by name. + * + * @param string $name The route name + * + * @return Route|null A Route instance or null when not found + */ + public function get($name) + { + return isset($this->routes[$name]) ? $this->routes[$name] : null; + } + + /** + * Removes a route or an array of routes by name from the collection. + * + * @param string|array $name The route name or an array of route names + */ + public function remove($name) + { + foreach ((array) $name as $n) { + unset($this->routes[$n]); + } + } + + /** + * Adds a route collection at the end of the current set by appending all + * routes of the added collection. + * + * @param RouteCollection $collection A RouteCollection instance + */ + public function addCollection(RouteCollection $collection) + { + // we need to remove all routes with the same names first because just replacing them + // would not place the new route at the end of the merged array + foreach ($collection->all() as $name => $route) { + unset($this->routes[$name]); + $this->routes[$name] = $route; + } + + $this->resources = array_merge($this->resources, $collection->getResources()); + } + + /** + * Adds a prefix to the path of all child routes. + * + * @param string $prefix An optional prefix to add before each pattern of the route collection + * @param array $defaults An array of default values + * @param array $requirements An array of requirements + */ + public function addPrefix($prefix, array $defaults = array(), array $requirements = array()) + { + $prefix = trim(trim($prefix), '/'); + + if ('' === $prefix) { + return; + } + + foreach ($this->routes as $route) { + $route->setPath('/'.$prefix.$route->getPath()); + $route->addDefaults($defaults); + $route->addRequirements($requirements); + } + } + + /** + * Sets the host pattern on all routes. + * + * @param string $pattern The pattern + * @param array $defaults An array of default values + * @param array $requirements An array of requirements + */ + public function setHost($pattern, array $defaults = array(), array $requirements = array()) + { + foreach ($this->routes as $route) { + $route->setHost($pattern); + $route->addDefaults($defaults); + $route->addRequirements($requirements); + } + } + + /** + * Sets a condition on all routes. + * + * Existing conditions will be overridden. + * + * @param string $condition The condition + */ + public function setCondition($condition) + { + foreach ($this->routes as $route) { + $route->setCondition($condition); + } + } + + /** + * Adds defaults to all routes. + * + * An existing default value under the same name in a route will be overridden. + * + * @param array $defaults An array of default values + */ + public function addDefaults(array $defaults) + { + if ($defaults) { + foreach ($this->routes as $route) { + $route->addDefaults($defaults); + } + } + } + + /** + * Adds requirements to all routes. + * + * An existing requirement under the same name in a route will be overridden. + * + * @param array $requirements An array of requirements + */ + public function addRequirements(array $requirements) + { + if ($requirements) { + foreach ($this->routes as $route) { + $route->addRequirements($requirements); + } + } + } + + /** + * Adds options to all routes. + * + * An existing option value under the same name in a route will be overridden. + * + * @param array $options An array of options + */ + public function addOptions(array $options) + { + if ($options) { + foreach ($this->routes as $route) { + $route->addOptions($options); + } + } + } + + /** + * Sets the schemes (e.g. 'https') all child routes are restricted to. + * + * @param string|array $schemes The scheme or an array of schemes + */ + public function setSchemes($schemes) + { + foreach ($this->routes as $route) { + $route->setSchemes($schemes); + } + } + + /** + * Sets the HTTP methods (e.g. 'POST') all child routes are restricted to. + * + * @param string|array $methods The method or an array of methods + */ + public function setMethods($methods) + { + foreach ($this->routes as $route) { + $route->setMethods($methods); + } + } + + /** + * Returns an array of resources loaded to build this collection. + * + * @return ResourceInterface[] An array of resources + */ + public function getResources() + { + return array_unique($this->resources); + } + + /** + * Adds a resource for this collection. + * + * @param ResourceInterface $resource A resource instance + */ + public function addResource(ResourceInterface $resource) + { + $this->resources[] = $resource; + } +} diff --git a/vendor/symfony/routing/RouteCollectionBuilder.php b/vendor/symfony/routing/RouteCollectionBuilder.php new file mode 100644 index 00000000..54bd86b7 --- /dev/null +++ b/vendor/symfony/routing/RouteCollectionBuilder.php @@ -0,0 +1,383 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Config\Exception\FileLoaderLoadException; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * Helps add and import routes into a RouteCollection. + * + * @author Ryan Weaver + */ +class RouteCollectionBuilder +{ + /** + * @var Route[]|RouteCollectionBuilder[] + */ + private $routes = array(); + + private $loader; + private $defaults = array(); + private $prefix; + private $host; + private $condition; + private $requirements = array(); + private $options = array(); + private $schemes; + private $methods; + private $resources = array(); + + /** + * @param LoaderInterface $loader + */ + public function __construct(LoaderInterface $loader = null) + { + $this->loader = $loader; + } + + /** + * Import an external routing resource and returns the RouteCollectionBuilder. + * + * $routes->import('blog.yml', '/blog'); + * + * @param mixed $resource + * @param string|null $prefix + * @param string $type + * + * @return self + * + * @throws FileLoaderLoadException + */ + public function import($resource, $prefix = '/', $type = null) + { + /** @var RouteCollection[] $collection */ + $collections = $this->load($resource, $type); + + // create a builder from the RouteCollection + $builder = $this->createBuilder(); + + foreach ($collections as $collection) { + if (null === $collection) { + continue; + } + + foreach ($collection->all() as $name => $route) { + $builder->addRoute($route, $name); + } + + foreach ($collection->getResources() as $resource) { + $builder->addResource($resource); + } + + // mount into this builder + $this->mount($prefix, $builder); + } + + return $builder; + } + + /** + * Adds a route and returns it for future modification. + * + * @param string $path The route path + * @param string $controller The route's controller + * @param string|null $name The name to give this route + * + * @return Route + */ + public function add($path, $controller, $name = null) + { + $route = new Route($path); + $route->setDefault('_controller', $controller); + $this->addRoute($route, $name); + + return $route; + } + + /** + * Returns a RouteCollectionBuilder that can be configured and then added with mount(). + * + * @return self + */ + public function createBuilder() + { + return new self($this->loader); + } + + /** + * Add a RouteCollectionBuilder. + * + * @param string $prefix + * @param RouteCollectionBuilder $builder + */ + public function mount($prefix, RouteCollectionBuilder $builder) + { + $builder->prefix = trim(trim($prefix), '/'); + $this->routes[] = $builder; + } + + /** + * Adds a Route object to the builder. + * + * @param Route $route + * @param string|null $name + * + * @return $this + */ + public function addRoute(Route $route, $name = null) + { + if (null === $name) { + // used as a flag to know which routes will need a name later + $name = '_unnamed_route_'.spl_object_hash($route); + } + + $this->routes[$name] = $route; + + return $this; + } + + /** + * Sets the host on all embedded routes (unless already set). + * + * @param string $pattern + * + * @return $this + */ + public function setHost($pattern) + { + $this->host = $pattern; + + return $this; + } + + /** + * Sets a condition on all embedded routes (unless already set). + * + * @param string $condition + * + * @return $this + */ + public function setCondition($condition) + { + $this->condition = $condition; + + return $this; + } + + /** + * Sets a default value that will be added to all embedded routes (unless that + * default value is already set). + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function setDefault($key, $value) + { + $this->defaults[$key] = $value; + + return $this; + } + + /** + * Sets a requirement that will be added to all embedded routes (unless that + * requirement is already set). + * + * @param string $key + * @param mixed $regex + * + * @return $this + */ + public function setRequirement($key, $regex) + { + $this->requirements[$key] = $regex; + + return $this; + } + + /** + * Sets an option that will be added to all embedded routes (unless that + * option is already set). + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function setOption($key, $value) + { + $this->options[$key] = $value; + + return $this; + } + + /** + * Sets the schemes on all embedded routes (unless already set). + * + * @param array|string $schemes + * + * @return $this + */ + public function setSchemes($schemes) + { + $this->schemes = $schemes; + + return $this; + } + + /** + * Sets the methods on all embedded routes (unless already set). + * + * @param array|string $methods + * + * @return $this + */ + public function setMethods($methods) + { + $this->methods = $methods; + + return $this; + } + + /** + * Adds a resource for this collection. + * + * @param ResourceInterface $resource + * + * @return $this + */ + private function addResource(ResourceInterface $resource) + { + $this->resources[] = $resource; + + return $this; + } + + /** + * Creates the final RouteCollection and returns it. + * + * @return RouteCollection + */ + public function build() + { + $routeCollection = new RouteCollection(); + + foreach ($this->routes as $name => $route) { + if ($route instanceof Route) { + $route->setDefaults(array_merge($this->defaults, $route->getDefaults())); + $route->setOptions(array_merge($this->options, $route->getOptions())); + + foreach ($this->requirements as $key => $val) { + if (!$route->hasRequirement($key)) { + $route->setRequirement($key, $val); + } + } + + if (null !== $this->prefix) { + $route->setPath('/'.$this->prefix.$route->getPath()); + } + + if (!$route->getHost()) { + $route->setHost($this->host); + } + + if (!$route->getCondition()) { + $route->setCondition($this->condition); + } + + if (!$route->getSchemes()) { + $route->setSchemes($this->schemes); + } + + if (!$route->getMethods()) { + $route->setMethods($this->methods); + } + + // auto-generate the route name if it's been marked + if ('_unnamed_route_' === substr($name, 0, 15)) { + $name = $this->generateRouteName($route); + } + + $routeCollection->add($name, $route); + } else { + /* @var self $route */ + $subCollection = $route->build(); + $subCollection->addPrefix($this->prefix); + + $routeCollection->addCollection($subCollection); + } + + foreach ($this->resources as $resource) { + $routeCollection->addResource($resource); + } + } + + return $routeCollection; + } + + /** + * Generates a route name based on details of this route. + * + * @return string + */ + private function generateRouteName(Route $route) + { + $methods = implode('_', $route->getMethods()).'_'; + + $routeName = $methods.$route->getPath(); + $routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName); + $routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName); + + // Collapse consecutive underscores down into a single underscore. + $routeName = preg_replace('/_+/', '_', $routeName); + + return $routeName; + } + + /** + * Finds a loader able to load an imported resource and loads it. + * + * @param mixed $resource A resource + * @param string|null $type The resource type or null if unknown + * + * @return RouteCollection[] + * + * @throws FileLoaderLoadException If no loader is found + */ + private function load($resource, $type = null) + { + if (null === $this->loader) { + throw new \BadMethodCallException('Cannot import other routing resources: you must pass a LoaderInterface when constructing RouteCollectionBuilder.'); + } + + if ($this->loader->supports($resource, $type)) { + $collections = $this->loader->load($resource, $type); + + return is_array($collections) ? $collections : array($collections); + } + + if (null === $resolver = $this->loader->getResolver()) { + throw new FileLoaderLoadException($resource, null, null, null, $type); + } + + if (false === $loader = $resolver->resolve($resource, $type)) { + throw new FileLoaderLoadException($resource, null, null, null, $type); + } + + $collections = $loader->load($resource, $type); + + return is_array($collections) ? $collections : array($collections); + } +} diff --git a/vendor/symfony/routing/RouteCompiler.php b/vendor/symfony/routing/RouteCompiler.php new file mode 100644 index 00000000..a64776a0 --- /dev/null +++ b/vendor/symfony/routing/RouteCompiler.php @@ -0,0 +1,319 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * RouteCompiler compiles Route instances to CompiledRoute instances. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class RouteCompiler implements RouteCompilerInterface +{ + const REGEX_DELIMITER = '#'; + + /** + * This string defines the characters that are automatically considered separators in front of + * optional placeholders (with default and no static text following). Such a single separator + * can be left out together with the optional placeholder from matching and generating URLs. + */ + const SEPARATORS = '/,;.:-_~+*=@|'; + + /** + * The maximum supported length of a PCRE subpattern name + * http://pcre.org/current/doc/html/pcre2pattern.html#SEC16. + * + * @internal + */ + const VARIABLE_MAXIMUM_LENGTH = 32; + + /** + * {@inheritdoc} + * + * @throws \InvalidArgumentException If a path variable is named _fragment + * @throws \LogicException If a variable is referenced more than once + * @throws \DomainException If a variable name starts with a digit or if it is too long to be successfully used as + * a PCRE subpattern. + */ + public static function compile(Route $route) + { + $hostVariables = array(); + $variables = array(); + $hostRegex = null; + $hostTokens = array(); + + if ('' !== $host = $route->getHost()) { + $result = self::compilePattern($route, $host, true); + + $hostVariables = $result['variables']; + $variables = $hostVariables; + + $hostTokens = $result['tokens']; + $hostRegex = $result['regex']; + } + + $path = $route->getPath(); + + $result = self::compilePattern($route, $path, false); + + $staticPrefix = $result['staticPrefix']; + + $pathVariables = $result['variables']; + + foreach ($pathVariables as $pathParam) { + if ('_fragment' === $pathParam) { + throw new \InvalidArgumentException(sprintf('Route pattern "%s" cannot contain "_fragment" as a path parameter.', $route->getPath())); + } + } + + $variables = array_merge($variables, $pathVariables); + + $tokens = $result['tokens']; + $regex = $result['regex']; + + return new CompiledRoute( + $staticPrefix, + $regex, + $tokens, + $pathVariables, + $hostRegex, + $hostTokens, + $hostVariables, + array_unique($variables) + ); + } + + private static function compilePattern(Route $route, $pattern, $isHost) + { + $tokens = array(); + $variables = array(); + $matches = array(); + $pos = 0; + $defaultSeparator = $isHost ? '.' : '/'; + $useUtf8 = preg_match('//u', $pattern); + $needsUtf8 = $route->getOption('utf8'); + + if (!$needsUtf8 && $useUtf8 && preg_match('/[\x80-\xFF]/', $pattern)) { + $needsUtf8 = true; + @trigger_error(sprintf('Using UTF-8 route patterns without setting the "utf8" option is deprecated since Symfony 3.2 and will throw a LogicException in 4.0. Turn on the "utf8" route option for pattern "%s".', $pattern), E_USER_DEPRECATED); + } + if (!$useUtf8 && $needsUtf8) { + throw new \LogicException(sprintf('Cannot mix UTF-8 requirements with non-UTF-8 pattern "%s".', $pattern)); + } + + // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable + // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself. + preg_match_all('#\{\w+\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + foreach ($matches as $match) { + $varName = substr($match[0][0], 1, -1); + // get all static text preceding the current variable + $precedingText = substr($pattern, $pos, $match[0][1] - $pos); + $pos = $match[0][1] + strlen($match[0][0]); + + if (!strlen($precedingText)) { + $precedingChar = ''; + } elseif ($useUtf8) { + preg_match('/.$/u', $precedingText, $precedingChar); + $precedingChar = $precedingChar[0]; + } else { + $precedingChar = substr($precedingText, -1); + } + $isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar); + + // A PCRE subpattern name must start with a non-digit. Also a PHP variable cannot start with a digit so the + // variable would not be usable as a Controller action argument. + if (preg_match('/^\d/', $varName)) { + throw new \DomainException(sprintf('Variable name "%s" cannot start with a digit in route pattern "%s". Please use a different name.', $varName, $pattern)); + } + if (in_array($varName, $variables)) { + throw new \LogicException(sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $pattern, $varName)); + } + + if (strlen($varName) > self::VARIABLE_MAXIMUM_LENGTH) { + throw new \DomainException(sprintf('Variable name "%s" cannot be longer than %s characters in route pattern "%s". Please use a shorter name.', $varName, self::VARIABLE_MAXIMUM_LENGTH, $pattern)); + } + + if ($isSeparator && $precedingText !== $precedingChar) { + $tokens[] = array('text', substr($precedingText, 0, -strlen($precedingChar))); + } elseif (!$isSeparator && strlen($precedingText) > 0) { + $tokens[] = array('text', $precedingText); + } + + $regexp = $route->getRequirement($varName); + if (null === $regexp) { + $followingPattern = (string) substr($pattern, $pos); + // Find the next static character after the variable that functions as a separator. By default, this separator and '/' + // are disallowed for the variable. This default requirement makes sure that optional variables can be matched at all + // and that the generating-matching-combination of URLs unambiguous, i.e. the params used for generating the URL are + // the same that will be matched. Example: new Route('/{page}.{_format}', array('_format' => 'html')) + // If {page} would also match the separating dot, {_format} would never match as {page} will eagerly consume everything. + // Also even if {_format} was not optional the requirement prevents that {page} matches something that was originally + // part of {_format} when generating the URL, e.g. _format = 'mobile.html'. + $nextSeparator = self::findNextSeparator($followingPattern, $useUtf8); + $regexp = sprintf( + '[^%s%s]+', + preg_quote($defaultSeparator, self::REGEX_DELIMITER), + $defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : '' + ); + if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) { + // When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive + // quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns. + // Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow + // after it. This optimization cannot be applied when the next char is no real separator or when the next variable is + // directly adjacent, e.g. '/{x}{y}'. + $regexp .= '+'; + } + } else { + if (!preg_match('//u', $regexp)) { + $useUtf8 = false; + } elseif (!$needsUtf8 && preg_match('/[\x80-\xFF]|(?= 0; --$i) { + $token = $tokens[$i]; + if ('variable' === $token[0] && $route->hasDefault($token[3])) { + $firstOptional = $i; + } else { + break; + } + } + } + + // compute the matching regexp + $regexp = ''; + for ($i = 0, $nbToken = count($tokens); $i < $nbToken; ++$i) { + $regexp .= self::computeRegexp($tokens, $i, $firstOptional); + } + $regexp = self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s'.($isHost ? 'i' : ''); + + // enable Utf8 matching if really required + if ($needsUtf8) { + $regexp .= 'u'; + for ($i = 0, $nbToken = count($tokens); $i < $nbToken; ++$i) { + if ('variable' === $tokens[$i][0]) { + $tokens[$i][] = true; + } + } + } + + return array( + 'staticPrefix' => self::determineStaticPrefix($route, $tokens), + 'regex' => $regexp, + 'tokens' => array_reverse($tokens), + 'variables' => $variables, + ); + } + + /** + * Determines the longest static prefix possible for a route. + * + * @param Route $route + * @param array $tokens + * + * @return string The leading static part of a route's path + */ + private static function determineStaticPrefix(Route $route, array $tokens) + { + if ('text' !== $tokens[0][0]) { + return ($route->hasDefault($tokens[0][3]) || '/' === $tokens[0][1]) ? '' : $tokens[0][1]; + } + + $prefix = $tokens[0][1]; + + if (isset($tokens[1][1]) && '/' !== $tokens[1][1] && false === $route->hasDefault($tokens[1][3])) { + $prefix .= $tokens[1][1]; + } + + return $prefix; + } + + /** + * Returns the next static character in the Route pattern that will serve as a separator. + * + * @param string $pattern The route pattern + * @param bool $useUtf8 Whether the character is encoded in UTF-8 or not + * + * @return string The next static character that functions as separator (or empty string when none available) + */ + private static function findNextSeparator($pattern, $useUtf8) + { + if ('' == $pattern) { + // return empty string if pattern is empty or false (false which can be returned by substr) + return ''; + } + // first remove all placeholders from the pattern so we can find the next real static character + if ('' === $pattern = preg_replace('#\{\w+\}#', '', $pattern)) { + return ''; + } + if ($useUtf8) { + preg_match('/^./u', $pattern, $pattern); + } + + return false !== strpos(static::SEPARATORS, $pattern[0]) ? $pattern[0] : ''; + } + + /** + * Computes the regexp used to match a specific token. It can be static text or a subpattern. + * + * @param array $tokens The route tokens + * @param int $index The index of the current token + * @param int $firstOptional The index of the first optional token + * + * @return string The regexp pattern for a single token + */ + private static function computeRegexp(array $tokens, $index, $firstOptional) + { + $token = $tokens[$index]; + if ('text' === $token[0]) { + // Text tokens + return preg_quote($token[1], self::REGEX_DELIMITER); + } else { + // Variable tokens + if (0 === $index && 0 === $firstOptional) { + // When the only token is an optional variable token, the separator is required + return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); + } else { + $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); + if ($index >= $firstOptional) { + // Enclose each optional token in a subpattern to make it optional. + // "?:" means it is non-capturing, i.e. the portion of the subject string that + // matched the optional subpattern is not passed back. + $regexp = "(?:$regexp"; + $nbTokens = count($tokens); + if ($nbTokens - 1 == $index) { + // Close the optional subpatterns + $regexp .= str_repeat(')?', $nbTokens - $firstOptional - (0 === $firstOptional ? 1 : 0)); + } + } + + return $regexp; + } + } + } +} diff --git a/vendor/symfony/routing/RouteCompilerInterface.php b/vendor/symfony/routing/RouteCompilerInterface.php new file mode 100644 index 00000000..e6f8ee6d --- /dev/null +++ b/vendor/symfony/routing/RouteCompilerInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * RouteCompilerInterface is the interface that all RouteCompiler classes must implement. + * + * @author Fabien Potencier + */ +interface RouteCompilerInterface +{ + /** + * Compiles the current route instance. + * + * @param Route $route A Route instance + * + * @return CompiledRoute A CompiledRoute instance + * + * @throws \LogicException If the Route cannot be compiled because the + * path or host pattern is invalid + */ + public static function compile(Route $route); +} diff --git a/vendor/symfony/routing/Router.php b/vendor/symfony/routing/Router.php new file mode 100644 index 00000000..6ac4205a --- /dev/null +++ b/vendor/symfony/routing/Router.php @@ -0,0 +1,388 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\ConfigCacheInterface; +use Symfony\Component\Config\ConfigCacheFactoryInterface; +use Symfony\Component\Config\ConfigCacheFactory; +use Psr\Log\LoggerInterface; +use Symfony\Component\Routing\Generator\ConfigurableRequirementsInterface; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Generator\Dumper\GeneratorDumperInterface; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; +use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; + +/** + * The Router class is an example of the integration of all pieces of the + * routing system for easier use. + * + * @author Fabien Potencier + */ +class Router implements RouterInterface, RequestMatcherInterface +{ + /** + * @var UrlMatcherInterface|null + */ + protected $matcher; + + /** + * @var UrlGeneratorInterface|null + */ + protected $generator; + + /** + * @var RequestContext + */ + protected $context; + + /** + * @var LoaderInterface + */ + protected $loader; + + /** + * @var RouteCollection|null + */ + protected $collection; + + /** + * @var mixed + */ + protected $resource; + + /** + * @var array + */ + protected $options = array(); + + /** + * @var LoggerInterface|null + */ + protected $logger; + + /** + * @var ConfigCacheFactoryInterface|null + */ + private $configCacheFactory; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + private $expressionLanguageProviders = array(); + + /** + * Constructor. + * + * @param LoaderInterface $loader A LoaderInterface instance + * @param mixed $resource The main resource to load + * @param array $options An array of options + * @param RequestContext $context The context + * @param LoggerInterface $logger A logger instance + */ + public function __construct(LoaderInterface $loader, $resource, array $options = array(), RequestContext $context = null, LoggerInterface $logger = null) + { + $this->loader = $loader; + $this->resource = $resource; + $this->logger = $logger; + $this->context = $context ?: new RequestContext(); + $this->setOptions($options); + } + + /** + * Sets options. + * + * Available options: + * + * * cache_dir: The cache directory (or null to disable caching) + * * debug: Whether to enable debugging or not (false by default) + * * generator_class: The name of a UrlGeneratorInterface implementation + * * generator_base_class: The base class for the dumped generator class + * * generator_cache_class: The class name for the dumped generator class + * * generator_dumper_class: The name of a GeneratorDumperInterface implementation + * * matcher_class: The name of a UrlMatcherInterface implementation + * * matcher_base_class: The base class for the dumped matcher class + * * matcher_dumper_class: The class name for the dumped matcher class + * * matcher_cache_class: The name of a MatcherDumperInterface implementation + * * resource_type: Type hint for the main resource (optional) + * * strict_requirements: Configure strict requirement checking for generators + * implementing ConfigurableRequirementsInterface (default is true) + * + * @param array $options An array of options + * + * @throws \InvalidArgumentException When unsupported option is provided + */ + public function setOptions(array $options) + { + $this->options = array( + 'cache_dir' => null, + 'debug' => false, + 'generator_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + 'generator_base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + 'generator_dumper_class' => 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper', + 'generator_cache_class' => 'ProjectUrlGenerator', + 'matcher_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + 'matcher_base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + 'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper', + 'matcher_cache_class' => 'ProjectUrlMatcher', + 'resource_type' => null, + 'strict_requirements' => true, + ); + + // check option names and live merge, if errors are encountered Exception will be thrown + $invalid = array(); + foreach ($options as $key => $value) { + if (array_key_exists($key, $this->options)) { + $this->options[$key] = $value; + } else { + $invalid[] = $key; + } + } + + if ($invalid) { + throw new \InvalidArgumentException(sprintf('The Router does not support the following options: "%s".', implode('", "', $invalid))); + } + } + + /** + * Sets an option. + * + * @param string $key The key + * @param mixed $value The value + * + * @throws \InvalidArgumentException + */ + public function setOption($key, $value) + { + if (!array_key_exists($key, $this->options)) { + throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); + } + + $this->options[$key] = $value; + } + + /** + * Gets an option value. + * + * @param string $key The key + * + * @return mixed The value + * + * @throws \InvalidArgumentException + */ + public function getOption($key) + { + if (!array_key_exists($key, $this->options)) { + throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); + } + + return $this->options[$key]; + } + + /** + * {@inheritdoc} + */ + public function getRouteCollection() + { + if (null === $this->collection) { + $this->collection = $this->loader->load($this->resource, $this->options['resource_type']); + } + + return $this->collection; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + + if (null !== $this->matcher) { + $this->getMatcher()->setContext($context); + } + if (null !== $this->generator) { + $this->getGenerator()->setContext($context); + } + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * Sets the ConfigCache factory to use. + * + * @param ConfigCacheFactoryInterface $configCacheFactory The factory to use + */ + public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory) + { + $this->configCacheFactory = $configCacheFactory; + } + + /** + * {@inheritdoc} + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + return $this->getGenerator()->generate($name, $parameters, $referenceType); + } + + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + return $this->getMatcher()->match($pathinfo); + } + + /** + * {@inheritdoc} + */ + public function matchRequest(Request $request) + { + $matcher = $this->getMatcher(); + if (!$matcher instanceof RequestMatcherInterface) { + // fallback to the default UrlMatcherInterface + return $matcher->match($request->getPathInfo()); + } + + return $matcher->matchRequest($request); + } + + /** + * Gets the UrlMatcher instance associated with this Router. + * + * @return UrlMatcherInterface A UrlMatcherInterface instance + */ + public function getMatcher() + { + if (null !== $this->matcher) { + return $this->matcher; + } + + if (null === $this->options['cache_dir'] || null === $this->options['matcher_cache_class']) { + $this->matcher = new $this->options['matcher_class']($this->getRouteCollection(), $this->context); + if (method_exists($this->matcher, 'addExpressionLanguageProvider')) { + foreach ($this->expressionLanguageProviders as $provider) { + $this->matcher->addExpressionLanguageProvider($provider); + } + } + + return $this->matcher; + } + + $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['matcher_cache_class'].'.php', + function (ConfigCacheInterface $cache) { + $dumper = $this->getMatcherDumperInstance(); + if (method_exists($dumper, 'addExpressionLanguageProvider')) { + foreach ($this->expressionLanguageProviders as $provider) { + $dumper->addExpressionLanguageProvider($provider); + } + } + + $options = array( + 'class' => $this->options['matcher_cache_class'], + 'base_class' => $this->options['matcher_base_class'], + ); + + $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources()); + } + ); + + require_once $cache->getPath(); + + return $this->matcher = new $this->options['matcher_cache_class']($this->context); + } + + /** + * Gets the UrlGenerator instance associated with this Router. + * + * @return UrlGeneratorInterface A UrlGeneratorInterface instance + */ + public function getGenerator() + { + if (null !== $this->generator) { + return $this->generator; + } + + if (null === $this->options['cache_dir'] || null === $this->options['generator_cache_class']) { + $this->generator = new $this->options['generator_class']($this->getRouteCollection(), $this->context, $this->logger); + } else { + $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['generator_cache_class'].'.php', + function (ConfigCacheInterface $cache) { + $dumper = $this->getGeneratorDumperInstance(); + + $options = array( + 'class' => $this->options['generator_cache_class'], + 'base_class' => $this->options['generator_base_class'], + ); + + $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources()); + } + ); + + require_once $cache->getPath(); + + $this->generator = new $this->options['generator_cache_class']($this->context, $this->logger); + } + + if ($this->generator instanceof ConfigurableRequirementsInterface) { + $this->generator->setStrictRequirements($this->options['strict_requirements']); + } + + return $this->generator; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * @return GeneratorDumperInterface + */ + protected function getGeneratorDumperInstance() + { + return new $this->options['generator_dumper_class']($this->getRouteCollection()); + } + + /** + * @return MatcherDumperInterface + */ + protected function getMatcherDumperInstance() + { + return new $this->options['matcher_dumper_class']($this->getRouteCollection()); + } + + /** + * Provides the ConfigCache factory implementation, falling back to a + * default implementation if necessary. + * + * @return ConfigCacheFactoryInterface $configCacheFactory + */ + private function getConfigCacheFactory() + { + if (null === $this->configCacheFactory) { + $this->configCacheFactory = new ConfigCacheFactory($this->options['debug']); + } + + return $this->configCacheFactory; + } +} diff --git a/vendor/symfony/routing/RouterInterface.php b/vendor/symfony/routing/RouterInterface.php new file mode 100644 index 00000000..a10ae34e --- /dev/null +++ b/vendor/symfony/routing/RouterInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; + +/** + * RouterInterface is the interface that all Router classes must implement. + * + * This interface is the concatenation of UrlMatcherInterface and UrlGeneratorInterface. + * + * @author Fabien Potencier + */ +interface RouterInterface extends UrlMatcherInterface, UrlGeneratorInterface +{ + /** + * Gets the RouteCollection instance associated with this Router. + * + * @return RouteCollection A RouteCollection instance + */ + public function getRouteCollection(); +} diff --git a/vendor/symfony/routing/Tests/Annotation/RouteTest.php b/vendor/symfony/routing/Tests/Annotation/RouteTest.php new file mode 100644 index 00000000..9af22f29 --- /dev/null +++ b/vendor/symfony/routing/Tests/Annotation/RouteTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Annotation; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Annotation\Route; + +class RouteTest extends TestCase +{ + /** + * @expectedException \BadMethodCallException + */ + public function testInvalidRouteParameter() + { + $route = new Route(array('foo' => 'bar')); + } + + /** + * @dataProvider getValidParameters + */ + public function testRouteParameters($parameter, $value, $getter) + { + $route = new Route(array($parameter => $value)); + $this->assertEquals($route->$getter(), $value); + } + + public function getValidParameters() + { + return array( + array('value', '/Blog', 'getPath'), + array('requirements', array('locale' => 'en'), 'getRequirements'), + array('options', array('compiler_class' => 'RouteCompiler'), 'getOptions'), + array('name', 'blog_index', 'getName'), + array('defaults', array('_controller' => 'MyBlogBundle:Blog:index'), 'getDefaults'), + array('schemes', array('https'), 'getSchemes'), + array('methods', array('GET', 'POST'), 'getMethods'), + array('host', '{locale}.example.com', 'getHost'), + array('condition', 'context.getMethod() == "GET"', 'getCondition'), + ); + } +} diff --git a/vendor/symfony/routing/Tests/CompiledRouteTest.php b/vendor/symfony/routing/Tests/CompiledRouteTest.php new file mode 100644 index 00000000..c5531795 --- /dev/null +++ b/vendor/symfony/routing/Tests/CompiledRouteTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\CompiledRoute; + +class CompiledRouteTest extends TestCase +{ + public function testAccessors() + { + $compiled = new CompiledRoute('prefix', 'regex', array('tokens'), array(), array(), array(), array(), array('variables')); + $this->assertEquals('prefix', $compiled->getStaticPrefix(), '__construct() takes a static prefix as its second argument'); + $this->assertEquals('regex', $compiled->getRegex(), '__construct() takes a regexp as its third argument'); + $this->assertEquals(array('tokens'), $compiled->getTokens(), '__construct() takes an array of tokens as its fourth argument'); + $this->assertEquals(array('variables'), $compiled->getVariables(), '__construct() takes an array of variables as its ninth argument'); + } +} diff --git a/vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.php b/vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.php new file mode 100644 index 00000000..97a34c96 --- /dev/null +++ b/vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass; + +class RoutingResolverPassTest extends TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + $container->register('routing.resolver', LoaderResolver::class); + $container->register('loader1')->addTag('routing.loader'); + $container->register('loader2')->addTag('routing.loader'); + + (new RoutingResolverPass())->process($container); + + $this->assertEquals( + array(array('addLoader', array(new Reference('loader1'))), array('addLoader', array(new Reference('loader2')))), + $container->getDefinition('routing.resolver')->getMethodCalls() + ); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php new file mode 100644 index 00000000..56bcab2a --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses; + +abstract class AbstractClass +{ +} diff --git a/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php new file mode 100644 index 00000000..a3882773 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses; + +class BarClass +{ + public function routeAction($arg1, $arg2 = 'defaultValue2', $arg3 = 'defaultValue3') + { + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php new file mode 100644 index 00000000..471968b5 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses; + +class BazClass +{ + public function __invoke() + { + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php new file mode 100644 index 00000000..320dc350 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses; + +class FooClass +{ +} diff --git a/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php new file mode 100644 index 00000000..ee8f4b07 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures; + +use Symfony\Component\Routing\CompiledRoute; + +class CustomCompiledRoute extends CompiledRoute +{ +} diff --git a/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php b/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php new file mode 100644 index 00000000..c2e2afd9 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCompiler; + +class CustomRouteCompiler extends RouteCompiler +{ + /** + * {@inheritdoc} + */ + public static function compile(Route $route) + { + return new CustomCompiledRoute('', '', array(), array()); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php b/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php new file mode 100644 index 00000000..9fd5754a --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures; + +use Symfony\Component\Routing\Loader\XmlFileLoader; +use Symfony\Component\Config\Util\XmlUtils; + +/** + * XmlFileLoader with schema validation turned off. + */ +class CustomXmlFileLoader extends XmlFileLoader +{ + protected function loadFile($file) + { + return XmlUtils::loadFile($file, function () { return true; }); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php b/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php new file mode 100644 index 00000000..8900d34e --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php @@ -0,0 +1,3 @@ +class NoStartTagClass +{ +} diff --git a/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php b/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php new file mode 100644 index 00000000..729c9b4d --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\OtherAnnotatedClasses; + +class VariadicClass +{ + public function routeAction(...$params) + { + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php b/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php new file mode 100644 index 00000000..15937bcf --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures; + +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; + +/** + * @author Fabien Potencier + */ +class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface +{ + public function redirect($path, $route, $scheme = null) + { + return array( + '_controller' => 'Some controller reference...', + 'path' => $path, + 'scheme' => $scheme, + ); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/annotated.php b/vendor/symfony/routing/Tests/Fixtures/annotated.php new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/routing/Tests/Fixtures/bad_format.yml b/vendor/symfony/routing/Tests/Fixtures/bad_format.yml new file mode 100644 index 00000000..8ba50e2e --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/bad_format.yml @@ -0,0 +1,3 @@ +blog_show: + path: /blog/{slug} + defaults: { _controller: "MyBundle:Blog:show" } diff --git a/vendor/symfony/routing/Tests/Fixtures/bar.xml b/vendor/symfony/routing/Tests/Fixtures/bar.xml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml b/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml new file mode 100644 index 00000000..d0788366 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml @@ -0,0 +1,2 @@ +route1: + path: /route/1 diff --git a/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml b/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml new file mode 100644 index 00000000..938fb245 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml @@ -0,0 +1,2 @@ +route2: + path: /route/2 diff --git a/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml b/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml new file mode 100644 index 00000000..088cfb4d --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml @@ -0,0 +1,2 @@ +route3: + path: /route/3 diff --git a/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml b/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml new file mode 100644 index 00000000..af829e58 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml @@ -0,0 +1,3 @@ +_directory: + resource: "../directory" + type: directory diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.apache b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.apache new file mode 100644 index 00000000..26a561cc --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.apache @@ -0,0 +1,163 @@ +# skip "real" requests +RewriteCond %{REQUEST_FILENAME} -f +RewriteRule .* - [QSA,L] + +# foo +RewriteCond %{REQUEST_URI} ^/foo/(baz|symfony)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:foo,E=_ROUTING_param_bar:%1,E=_ROUTING_default_def:test] + +# foobar +RewriteCond %{REQUEST_URI} ^/foo(?:/([^/]++))?$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:foobar,E=_ROUTING_param_bar:%1,E=_ROUTING_default_bar:toto] + +# bar +RewriteCond %{REQUEST_URI} ^/bar/([^/]++)$ +RewriteCond %{REQUEST_METHOD} !^(GET|HEAD)$ [NC] +RewriteRule .* - [S=1,E=_ROUTING_allow_GET:1,E=_ROUTING_allow_HEAD:1] +RewriteCond %{REQUEST_URI} ^/bar/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:bar,E=_ROUTING_param_foo:%1] + +# baragain +RewriteCond %{REQUEST_URI} ^/baragain/([^/]++)$ +RewriteCond %{REQUEST_METHOD} !^(GET|POST|HEAD)$ [NC] +RewriteRule .* - [S=1,E=_ROUTING_allow_GET:1,E=_ROUTING_allow_POST:1,E=_ROUTING_allow_HEAD:1] +RewriteCond %{REQUEST_URI} ^/baragain/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baragain,E=_ROUTING_param_foo:%1] + +# baz +RewriteCond %{REQUEST_URI} ^/test/baz$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz] + +# baz2 +RewriteCond %{REQUEST_URI} ^/test/baz\.html$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz2] + +# baz3 +RewriteCond %{REQUEST_URI} ^/test/baz3$ +RewriteRule .* $0/ [QSA,L,R=301] +RewriteCond %{REQUEST_URI} ^/test/baz3/$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz3] + +# baz4 +RewriteCond %{REQUEST_URI} ^/test/([^/]++)$ +RewriteRule .* $0/ [QSA,L,R=301] +RewriteCond %{REQUEST_URI} ^/test/([^/]++)/$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz4,E=_ROUTING_param_foo:%1] + +# baz5 +RewriteCond %{REQUEST_URI} ^/test/([^/]++)/$ +RewriteCond %{REQUEST_METHOD} !^(GET|HEAD)$ [NC] +RewriteRule .* - [S=2,E=_ROUTING_allow_GET:1,E=_ROUTING_allow_HEAD:1] +RewriteCond %{REQUEST_URI} ^/test/([^/]++)$ +RewriteRule .* $0/ [QSA,L,R=301] +RewriteCond %{REQUEST_URI} ^/test/([^/]++)/$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz5,E=_ROUTING_param_foo:%1] + +# baz5unsafe +RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]++)/$ +RewriteCond %{REQUEST_METHOD} !^(POST)$ [NC] +RewriteRule .* - [S=1,E=_ROUTING_allow_POST:1] +RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]++)/$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz5unsafe,E=_ROUTING_param_foo:%1] + +# baz6 +RewriteCond %{REQUEST_URI} ^/test/baz$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz6,E=_ROUTING_default_foo:bar\ baz] + +# baz7 +RewriteCond %{REQUEST_URI} ^/te\ st/baz$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz7] + +# baz8 +RewriteCond %{REQUEST_URI} ^/te\\\ st/baz$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz8] + +# baz9 +RewriteCond %{REQUEST_URI} ^/test/(te\\\ st)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz9,E=_ROUTING_param_baz:%1] + +RewriteCond %{HTTP:Host} ^a\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_1:1] + +# route1 +RewriteCond %{ENV:__ROUTING_host_1} =1 +RewriteCond %{REQUEST_URI} ^/route1$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route1] + +# route2 +RewriteCond %{ENV:__ROUTING_host_1} =1 +RewriteCond %{REQUEST_URI} ^/c2/route2$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route2] + +RewriteCond %{HTTP:Host} ^b\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_2:1] + +# route3 +RewriteCond %{ENV:__ROUTING_host_2} =1 +RewriteCond %{REQUEST_URI} ^/c2/route3$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route3] + +RewriteCond %{HTTP:Host} ^a\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_3:1] + +# route4 +RewriteCond %{ENV:__ROUTING_host_3} =1 +RewriteCond %{REQUEST_URI} ^/route4$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route4] + +RewriteCond %{HTTP:Host} ^c\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_4:1] + +# route5 +RewriteCond %{ENV:__ROUTING_host_4} =1 +RewriteCond %{REQUEST_URI} ^/route5$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route5] + +# route6 +RewriteCond %{REQUEST_URI} ^/route6$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route6] + +RewriteCond %{HTTP:Host} ^([^\.]++)\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_5:1,E=__ROUTING_host_5_var1:%1] + +# route11 +RewriteCond %{ENV:__ROUTING_host_5} =1 +RewriteCond %{REQUEST_URI} ^/route11$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route11,E=_ROUTING_param_var1:%{ENV:__ROUTING_host_5_var1}] + +# route12 +RewriteCond %{ENV:__ROUTING_host_5} =1 +RewriteCond %{REQUEST_URI} ^/route12$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route12,E=_ROUTING_param_var1:%{ENV:__ROUTING_host_5_var1},E=_ROUTING_default_var1:val] + +# route13 +RewriteCond %{ENV:__ROUTING_host_5} =1 +RewriteCond %{REQUEST_URI} ^/route13/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route13,E=_ROUTING_param_var1:%{ENV:__ROUTING_host_5_var1},E=_ROUTING_param_name:%1] + +# route14 +RewriteCond %{ENV:__ROUTING_host_5} =1 +RewriteCond %{REQUEST_URI} ^/route14/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route14,E=_ROUTING_param_var1:%{ENV:__ROUTING_host_5_var1},E=_ROUTING_param_name:%1,E=_ROUTING_default_var1:val] + +RewriteCond %{HTTP:Host} ^c\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_6:1] + +# route15 +RewriteCond %{ENV:__ROUTING_host_6} =1 +RewriteCond %{REQUEST_URI} ^/route15/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route15,E=_ROUTING_param_name:%1] + +# route16 +RewriteCond %{REQUEST_URI} ^/route16/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route16,E=_ROUTING_param_name:%1,E=_ROUTING_default_var1:val] + +# route17 +RewriteCond %{REQUEST_URI} ^/route17$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route17] + +# 405 Method Not Allowed +RewriteCond %{ENV:_ROUTING__allow_GET} =1 [OR] +RewriteCond %{ENV:_ROUTING__allow_HEAD} =1 [OR] +RewriteCond %{ENV:_ROUTING__allow_POST} =1 +RewriteRule .* app.php [QSA,L] diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php new file mode 100644 index 00000000..8ae0ee96 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php @@ -0,0 +1,317 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request; + $requestMethod = $canonicalMethod = $context->getMethod(); + $scheme = $context->getScheme(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + + if (0 === strpos($pathinfo, '/foo')) { + // foo + if (preg_match('#^/foo/(?Pbaz|symfony)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',)); + } + + // foofoo + if ('/foofoo' === $pathinfo) { + return array ( 'def' => 'test', '_route' => 'foofoo',); + } + + } + + elseif (0 === strpos($pathinfo, '/bar')) { + // bar + if (preg_match('#^/bar/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_bar; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ()); + } + not_bar: + + // barhead + if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_barhead; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ()); + } + not_barhead: + + } + + elseif (0 === strpos($pathinfo, '/test')) { + if (0 === strpos($pathinfo, '/test/baz')) { + // baz + if ('/test/baz' === $pathinfo) { + return array('_route' => 'baz'); + } + + // baz2 + if ('/test/baz.html' === $pathinfo) { + return array('_route' => 'baz2'); + } + + // baz3 + if ('/test/baz3/' === $pathinfo) { + return array('_route' => 'baz3'); + } + + } + + // baz4 + if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ()); + } + + // baz5 + if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_baz5; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ()); + } + not_baz5: + + // baz.baz6 + if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('PUT' !== $canonicalMethod) { + $allow[] = 'PUT'; + goto not_bazbaz6; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ()); + } + not_bazbaz6: + + } + + // quoter + if (preg_match('#^/(?P[\']+)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ()); + } + + // space + if ('/spa ce' === $pathinfo) { + return array('_route' => 'space'); + } + + if (0 === strpos($pathinfo, '/a')) { + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ()); + } + + // bar1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ()); + } + + } + + // overridden + if (preg_match('#^/a/(?P.*)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ()); + } + + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ()); + } + + // bar2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ()); + } + + } + + } + + elseif (0 === strpos($pathinfo, '/multi')) { + // helloWorld + if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P[^/]++))?$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',)); + } + + // hey + if ('/multi/hey/' === $pathinfo) { + return array('_route' => 'hey'); + } + + // overridden2 + if ('/multi/new' === $pathinfo) { + return array('_route' => 'overridden2'); + } + + } + + // foo3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ()); + } + + // bar3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ()); + } + + if (0 === strpos($pathinfo, '/aba')) { + // ababa + if ('/ababa' === $pathinfo) { + return array('_route' => 'ababa'); + } + + // foo4 + if (preg_match('#^/aba/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ()); + } + + } + + $host = $context->getHost(); + + if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + // route1 + if ('/route1' === $pathinfo) { + return array('_route' => 'route1'); + } + + // route2 + if ('/c2/route2' === $pathinfo) { + return array('_route' => 'route2'); + } + + } + + if (preg_match('#^b\\.example\\.com$#si', $host, $hostMatches)) { + // route3 + if ('/c2/route3' === $pathinfo) { + return array('_route' => 'route3'); + } + + } + + if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + // route4 + if ('/route4' === $pathinfo) { + return array('_route' => 'route4'); + } + + } + + if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) { + // route5 + if ('/route5' === $pathinfo) { + return array('_route' => 'route5'); + } + + } + + // route6 + if ('/route6' === $pathinfo) { + return array('_route' => 'route6'); + } + + if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#si', $host, $hostMatches)) { + if (0 === strpos($pathinfo, '/route1')) { + // route11 + if ('/route11' === $pathinfo) { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route11')), array ()); + } + + // route12 + if ('/route12' === $pathinfo) { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route12')), array ( 'var1' => 'val',)); + } + + // route13 + if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route13')), array ()); + } + + // route14 + if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',)); + } + + } + + } + + if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) { + // route15 + if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ()); + } + + } + + // route16 + if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',)); + } + + // route17 + if ('/route17' === $pathinfo) { + return array('_route' => 'route17'); + } + + // a + if ('/a/a...' === $pathinfo) { + return array('_route' => 'a'); + } + + if (0 === strpos($pathinfo, '/a/b')) { + // b + if (preg_match('#^/a/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ()); + } + + // c + if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ()); + } + + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.apache b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.apache new file mode 100644 index 00000000..309f2ff0 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.apache @@ -0,0 +1,7 @@ +# skip "real" requests +RewriteCond %{REQUEST_FILENAME} -f +RewriteRule .* - [QSA,L] + +# foo +RewriteCond %{REQUEST_URI} ^/foo$ +RewriteRule .* ap\ p_d\ ev.php [QSA,L,E=_ROUTING_route:foo] diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php new file mode 100644 index 00000000..91ec4789 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php @@ -0,0 +1,349 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request; + $requestMethod = $canonicalMethod = $context->getMethod(); + $scheme = $context->getScheme(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + + if (0 === strpos($pathinfo, '/foo')) { + // foo + if (preg_match('#^/foo/(?Pbaz|symfony)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',)); + } + + // foofoo + if ('/foofoo' === $pathinfo) { + return array ( 'def' => 'test', '_route' => 'foofoo',); + } + + } + + elseif (0 === strpos($pathinfo, '/bar')) { + // bar + if (preg_match('#^/bar/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_bar; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ()); + } + not_bar: + + // barhead + if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_barhead; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ()); + } + not_barhead: + + } + + elseif (0 === strpos($pathinfo, '/test')) { + if (0 === strpos($pathinfo, '/test/baz')) { + // baz + if ('/test/baz' === $pathinfo) { + return array('_route' => 'baz'); + } + + // baz2 + if ('/test/baz.html' === $pathinfo) { + return array('_route' => 'baz2'); + } + + // baz3 + if ('/test/baz3' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'baz3'); + } + + return array('_route' => 'baz3'); + } + + } + + // baz4 + if (preg_match('#^/test/(?P[^/]++)/?$#s', $pathinfo, $matches)) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'baz4'); + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ()); + } + + // baz5 + if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_baz5; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ()); + } + not_baz5: + + // baz.baz6 + if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('PUT' !== $canonicalMethod) { + $allow[] = 'PUT'; + goto not_bazbaz6; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ()); + } + not_bazbaz6: + + } + + // quoter + if (preg_match('#^/(?P[\']+)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ()); + } + + // space + if ('/spa ce' === $pathinfo) { + return array('_route' => 'space'); + } + + if (0 === strpos($pathinfo, '/a')) { + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ()); + } + + // bar1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ()); + } + + } + + // overridden + if (preg_match('#^/a/(?P.*)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ()); + } + + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ()); + } + + // bar2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ()); + } + + } + + } + + elseif (0 === strpos($pathinfo, '/multi')) { + // helloWorld + if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P[^/]++))?$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',)); + } + + // hey + if ('/multi/hey' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'hey'); + } + + return array('_route' => 'hey'); + } + + // overridden2 + if ('/multi/new' === $pathinfo) { + return array('_route' => 'overridden2'); + } + + } + + // foo3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ()); + } + + // bar3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ()); + } + + if (0 === strpos($pathinfo, '/aba')) { + // ababa + if ('/ababa' === $pathinfo) { + return array('_route' => 'ababa'); + } + + // foo4 + if (preg_match('#^/aba/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ()); + } + + } + + $host = $context->getHost(); + + if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + // route1 + if ('/route1' === $pathinfo) { + return array('_route' => 'route1'); + } + + // route2 + if ('/c2/route2' === $pathinfo) { + return array('_route' => 'route2'); + } + + } + + if (preg_match('#^b\\.example\\.com$#si', $host, $hostMatches)) { + // route3 + if ('/c2/route3' === $pathinfo) { + return array('_route' => 'route3'); + } + + } + + if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + // route4 + if ('/route4' === $pathinfo) { + return array('_route' => 'route4'); + } + + } + + if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) { + // route5 + if ('/route5' === $pathinfo) { + return array('_route' => 'route5'); + } + + } + + // route6 + if ('/route6' === $pathinfo) { + return array('_route' => 'route6'); + } + + if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#si', $host, $hostMatches)) { + if (0 === strpos($pathinfo, '/route1')) { + // route11 + if ('/route11' === $pathinfo) { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route11')), array ()); + } + + // route12 + if ('/route12' === $pathinfo) { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route12')), array ( 'var1' => 'val',)); + } + + // route13 + if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route13')), array ()); + } + + // route14 + if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',)); + } + + } + + } + + if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) { + // route15 + if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ()); + } + + } + + // route16 + if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',)); + } + + // route17 + if ('/route17' === $pathinfo) { + return array('_route' => 'route17'); + } + + // a + if ('/a/a...' === $pathinfo) { + return array('_route' => 'a'); + } + + if (0 === strpos($pathinfo, '/a/b')) { + // b + if (preg_match('#^/a/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ()); + } + + // c + if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ()); + } + + } + + // secure + if ('/secure' === $pathinfo) { + $requiredSchemes = array ( 'https' => 0,); + if (!isset($requiredSchemes[$scheme])) { + return $this->redirect($pathinfo, 'secure', key($requiredSchemes)); + } + + return array('_route' => 'secure'); + } + + // nonsecure + if ('/nonsecure' === $pathinfo) { + $requiredSchemes = array ( 'http' => 0,); + if (!isset($requiredSchemes[$scheme])) { + return $this->redirect($pathinfo, 'nonsecure', key($requiredSchemes)); + } + + return array('_route' => 'nonsecure'); + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php new file mode 100644 index 00000000..48cd6dfa --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php @@ -0,0 +1,58 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request; + $requestMethod = $canonicalMethod = $context->getMethod(); + $scheme = $context->getScheme(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + + if (0 === strpos($pathinfo, '/rootprefix')) { + // static + if ('/rootprefix/test' === $pathinfo) { + return array('_route' => 'static'); + } + + // dynamic + if (preg_match('#^/rootprefix/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'dynamic')), array ()); + } + + } + + // with-condition + if ('/with-condition' === $pathinfo && ($context->getMethod() == "GET")) { + return array('_route' => 'with-condition'); + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php new file mode 100644 index 00000000..c638dca2 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php @@ -0,0 +1,109 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request; + $requestMethod = $canonicalMethod = $context->getMethod(); + $scheme = $context->getScheme(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + + // just_head + if ('/just_head' === $pathinfo) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_just_head; + } + + return array('_route' => 'just_head'); + } + not_just_head: + + // head_and_get + if ('/head_and_get' === $pathinfo) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_head_and_get; + } + + return array('_route' => 'head_and_get'); + } + not_head_and_get: + + // get_and_head + if ('/get_and_head' === $pathinfo) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_get_and_head; + } + + return array('_route' => 'get_and_head'); + } + not_get_and_head: + + // post_and_head + if ('/post_and_get' === $pathinfo) { + if (!in_array($requestMethod, array('POST', 'HEAD'))) { + $allow = array_merge($allow, array('POST', 'HEAD')); + goto not_post_and_head; + } + + return array('_route' => 'post_and_head'); + } + not_post_and_head: + + if (0 === strpos($pathinfo, '/put_and_post')) { + // put_and_post + if ('/put_and_post' === $pathinfo) { + if (!in_array($requestMethod, array('PUT', 'POST'))) { + $allow = array_merge($allow, array('PUT', 'POST')); + goto not_put_and_post; + } + + return array('_route' => 'put_and_post'); + } + not_put_and_post: + + // put_and_get_and_head + if ('/put_and_post' === $pathinfo) { + if (!in_array($canonicalMethod, array('PUT', 'GET'))) { + $allow = array_merge($allow, array('PUT', 'GET')); + goto not_put_and_get_and_head; + } + + return array('_route' => 'put_and_get_and_head'); + } + not_put_and_get_and_head: + + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php new file mode 100644 index 00000000..1e6824b3 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php @@ -0,0 +1,158 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request; + $requestMethod = $canonicalMethod = $context->getMethod(); + $scheme = $context->getScheme(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + + if (0 === strpos($pathinfo, '/a')) { + // a_first + if ('/a/11' === $pathinfo) { + return array('_route' => 'a_first'); + } + + // a_second + if ('/a/22' === $pathinfo) { + return array('_route' => 'a_second'); + } + + // a_third + if ('/a/333' === $pathinfo) { + return array('_route' => 'a_third'); + } + + } + + // a_wildcard + if (preg_match('#^/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'a_wildcard')), array ()); + } + + if (0 === strpos($pathinfo, '/a')) { + // a_fourth + if ('/a/44' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'a_fourth'); + } + + return array('_route' => 'a_fourth'); + } + + // a_fifth + if ('/a/55' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'a_fifth'); + } + + return array('_route' => 'a_fifth'); + } + + // a_sixth + if ('/a/66' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'a_sixth'); + } + + return array('_route' => 'a_sixth'); + } + + } + + // nested_wildcard + if (0 === strpos($pathinfo, '/nested') && preg_match('#^/nested/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'nested_wildcard')), array ()); + } + + if (0 === strpos($pathinfo, '/nested/group')) { + // nested_a + if ('/nested/group/a' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'nested_a'); + } + + return array('_route' => 'nested_a'); + } + + // nested_b + if ('/nested/group/b' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'nested_b'); + } + + return array('_route' => 'nested_b'); + } + + // nested_c + if ('/nested/group/c' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'nested_c'); + } + + return array('_route' => 'nested_c'); + } + + } + + elseif (0 === strpos($pathinfo, '/slashed/group')) { + // slashed_a + if ('/slashed/group' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'slashed_a'); + } + + return array('_route' => 'slashed_a'); + } + + // slashed_b + if ('/slashed/group/b' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'slashed_b'); + } + + return array('_route' => 'slashed_b'); + } + + // slashed_c + if ('/slashed/group/c' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'slashed_c'); + } + + return array('_route' => 'slashed_c'); + } + + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php new file mode 100644 index 00000000..c7645521 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php @@ -0,0 +1,204 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request; + $requestMethod = $canonicalMethod = $context->getMethod(); + $scheme = $context->getScheme(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + + if (0 === strpos($pathinfo, '/trailing/simple')) { + // simple_trailing_slash_no_methods + if ('/trailing/simple/no-methods/' === $pathinfo) { + return array('_route' => 'simple_trailing_slash_no_methods'); + } + + // simple_trailing_slash_GET_method + if ('/trailing/simple/get-method/' === $pathinfo) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_simple_trailing_slash_GET_method; + } + + return array('_route' => 'simple_trailing_slash_GET_method'); + } + not_simple_trailing_slash_GET_method: + + // simple_trailing_slash_HEAD_method + if ('/trailing/simple/head-method/' === $pathinfo) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_simple_trailing_slash_HEAD_method; + } + + return array('_route' => 'simple_trailing_slash_HEAD_method'); + } + not_simple_trailing_slash_HEAD_method: + + // simple_trailing_slash_POST_method + if ('/trailing/simple/post-method/' === $pathinfo) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_simple_trailing_slash_POST_method; + } + + return array('_route' => 'simple_trailing_slash_POST_method'); + } + not_simple_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/trailing/regex')) { + // regex_trailing_slash_no_methods + if (0 === strpos($pathinfo, '/trailing/regex/no-methods') && preg_match('#^/trailing/regex/no\\-methods/(?P[^/]++)/$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_no_methods')), array ()); + } + + // regex_trailing_slash_GET_method + if (0 === strpos($pathinfo, '/trailing/regex/get-method') && preg_match('#^/trailing/regex/get\\-method/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_regex_trailing_slash_GET_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_GET_method')), array ()); + } + not_regex_trailing_slash_GET_method: + + // regex_trailing_slash_HEAD_method + if (0 === strpos($pathinfo, '/trailing/regex/head-method') && preg_match('#^/trailing/regex/head\\-method/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_regex_trailing_slash_HEAD_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_HEAD_method')), array ()); + } + not_regex_trailing_slash_HEAD_method: + + // regex_trailing_slash_POST_method + if (0 === strpos($pathinfo, '/trailing/regex/post-method') && preg_match('#^/trailing/regex/post\\-method/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_regex_trailing_slash_POST_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_POST_method')), array ()); + } + not_regex_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/not-trailing/simple')) { + // simple_not_trailing_slash_no_methods + if ('/not-trailing/simple/no-methods' === $pathinfo) { + return array('_route' => 'simple_not_trailing_slash_no_methods'); + } + + // simple_not_trailing_slash_GET_method + if ('/not-trailing/simple/get-method' === $pathinfo) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_simple_not_trailing_slash_GET_method; + } + + return array('_route' => 'simple_not_trailing_slash_GET_method'); + } + not_simple_not_trailing_slash_GET_method: + + // simple_not_trailing_slash_HEAD_method + if ('/not-trailing/simple/head-method' === $pathinfo) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_simple_not_trailing_slash_HEAD_method; + } + + return array('_route' => 'simple_not_trailing_slash_HEAD_method'); + } + not_simple_not_trailing_slash_HEAD_method: + + // simple_not_trailing_slash_POST_method + if ('/not-trailing/simple/post-method' === $pathinfo) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_simple_not_trailing_slash_POST_method; + } + + return array('_route' => 'simple_not_trailing_slash_POST_method'); + } + not_simple_not_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/not-trailing/regex')) { + // regex_not_trailing_slash_no_methods + if (0 === strpos($pathinfo, '/not-trailing/regex/no-methods') && preg_match('#^/not\\-trailing/regex/no\\-methods/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_no_methods')), array ()); + } + + // regex_not_trailing_slash_GET_method + if (0 === strpos($pathinfo, '/not-trailing/regex/get-method') && preg_match('#^/not\\-trailing/regex/get\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_regex_not_trailing_slash_GET_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_GET_method')), array ()); + } + not_regex_not_trailing_slash_GET_method: + + // regex_not_trailing_slash_HEAD_method + if (0 === strpos($pathinfo, '/not-trailing/regex/head-method') && preg_match('#^/not\\-trailing/regex/head\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_regex_not_trailing_slash_HEAD_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_HEAD_method')), array ()); + } + not_regex_not_trailing_slash_HEAD_method: + + // regex_not_trailing_slash_POST_method + if (0 === strpos($pathinfo, '/not-trailing/regex/post-method') && preg_match('#^/not\\-trailing/regex/post\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_regex_not_trailing_slash_POST_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_POST_method')), array ()); + } + not_regex_not_trailing_slash_POST_method: + + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php new file mode 100644 index 00000000..876a90f2 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php @@ -0,0 +1,228 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request; + $requestMethod = $canonicalMethod = $context->getMethod(); + $scheme = $context->getScheme(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + + if (0 === strpos($pathinfo, '/trailing/simple')) { + // simple_trailing_slash_no_methods + if ('/trailing/simple/no-methods' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'simple_trailing_slash_no_methods'); + } + + return array('_route' => 'simple_trailing_slash_no_methods'); + } + + // simple_trailing_slash_GET_method + if ('/trailing/simple/get-method' === $trimmedPathinfo) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_simple_trailing_slash_GET_method; + } + + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'simple_trailing_slash_GET_method'); + } + + return array('_route' => 'simple_trailing_slash_GET_method'); + } + not_simple_trailing_slash_GET_method: + + // simple_trailing_slash_HEAD_method + if ('/trailing/simple/head-method' === $trimmedPathinfo) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_simple_trailing_slash_HEAD_method; + } + + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'simple_trailing_slash_HEAD_method'); + } + + return array('_route' => 'simple_trailing_slash_HEAD_method'); + } + not_simple_trailing_slash_HEAD_method: + + // simple_trailing_slash_POST_method + if ('/trailing/simple/post-method/' === $pathinfo) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_simple_trailing_slash_POST_method; + } + + return array('_route' => 'simple_trailing_slash_POST_method'); + } + not_simple_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/trailing/regex')) { + // regex_trailing_slash_no_methods + if (0 === strpos($pathinfo, '/trailing/regex/no-methods') && preg_match('#^/trailing/regex/no\\-methods/(?P[^/]++)/?$#s', $pathinfo, $matches)) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'regex_trailing_slash_no_methods'); + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_no_methods')), array ()); + } + + // regex_trailing_slash_GET_method + if (0 === strpos($pathinfo, '/trailing/regex/get-method') && preg_match('#^/trailing/regex/get\\-method/(?P[^/]++)/?$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_regex_trailing_slash_GET_method; + } + + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'regex_trailing_slash_GET_method'); + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_GET_method')), array ()); + } + not_regex_trailing_slash_GET_method: + + // regex_trailing_slash_HEAD_method + if (0 === strpos($pathinfo, '/trailing/regex/head-method') && preg_match('#^/trailing/regex/head\\-method/(?P[^/]++)/?$#s', $pathinfo, $matches)) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_regex_trailing_slash_HEAD_method; + } + + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'regex_trailing_slash_HEAD_method'); + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_HEAD_method')), array ()); + } + not_regex_trailing_slash_HEAD_method: + + // regex_trailing_slash_POST_method + if (0 === strpos($pathinfo, '/trailing/regex/post-method') && preg_match('#^/trailing/regex/post\\-method/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_regex_trailing_slash_POST_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_POST_method')), array ()); + } + not_regex_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/not-trailing/simple')) { + // simple_not_trailing_slash_no_methods + if ('/not-trailing/simple/no-methods' === $pathinfo) { + return array('_route' => 'simple_not_trailing_slash_no_methods'); + } + + // simple_not_trailing_slash_GET_method + if ('/not-trailing/simple/get-method' === $pathinfo) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_simple_not_trailing_slash_GET_method; + } + + return array('_route' => 'simple_not_trailing_slash_GET_method'); + } + not_simple_not_trailing_slash_GET_method: + + // simple_not_trailing_slash_HEAD_method + if ('/not-trailing/simple/head-method' === $pathinfo) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_simple_not_trailing_slash_HEAD_method; + } + + return array('_route' => 'simple_not_trailing_slash_HEAD_method'); + } + not_simple_not_trailing_slash_HEAD_method: + + // simple_not_trailing_slash_POST_method + if ('/not-trailing/simple/post-method' === $pathinfo) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_simple_not_trailing_slash_POST_method; + } + + return array('_route' => 'simple_not_trailing_slash_POST_method'); + } + not_simple_not_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/not-trailing/regex')) { + // regex_not_trailing_slash_no_methods + if (0 === strpos($pathinfo, '/not-trailing/regex/no-methods') && preg_match('#^/not\\-trailing/regex/no\\-methods/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_no_methods')), array ()); + } + + // regex_not_trailing_slash_GET_method + if (0 === strpos($pathinfo, '/not-trailing/regex/get-method') && preg_match('#^/not\\-trailing/regex/get\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_regex_not_trailing_slash_GET_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_GET_method')), array ()); + } + not_regex_not_trailing_slash_GET_method: + + // regex_not_trailing_slash_HEAD_method + if (0 === strpos($pathinfo, '/not-trailing/regex/head-method') && preg_match('#^/not\\-trailing/regex/head\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_regex_not_trailing_slash_HEAD_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_HEAD_method')), array ()); + } + not_regex_not_trailing_slash_HEAD_method: + + // regex_not_trailing_slash_POST_method + if (0 === strpos($pathinfo, '/not-trailing/regex/post-method') && preg_match('#^/not\\-trailing/regex/post\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_regex_not_trailing_slash_POST_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_POST_method')), array ()); + } + not_regex_not_trailing_slash_POST_method: + + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/empty.yml b/vendor/symfony/routing/Tests/Fixtures/empty.yml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/routing/Tests/Fixtures/file_resource.yml b/vendor/symfony/routing/Tests/Fixtures/file_resource.yml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/routing/Tests/Fixtures/foo.xml b/vendor/symfony/routing/Tests/Fixtures/foo.xml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/routing/Tests/Fixtures/foo1.xml b/vendor/symfony/routing/Tests/Fixtures/foo1.xml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/routing/Tests/Fixtures/incomplete.yml b/vendor/symfony/routing/Tests/Fixtures/incomplete.yml new file mode 100644 index 00000000..df64d324 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/incomplete.yml @@ -0,0 +1,2 @@ +blog_show: + defaults: { _controller: MyBlogBundle:Blog:show } diff --git a/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml b/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml new file mode 100644 index 00000000..f93bf9c6 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml @@ -0,0 +1,20 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + true + 1 + 3.5 + foo + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml b/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml new file mode 100644 index 00000000..987086db --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + true + 1 + 3.5 + foo + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml b/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml new file mode 100644 index 00000000..32d393c5 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + true + 1 + 3.5 + foo + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml b/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml new file mode 100644 index 00000000..c70e03cc --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + + + + + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml b/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml new file mode 100644 index 00000000..47feb29b --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml @@ -0,0 +1,20 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + true + 1 + 3.5 + foo + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml b/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml new file mode 100644 index 00000000..6d770653 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + true + 1 + 3.5 + foo + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml b/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml new file mode 100644 index 00000000..2beee614 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + true + 1 + 3.5 + foo + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/map_null_values.xml b/vendor/symfony/routing/Tests/Fixtures/map_null_values.xml new file mode 100644 index 00000000..8fd8954e --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/map_null_values.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + + + + + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/missing_id.xml b/vendor/symfony/routing/Tests/Fixtures/missing_id.xml new file mode 100644 index 00000000..4ea4115f --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/missing_id.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/missing_path.xml b/vendor/symfony/routing/Tests/Fixtures/missing_path.xml new file mode 100644 index 00000000..ef5bc088 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/missing_path.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml b/vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml new file mode 100644 index 00000000..e33955ae --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml @@ -0,0 +1,16 @@ + + + + + + MyBundle:Blog:show + \w+ + en|fr|de + RouteCompiler + + 1 + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml b/vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml new file mode 100644 index 00000000..a3e94737 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml @@ -0,0 +1,3 @@ +blog_show: + resource: validpattern.yml + path: /test diff --git a/vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml b/vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml new file mode 100644 index 00000000..547cda3b --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml @@ -0,0 +1,3 @@ +blog_show: + path: /blog/{slug} + type: custom diff --git a/vendor/symfony/routing/Tests/Fixtures/nonvalid.xml b/vendor/symfony/routing/Tests/Fixtures/nonvalid.xml new file mode 100644 index 00000000..dc147d2e --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonvalid.xml @@ -0,0 +1,10 @@ + + + + + + MyBundle:Blog:show + + diff --git a/vendor/symfony/routing/Tests/Fixtures/nonvalid.yml b/vendor/symfony/routing/Tests/Fixtures/nonvalid.yml new file mode 100644 index 00000000..257cc564 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonvalid.yml @@ -0,0 +1 @@ +foo diff --git a/vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml b/vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml new file mode 100644 index 00000000..cfa9992b --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml @@ -0,0 +1 @@ +route: string diff --git a/vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml b/vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml new file mode 100644 index 00000000..015e270f --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml @@ -0,0 +1,3 @@ +someroute: + resource: path/to/some.yml + name_prefix: test_ diff --git a/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml b/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml new file mode 100644 index 00000000..863ef03b --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml @@ -0,0 +1,8 @@ + + + + + bar + diff --git a/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml b/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml new file mode 100644 index 00000000..908958c0 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml @@ -0,0 +1,12 @@ + + + + + + MyBundle:Blog:show + + baz + + diff --git a/vendor/symfony/routing/Tests/Fixtures/null_values.xml b/vendor/symfony/routing/Tests/Fixtures/null_values.xml new file mode 100644 index 00000000..f9e2aa24 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/null_values.xml @@ -0,0 +1,12 @@ + + + + + + + foo + bar + + diff --git a/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml b/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml new file mode 100644 index 00000000..ecfde280 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml @@ -0,0 +1,33 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + true + + + 1 + + + 3.5 + + + false + + + 1 + + + 0 + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml b/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml new file mode 100644 index 00000000..78be239a --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml @@ -0,0 +1,2 @@ +"#$péß^a|": + path: "true" diff --git a/vendor/symfony/routing/Tests/Fixtures/validpattern.php b/vendor/symfony/routing/Tests/Fixtures/validpattern.php new file mode 100644 index 00000000..edc16d8c --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/validpattern.php @@ -0,0 +1,18 @@ +add('blog_show', new Route( + '/blog/{slug}', + array('_controller' => 'MyBlogBundle:Blog:show'), + array('locale' => '\w+'), + array('compiler_class' => 'RouteCompiler'), + '{locale}.example.com', + array('https'), + array('GET', 'POST', 'put', 'OpTiOnS'), + 'context.getMethod() == "GET"' +)); + +return $collection; diff --git a/vendor/symfony/routing/Tests/Fixtures/validpattern.xml b/vendor/symfony/routing/Tests/Fixtures/validpattern.xml new file mode 100644 index 00000000..dbc72e46 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/validpattern.xml @@ -0,0 +1,15 @@ + + + + + + MyBundle:Blog:show + \w+ + + context.getMethod() == "GET" + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/validpattern.yml b/vendor/symfony/routing/Tests/Fixtures/validpattern.yml new file mode 100644 index 00000000..565abaaa --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/validpattern.yml @@ -0,0 +1,13 @@ +blog_show: + path: /blog/{slug} + defaults: { _controller: "MyBundle:Blog:show" } + host: "{locale}.example.com" + requirements: { 'locale': '\w+' } + methods: ['GET','POST','put','OpTiOnS'] + schemes: ['https'] + condition: 'context.getMethod() == "GET"' + options: + compiler_class: RouteCompiler + +blog_show_inherited: + path: /blog/{slug} diff --git a/vendor/symfony/routing/Tests/Fixtures/validresource.php b/vendor/symfony/routing/Tests/Fixtures/validresource.php new file mode 100644 index 00000000..482c80b2 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/validresource.php @@ -0,0 +1,18 @@ +import('validpattern.php'); +$collection->addDefaults(array( + 'foo' => 123, +)); +$collection->addRequirements(array( + 'foo' => '\d+', +)); +$collection->addOptions(array( + 'foo' => 'bar', +)); +$collection->setCondition('context.getMethod() == "POST"'); +$collection->addPrefix('/prefix'); + +return $collection; diff --git a/vendor/symfony/routing/Tests/Fixtures/validresource.xml b/vendor/symfony/routing/Tests/Fixtures/validresource.xml new file mode 100644 index 00000000..b7a15ddc --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/validresource.xml @@ -0,0 +1,13 @@ + + + + + + 123 + \d+ + + context.getMethod() == "POST" + + diff --git a/vendor/symfony/routing/Tests/Fixtures/validresource.yml b/vendor/symfony/routing/Tests/Fixtures/validresource.yml new file mode 100644 index 00000000..faf2263a --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/validresource.yml @@ -0,0 +1,8 @@ +_blog: + resource: validpattern.yml + prefix: /{foo} + defaults: { 'foo': '123' } + requirements: { 'foo': '\d+' } + options: { 'foo': 'bar' } + host: "" + condition: 'context.getMethod() == "POST"' diff --git a/vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php b/vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php new file mode 100644 index 00000000..5871420b --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php @@ -0,0 +1,5 @@ + + + diff --git a/vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php b/vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php new file mode 100644 index 00000000..f84802b3 --- /dev/null +++ b/vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Generator\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper; +use Symfony\Component\Routing\RequestContext; + +class PhpGeneratorDumperTest extends TestCase +{ + /** + * @var RouteCollection + */ + private $routeCollection; + + /** + * @var PhpGeneratorDumper + */ + private $generatorDumper; + + /** + * @var string + */ + private $testTmpFilepath; + + /** + * @var string + */ + private $largeTestTmpFilepath; + + protected function setUp() + { + parent::setUp(); + + $this->routeCollection = new RouteCollection(); + $this->generatorDumper = new PhpGeneratorDumper($this->routeCollection); + $this->testTmpFilepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.php'; + $this->largeTestTmpFilepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.large.php'; + @unlink($this->testTmpFilepath); + @unlink($this->largeTestTmpFilepath); + } + + protected function tearDown() + { + parent::tearDown(); + + @unlink($this->testTmpFilepath); + + $this->routeCollection = null; + $this->generatorDumper = null; + $this->testTmpFilepath = null; + } + + public function testDumpWithRoutes() + { + $this->routeCollection->add('Test', new Route('/testing/{foo}')); + $this->routeCollection->add('Test2', new Route('/testing2')); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump()); + include $this->testTmpFilepath; + + $projectUrlGenerator = new \ProjectUrlGenerator(new RequestContext('/app.php')); + + $absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + $absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $relativeUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH); + $relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('http://localhost/app.php/testing/bar', $absoluteUrlWithParameter); + $this->assertEquals('http://localhost/app.php/testing2', $absoluteUrlWithoutParameter); + $this->assertEquals('/app.php/testing/bar', $relativeUrlWithParameter); + $this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter); + } + + public function testDumpWithTooManyRoutes() + { + if (defined('HHVM_VERSION_ID')) { + $this->markTestSkipped('HHVM consumes too much memory on this test.'); + } + + $this->routeCollection->add('Test', new Route('/testing/{foo}')); + for ($i = 0; $i < 32769; ++$i) { + $this->routeCollection->add('route_'.$i, new Route('/route_'.$i)); + } + $this->routeCollection->add('Test2', new Route('/testing2')); + + file_put_contents($this->largeTestTmpFilepath, $this->generatorDumper->dump(array( + 'class' => 'ProjectLargeUrlGenerator', + ))); + $this->routeCollection = $this->generatorDumper = null; + include $this->largeTestTmpFilepath; + + $projectUrlGenerator = new \ProjectLargeUrlGenerator(new RequestContext('/app.php')); + + $absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + $absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $relativeUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH); + $relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('http://localhost/app.php/testing/bar', $absoluteUrlWithParameter); + $this->assertEquals('http://localhost/app.php/testing2', $absoluteUrlWithoutParameter); + $this->assertEquals('/app.php/testing/bar', $relativeUrlWithParameter); + $this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testDumpWithoutRoutes() + { + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'WithoutRoutesUrlGenerator'))); + include $this->testTmpFilepath; + + $projectUrlGenerator = new \WithoutRoutesUrlGenerator(new RequestContext('/app.php')); + + $projectUrlGenerator->generate('Test', array()); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException + */ + public function testGenerateNonExistingRoute() + { + $this->routeCollection->add('Test', new Route('/test')); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'NonExistingRoutesUrlGenerator'))); + include $this->testTmpFilepath; + + $projectUrlGenerator = new \NonExistingRoutesUrlGenerator(new RequestContext()); + $url = $projectUrlGenerator->generate('NonExisting', array()); + } + + public function testDumpForRouteWithDefaults() + { + $this->routeCollection->add('Test', new Route('/testing/{foo}', array('foo' => 'bar'))); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'DefaultRoutesUrlGenerator'))); + include $this->testTmpFilepath; + + $projectUrlGenerator = new \DefaultRoutesUrlGenerator(new RequestContext()); + $url = $projectUrlGenerator->generate('Test', array()); + + $this->assertEquals('/testing', $url); + } + + public function testDumpWithSchemeRequirement() + { + $this->routeCollection->add('Test1', new Route('/testing', array(), array(), array(), '', array('ftp', 'https'))); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'SchemeUrlGenerator'))); + include $this->testTmpFilepath; + + $projectUrlGenerator = new \SchemeUrlGenerator(new RequestContext('/app.php')); + + $absoluteUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $relativeUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('ftp://localhost/app.php/testing', $absoluteUrl); + $this->assertEquals('ftp://localhost/app.php/testing', $relativeUrl); + + $projectUrlGenerator = new \SchemeUrlGenerator(new RequestContext('/app.php', 'GET', 'localhost', 'https')); + + $absoluteUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $relativeUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('https://localhost/app.php/testing', $absoluteUrl); + $this->assertEquals('/app.php/testing', $relativeUrl); + } +} diff --git a/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php b/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php new file mode 100644 index 00000000..e334e437 --- /dev/null +++ b/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php @@ -0,0 +1,692 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Generator; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RequestContext; + +class UrlGeneratorTest extends TestCase +{ + public function testAbsoluteUrlWithPort80() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('http://localhost/app.php/testing', $url); + } + + public function testAbsoluteSecureUrlWithPort443() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes, array('scheme' => 'https'))->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('https://localhost/app.php/testing', $url); + } + + public function testAbsoluteUrlWithNonStandardPort() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes, array('httpPort' => 8080))->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('http://localhost:8080/app.php/testing', $url); + } + + public function testAbsoluteSecureUrlWithNonStandardPort() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes, array('httpsPort' => 8080, 'scheme' => 'https'))->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('https://localhost:8080/app.php/testing', $url); + } + + public function testRelativeUrlWithoutParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing', $url); + } + + public function testRelativeUrlWithParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing/bar', $url); + } + + public function testRelativeUrlWithNullParameter() + { + $routes = $this->getRoutes('test', new Route('/testing.{format}', array('format' => null))); + $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing', $url); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testRelativeUrlWithNullParameterButNotOptional() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}/bar', array('foo' => null))); + // This must raise an exception because the default requirement for "foo" is "[^/]+" which is not met with these params. + // Generating path "/testing//bar" would be wrong as matching this route would fail. + $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + } + + public function testRelativeUrlWithOptionalZeroParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{page}')); + $url = $this->getGenerator($routes)->generate('test', array('page' => 0), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing/0', $url); + } + + public function testNotPassedOptionalParameterInBetween() + { + $routes = $this->getRoutes('test', new Route('/{slug}/{page}', array('slug' => 'index', 'page' => 0))); + $this->assertSame('/app.php/index/1', $this->getGenerator($routes)->generate('test', array('page' => 1))); + $this->assertSame('/app.php/', $this->getGenerator($routes)->generate('test')); + } + + public function testRelativeUrlWithExtraParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing?foo=bar', $url); + } + + public function testAbsoluteUrlWithExtraParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('http://localhost/app.php/testing?foo=bar', $url); + } + + public function testUrlWithNullExtraParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => null), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('http://localhost/app.php/testing', $url); + } + + public function testUrlWithExtraParametersFromGlobals() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $generator = $this->getGenerator($routes); + $context = new RequestContext('/app.php'); + $context->setParameter('bar', 'bar'); + $generator->setContext($context); + $url = $generator->generate('test', array('foo' => 'bar')); + + $this->assertEquals('/app.php/testing?foo=bar', $url); + } + + public function testUrlWithGlobalParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}')); + $generator = $this->getGenerator($routes); + $context = new RequestContext('/app.php'); + $context->setParameter('foo', 'bar'); + $generator->setContext($context); + $url = $generator->generate('test', array()); + + $this->assertEquals('/app.php/testing/bar', $url); + } + + public function testGlobalParameterHasHigherPriorityThanDefault() + { + $routes = $this->getRoutes('test', new Route('/{_locale}', array('_locale' => 'en'))); + $generator = $this->getGenerator($routes); + $context = new RequestContext('/app.php'); + $context->setParameter('_locale', 'de'); + $generator->setContext($context); + $url = $generator->generate('test', array()); + + $this->assertSame('/app.php/de', $url); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException + */ + public function testGenerateWithoutRoutes() + { + $routes = $this->getRoutes('foo', new Route('/testing/{foo}')); + $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\MissingMandatoryParametersException + */ + public function testGenerateForRouteWithoutMandatoryParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}')); + $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testGenerateForRouteWithInvalidOptionalParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testGenerateForRouteWithInvalidParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => '1|2'))); + $this->getGenerator($routes)->generate('test', array('foo' => '0'), UrlGeneratorInterface::ABSOLUTE_URL); + } + + public function testGenerateForRouteWithInvalidOptionalParameterNonStrict() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $generator = $this->getGenerator($routes); + $generator->setStrictRequirements(false); + $this->assertNull($generator->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL)); + } + + public function testGenerateForRouteWithInvalidOptionalParameterNonStrictWithLogger() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $logger->expects($this->once()) + ->method('error'); + $generator = $this->getGenerator($routes, array(), $logger); + $generator->setStrictRequirements(false); + $this->assertNull($generator->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL)); + } + + public function testGenerateForRouteWithInvalidParameterButDisabledRequirementsCheck() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $generator = $this->getGenerator($routes); + $generator->setStrictRequirements(null); + $this->assertSame('/app.php/testing/bar', $generator->generate('test', array('foo' => 'bar'))); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testGenerateForRouteWithInvalidMandatoryParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => 'd+'))); + $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testGenerateForRouteWithInvalidUtf8Parameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => '\pL+'), array('utf8' => true))); + $this->getGenerator($routes)->generate('test', array('foo' => 'abc123'), UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testRequiredParamAndEmptyPassed() + { + $routes = $this->getRoutes('test', new Route('/{slug}', array(), array('slug' => '.+'))); + $this->getGenerator($routes)->generate('test', array('slug' => '')); + } + + public function testSchemeRequirementDoesNothingIfSameCurrentScheme() + { + $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('http'))); + $this->assertEquals('/app.php/', $this->getGenerator($routes)->generate('test')); + + $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('https'))); + $this->assertEquals('/app.php/', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test')); + } + + public function testSchemeRequirementForcesAbsoluteUrl() + { + $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('https'))); + $this->assertEquals('https://localhost/app.php/', $this->getGenerator($routes)->generate('test')); + + $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('http'))); + $this->assertEquals('http://localhost/app.php/', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test')); + } + + public function testSchemeRequirementCreatesUrlForFirstRequiredScheme() + { + $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('Ftp', 'https'))); + $this->assertEquals('ftp://localhost/app.php/', $this->getGenerator($routes)->generate('test')); + } + + public function testPathWithTwoStartingSlashes() + { + $routes = $this->getRoutes('test', new Route('//path-and-not-domain')); + + // this must not generate '//path-and-not-domain' because that would be a network path + $this->assertSame('/path-and-not-domain', $this->getGenerator($routes, array('BaseUrl' => ''))->generate('test')); + } + + public function testNoTrailingSlashForMultipleOptionalParameters() + { + $routes = $this->getRoutes('test', new Route('/category/{slug1}/{slug2}/{slug3}', array('slug2' => null, 'slug3' => null))); + + $this->assertEquals('/app.php/category/foo', $this->getGenerator($routes)->generate('test', array('slug1' => 'foo'))); + } + + public function testWithAnIntegerAsADefaultValue() + { + $routes = $this->getRoutes('test', new Route('/{default}', array('default' => 0))); + + $this->assertEquals('/app.php/foo', $this->getGenerator($routes)->generate('test', array('default' => 'foo'))); + } + + public function testNullForOptionalParameterIsIgnored() + { + $routes = $this->getRoutes('test', new Route('/test/{default}', array('default' => 0))); + + $this->assertEquals('/app.php/test', $this->getGenerator($routes)->generate('test', array('default' => null))); + } + + public function testQueryParamSameAsDefault() + { + $routes = $this->getRoutes('test', new Route('/test', array('page' => 1))); + + $this->assertSame('/app.php/test?page=2', $this->getGenerator($routes)->generate('test', array('page' => 2))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('page' => 1))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('page' => '1'))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test')); + } + + public function testArrayQueryParamSameAsDefault() + { + $routes = $this->getRoutes('test', new Route('/test', array('array' => array('foo', 'bar')))); + + $this->assertSame('/app.php/test?array%5B0%5D=bar&array%5B1%5D=foo', $this->getGenerator($routes)->generate('test', array('array' => array('bar', 'foo')))); + $this->assertSame('/app.php/test?array%5Ba%5D=foo&array%5Bb%5D=bar', $this->getGenerator($routes)->generate('test', array('array' => array('a' => 'foo', 'b' => 'bar')))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('array' => array('foo', 'bar')))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('array' => array(1 => 'bar', 0 => 'foo')))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test')); + } + + public function testGenerateWithSpecialRouteName() + { + $routes = $this->getRoutes('$péß^a|', new Route('/bar')); + + $this->assertSame('/app.php/bar', $this->getGenerator($routes)->generate('$péß^a|')); + } + + public function testUrlEncoding() + { + $expectedPath = '/app.php/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id' + .'/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id' + .'?query=%40%3A%5B%5D/%28%29%2A%27%22%20%2B%2C%3B-._~%26%24%3C%3E%7C%7B%7D%25%5C%5E%60%21%3Ffoo%3Dbar%23id'; + + // This tests the encoding of reserved characters that are used for delimiting of URI components (defined in RFC 3986) + // and other special ASCII chars. These chars are tested as static text path, variable path and query param. + $chars = '@:[]/()*\'" +,;-._~&$<>|{}%\\^`!?foo=bar#id'; + $routes = $this->getRoutes('test', new Route("/$chars/{varpath}", array(), array('varpath' => '.+'))); + $this->assertSame($expectedPath, $this->getGenerator($routes)->generate('test', array( + 'varpath' => $chars, + 'query' => $chars, + ))); + } + + public function testEncodingOfRelativePathSegments() + { + $routes = $this->getRoutes('test', new Route('/dir/../dir/..')); + $this->assertSame('/app.php/dir/%2E%2E/dir/%2E%2E', $this->getGenerator($routes)->generate('test')); + $routes = $this->getRoutes('test', new Route('/dir/./dir/.')); + $this->assertSame('/app.php/dir/%2E/dir/%2E', $this->getGenerator($routes)->generate('test')); + $routes = $this->getRoutes('test', new Route('/a./.a/a../..a/...')); + $this->assertSame('/app.php/a./.a/a../..a/...', $this->getGenerator($routes)->generate('test')); + } + + public function testAdjacentVariables() + { + $routes = $this->getRoutes('test', new Route('/{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => '\d+'))); + $generator = $this->getGenerator($routes); + $this->assertSame('/app.php/foo123', $generator->generate('test', array('x' => 'foo', 'y' => '123'))); + $this->assertSame('/app.php/foo123bar.xml', $generator->generate('test', array('x' => 'foo', 'y' => '123', 'z' => 'bar', '_format' => 'xml'))); + + // The default requirement for 'x' should not allow the separator '.' in this case because it would otherwise match everything + // and following optional variables like _format could never match. + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\InvalidParameterException'); + $generator->generate('test', array('x' => 'do.t', 'y' => '123', 'z' => 'bar', '_format' => 'xml')); + } + + public function testOptionalVariableWithNoRealSeparator() + { + $routes = $this->getRoutes('test', new Route('/get{what}', array('what' => 'All'))); + $generator = $this->getGenerator($routes); + + $this->assertSame('/app.php/get', $generator->generate('test')); + $this->assertSame('/app.php/getSites', $generator->generate('test', array('what' => 'Sites'))); + } + + public function testRequiredVariableWithNoRealSeparator() + { + $routes = $this->getRoutes('test', new Route('/get{what}Suffix')); + $generator = $this->getGenerator($routes); + + $this->assertSame('/app.php/getSitesSuffix', $generator->generate('test', array('what' => 'Sites'))); + } + + public function testDefaultRequirementOfVariable() + { + $routes = $this->getRoutes('test', new Route('/{page}.{_format}')); + $generator = $this->getGenerator($routes); + + $this->assertSame('/app.php/index.mobile.html', $generator->generate('test', array('page' => 'index', '_format' => 'mobile.html'))); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testDefaultRequirementOfVariableDisallowsSlash() + { + $routes = $this->getRoutes('test', new Route('/{page}.{_format}')); + $this->getGenerator($routes)->generate('test', array('page' => 'index', '_format' => 'sl/ash')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testDefaultRequirementOfVariableDisallowsNextSeparator() + { + $routes = $this->getRoutes('test', new Route('/{page}.{_format}')); + $this->getGenerator($routes)->generate('test', array('page' => 'do.t', '_format' => 'html')); + } + + public function testWithHostDifferentFromContext() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com')); + + $this->assertEquals('//fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', array('name' => 'Fabien', 'locale' => 'fr'))); + } + + public function testWithHostSameAsContext() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com')); + + $this->assertEquals('/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' => 'Fabien', 'locale' => 'fr'))); + } + + public function testWithHostSameAsContextAndAbsolute() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com')); + + $this->assertEquals('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::ABSOLUTE_URL)); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testUrlWithInvalidParameterInHost() + { + $routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com')); + $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testUrlWithInvalidParameterInHostWhenParamHasADefaultValue() + { + $routes = $this->getRoutes('test', new Route('/', array('foo' => 'bar'), array('foo' => 'bar'), array(), '{foo}.example.com')); + $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testUrlWithInvalidParameterEqualsDefaultValueInHost() + { + $routes = $this->getRoutes('test', new Route('/', array('foo' => 'baz'), array('foo' => 'bar'), array(), '{foo}.example.com')); + $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH); + } + + public function testUrlWithInvalidParameterInHostInNonStrictMode() + { + $routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com')); + $generator = $this->getGenerator($routes); + $generator->setStrictRequirements(false); + $this->assertNull($generator->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH)); + } + + public function testHostIsCaseInsensitive() + { + $routes = $this->getRoutes('test', new Route('/', array(), array('locale' => 'en|de|fr'), array(), '{locale}.FooBar.com')); + $generator = $this->getGenerator($routes); + $this->assertSame('//EN.FooBar.com/app.php/', $generator->generate('test', array('locale' => 'EN'), UrlGeneratorInterface::NETWORK_PATH)); + } + + public function testGenerateNetworkPath() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com', array('http'))); + + $this->assertSame('//fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', + array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::NETWORK_PATH), 'network path with different host' + ); + $this->assertSame('//fr.example.com/app.php/Fabien?query=string', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', + array('name' => 'Fabien', 'locale' => 'fr', 'query' => 'string'), UrlGeneratorInterface::NETWORK_PATH), 'network path although host same as context' + ); + $this->assertSame('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test', + array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::NETWORK_PATH), 'absolute URL because scheme requirement does not match context' + ); + $this->assertSame('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', + array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::ABSOLUTE_URL), 'absolute URL with same scheme because it is requested' + ); + } + + public function testGenerateRelativePath() + { + $routes = new RouteCollection(); + $routes->add('article', new Route('/{author}/{article}/')); + $routes->add('comments', new Route('/{author}/{article}/comments')); + $routes->add('host', new Route('/{article}', array(), array(), array(), '{author}.example.com')); + $routes->add('scheme', new Route('/{author}/blog', array(), array(), array(), '', array('https'))); + $routes->add('unrelated', new Route('/about')); + + $generator = $this->getGenerator($routes, array('host' => 'example.com', 'pathInfo' => '/fabien/symfony-is-great/')); + + $this->assertSame('comments', $generator->generate('comments', + array('author' => 'fabien', 'article' => 'symfony-is-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('comments?page=2', $generator->generate('comments', + array('author' => 'fabien', 'article' => 'symfony-is-great', 'page' => 2), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('../twig-is-great/', $generator->generate('article', + array('author' => 'fabien', 'article' => 'twig-is-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('../../bernhard/forms-are-great/', $generator->generate('article', + array('author' => 'bernhard', 'article' => 'forms-are-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('//bernhard.example.com/app.php/forms-are-great', $generator->generate('host', + array('author' => 'bernhard', 'article' => 'forms-are-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('https://example.com/app.php/bernhard/blog', $generator->generate('scheme', + array('author' => 'bernhard'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('../../about', $generator->generate('unrelated', + array(), UrlGeneratorInterface::RELATIVE_PATH) + ); + } + + /** + * @dataProvider provideRelativePaths + */ + public function testGetRelativePath($sourcePath, $targetPath, $expectedPath) + { + $this->assertSame($expectedPath, UrlGenerator::getRelativePath($sourcePath, $targetPath)); + } + + public function provideRelativePaths() + { + return array( + array( + '/same/dir/', + '/same/dir/', + '', + ), + array( + '/same/file', + '/same/file', + '', + ), + array( + '/', + '/file', + 'file', + ), + array( + '/', + '/dir/file', + 'dir/file', + ), + array( + '/dir/file.html', + '/dir/different-file.html', + 'different-file.html', + ), + array( + '/same/dir/extra-file', + '/same/dir/', + './', + ), + array( + '/parent/dir/', + '/parent/', + '../', + ), + array( + '/parent/dir/extra-file', + '/parent/', + '../', + ), + array( + '/a/b/', + '/x/y/z/', + '../../x/y/z/', + ), + array( + '/a/b/c/d/e', + '/a/c/d', + '../../../c/d', + ), + array( + '/a/b/c//', + '/a/b/c/', + '../', + ), + array( + '/a/b/c/', + '/a/b/c//', + './/', + ), + array( + '/root/a/b/c/', + '/root/x/b/c/', + '../../../x/b/c/', + ), + array( + '/a/b/c/d/', + '/a', + '../../../../a', + ), + array( + '/special-chars/sp%20ce/1€/mäh/e=mc²', + '/special-chars/sp%20ce/1€/<µ>/e=mc²', + '../<µ>/e=mc²', + ), + array( + 'not-rooted', + 'dir/file', + 'dir/file', + ), + array( + '//dir/', + '', + '../../', + ), + array( + '/dir/', + '/dir/file:with-colon', + './file:with-colon', + ), + array( + '/dir/', + '/dir/subdir/file:with-colon', + 'subdir/file:with-colon', + ), + array( + '/dir/', + '/dir/:subdir/', + './:subdir/', + ), + ); + } + + public function testFragmentsCanBeAppendedToUrls() + { + $routes = $this->getRoutes('test', new Route('/testing')); + + $url = $this->getGenerator($routes)->generate('test', array('_fragment' => 'frag ment'), UrlGeneratorInterface::ABSOLUTE_PATH); + $this->assertEquals('/app.php/testing#frag%20ment', $url); + + $url = $this->getGenerator($routes)->generate('test', array('_fragment' => '0'), UrlGeneratorInterface::ABSOLUTE_PATH); + $this->assertEquals('/app.php/testing#0', $url); + } + + public function testFragmentsDoNotEscapeValidCharacters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('_fragment' => '?/'), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing#?/', $url); + } + + public function testFragmentsCanBeDefinedAsDefaults() + { + $routes = $this->getRoutes('test', new Route('/testing', array('_fragment' => 'fragment'))); + $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing#fragment', $url); + } + + protected function getGenerator(RouteCollection $routes, array $parameters = array(), $logger = null) + { + $context = new RequestContext('/app.php'); + foreach ($parameters as $key => $value) { + $method = 'set'.$key; + $context->$method($value); + } + + return new UrlGenerator($routes, $context, $logger); + } + + protected function getRoutes($name, Route $route) + { + $routes = new RouteCollection(); + $routes->add($name, $route); + + return $routes; + } +} diff --git a/vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.php b/vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.php new file mode 100644 index 00000000..e8bbe8fc --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; + +abstract class AbstractAnnotationLoaderTest extends TestCase +{ + public function getReader() + { + return $this->getMockBuilder('Doctrine\Common\Annotations\Reader') + ->disableOriginalConstructor() + ->getMock() + ; + } + + public function getClassLoader($reader) + { + return $this->getMockBuilder('Symfony\Component\Routing\Loader\AnnotationClassLoader') + ->setConstructorArgs(array($reader)) + ->getMockForAbstractClass() + ; + } +} diff --git a/vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php b/vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php new file mode 100644 index 00000000..bf2ab4ac --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php @@ -0,0 +1,254 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Routing\Annotation\Route; + +class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest +{ + protected $loader; + private $reader; + + protected function setUp() + { + parent::setUp(); + + $this->reader = $this->getReader(); + $this->loader = $this->getClassLoader($this->reader); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLoadMissingClass() + { + $this->loader->load('MissingClass'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLoadAbstractClass() + { + $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\AbstractClass'); + } + + /** + * @dataProvider provideTestSupportsChecksResource + */ + public function testSupportsChecksResource($resource, $expectedSupports) + { + $this->assertSame($expectedSupports, $this->loader->supports($resource), '->supports() returns true if the resource is loadable'); + } + + public function provideTestSupportsChecksResource() + { + return array( + array('class', true), + array('\fully\qualified\class\name', true), + array('namespaced\class\without\leading\slash', true), + array('ÿClassWithLegalSpecialCharacters', true), + array('5', false), + array('foo.foo', false), + array(null, false), + ); + } + + public function testSupportsChecksTypeIfSpecified() + { + $this->assertTrue($this->loader->supports('class', 'annotation'), '->supports() checks the resource type if specified'); + $this->assertFalse($this->loader->supports('class', 'foo'), '->supports() checks the resource type if specified'); + } + + public function getLoadTests() + { + return array( + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('name' => 'route1', 'path' => '/path'), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'), + ), + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('defaults' => array('arg2' => 'foo'), 'requirements' => array('arg3' => '\w+')), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'), + ), + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('options' => array('foo' => 'bar')), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'), + ), + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('schemes' => array('https'), 'methods' => array('GET')), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'), + ), + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('condition' => 'context.getMethod() == "GET"'), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'), + ), + ); + } + + /** + * @dataProvider getLoadTests + */ + public function testLoad($className, $routeData = array(), $methodArgs = array()) + { + $routeData = array_replace(array( + 'name' => 'route', + 'path' => '/', + 'requirements' => array(), + 'options' => array(), + 'defaults' => array(), + 'schemes' => array(), + 'methods' => array(), + 'condition' => '', + ), $routeData); + + $this->reader + ->expects($this->once()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array($this->getAnnotatedRoute($routeData)))) + ; + + $routeCollection = $this->loader->load($className); + $route = $routeCollection->get($routeData['name']); + + $this->assertSame($routeData['path'], $route->getPath(), '->load preserves path annotation'); + $this->assertCount( + count($routeData['requirements']), + array_intersect_assoc($routeData['requirements'], $route->getRequirements()), + '->load preserves requirements annotation' + ); + $this->assertCount( + count($routeData['options']), + array_intersect_assoc($routeData['options'], $route->getOptions()), + '->load preserves options annotation' + ); + $this->assertCount( + count($routeData['defaults']), + $route->getDefaults(), + '->load preserves defaults annotation' + ); + $this->assertEquals($routeData['schemes'], $route->getSchemes(), '->load preserves schemes annotation'); + $this->assertEquals($routeData['methods'], $route->getMethods(), '->load preserves methods annotation'); + $this->assertSame($routeData['condition'], $route->getCondition(), '->load preserves condition annotation'); + } + + public function testClassRouteLoad() + { + $classRouteData = array( + 'path' => '/prefix', + 'schemes' => array('https'), + 'methods' => array('GET'), + ); + + $methodRouteData = array( + 'name' => 'route1', + 'path' => '/path', + 'schemes' => array('http'), + 'methods' => array('POST', 'PUT'), + ); + + $this->reader + ->expects($this->once()) + ->method('getClassAnnotation') + ->will($this->returnValue($this->getAnnotatedRoute($classRouteData))) + ; + $this->reader + ->expects($this->once()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array($this->getAnnotatedRoute($methodRouteData)))) + ; + + $routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass'); + $route = $routeCollection->get($methodRouteData['name']); + + $this->assertSame($classRouteData['path'].$methodRouteData['path'], $route->getPath(), '->load concatenates class and method route path'); + $this->assertEquals(array_merge($classRouteData['schemes'], $methodRouteData['schemes']), $route->getSchemes(), '->load merges class and method route schemes'); + $this->assertEquals(array_merge($classRouteData['methods'], $methodRouteData['methods']), $route->getMethods(), '->load merges class and method route methods'); + } + + public function testInvokableClassRouteLoad() + { + $classRouteData = array( + 'name' => 'route1', + 'path' => '/', + 'schemes' => array('https'), + 'methods' => array('GET'), + ); + + $this->reader + ->expects($this->exactly(2)) + ->method('getClassAnnotation') + ->will($this->returnValue($this->getAnnotatedRoute($classRouteData))) + ; + $this->reader + ->expects($this->once()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array())) + ; + + $routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass'); + $route = $routeCollection->get($classRouteData['name']); + + $this->assertSame($classRouteData['path'], $route->getPath(), '->load preserves class route path'); + $this->assertEquals(array_merge($classRouteData['schemes'], $classRouteData['schemes']), $route->getSchemes(), '->load preserves class route schemes'); + $this->assertEquals(array_merge($classRouteData['methods'], $classRouteData['methods']), $route->getMethods(), '->load preserves class route methods'); + } + + public function testInvokableClassWithMethodRouteLoad() + { + $classRouteData = array( + 'name' => 'route1', + 'path' => '/prefix', + 'schemes' => array('https'), + 'methods' => array('GET'), + ); + + $methodRouteData = array( + 'name' => 'route2', + 'path' => '/path', + 'schemes' => array('http'), + 'methods' => array('POST', 'PUT'), + ); + + $this->reader + ->expects($this->once()) + ->method('getClassAnnotation') + ->will($this->returnValue($this->getAnnotatedRoute($classRouteData))) + ; + $this->reader + ->expects($this->once()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array($this->getAnnotatedRoute($methodRouteData)))) + ; + + $routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass'); + $route = $routeCollection->get($classRouteData['name']); + + $this->assertNull($route, '->load ignores class route'); + + $route = $routeCollection->get($methodRouteData['name']); + + $this->assertSame($classRouteData['path'].$methodRouteData['path'], $route->getPath(), '->load concatenates class and method route path'); + $this->assertEquals(array_merge($classRouteData['schemes'], $methodRouteData['schemes']), $route->getSchemes(), '->load merges class and method route schemes'); + $this->assertEquals(array_merge($classRouteData['methods'], $methodRouteData['methods']), $route->getMethods(), '->load merges class and method route methods'); + } + + private function getAnnotatedRoute($data) + { + return new Route($data); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php b/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php new file mode 100644 index 00000000..78cec4be --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader; +use Symfony\Component\Config\FileLocator; + +class AnnotationDirectoryLoaderTest extends AbstractAnnotationLoaderTest +{ + protected $loader; + protected $reader; + + protected function setUp() + { + parent::setUp(); + + $this->reader = $this->getReader(); + $this->loader = new AnnotationDirectoryLoader(new FileLocator(), $this->getClassLoader($this->reader)); + } + + public function testLoad() + { + $this->reader->expects($this->exactly(4))->method('getClassAnnotation'); + + $this->reader + ->expects($this->any()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array())) + ; + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses'); + } + + public function testLoadIgnoresHiddenDirectories() + { + $this->expectAnnotationsToBeReadFrom(array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass', + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass', + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\FooClass', + )); + + $this->reader + ->expects($this->any()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array())) + ; + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses'); + } + + public function testSupports() + { + $fixturesDir = __DIR__.'/../Fixtures'; + + $this->assertTrue($this->loader->supports($fixturesDir), '->supports() returns true if the resource is loadable'); + $this->assertFalse($this->loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($this->loader->supports($fixturesDir, 'annotation'), '->supports() checks the resource type if specified'); + $this->assertFalse($this->loader->supports($fixturesDir, 'foo'), '->supports() checks the resource type if specified'); + } + + private function expectAnnotationsToBeReadFrom(array $classes) + { + $this->reader->expects($this->exactly(count($classes))) + ->method('getClassAnnotation') + ->with($this->callback(function (\ReflectionClass $class) use ($classes) { + return in_array($class->getName(), $classes); + })); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php b/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php new file mode 100644 index 00000000..5d54f9f9 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Routing\Loader\AnnotationFileLoader; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Annotation\Route; + +class AnnotationFileLoaderTest extends AbstractAnnotationLoaderTest +{ + protected $loader; + protected $reader; + + protected function setUp() + { + parent::setUp(); + + $this->reader = $this->getReader(); + $this->loader = new AnnotationFileLoader(new FileLocator(), $this->getClassLoader($this->reader)); + } + + public function testLoad() + { + $this->reader->expects($this->once())->method('getClassAnnotation'); + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses/FooClass.php'); + } + + /** + * @requires PHP 5.4 + */ + public function testLoadTraitWithClassConstant() + { + $this->reader->expects($this->never())->method('getClassAnnotation'); + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses/FooTrait.php'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Did you forgot to add the "loader->load(__DIR__.'/../Fixtures/OtherAnnotatedClasses/NoStartTagClass.php'); + } + + /** + * @requires PHP 5.6 + */ + public function testLoadVariadic() + { + $route = new Route(array('path' => '/path/to/{id}')); + $this->reader->expects($this->once())->method('getClassAnnotation'); + $this->reader->expects($this->once())->method('getMethodAnnotations') + ->will($this->returnValue(array($route))); + + $this->loader->load(__DIR__.'/../Fixtures/OtherAnnotatedClasses/VariadicClass.php'); + } + + public function testSupports() + { + $fixture = __DIR__.'/../Fixtures/annotated.php'; + + $this->assertTrue($this->loader->supports($fixture), '->supports() returns true if the resource is loadable'); + $this->assertFalse($this->loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($this->loader->supports($fixture, 'annotation'), '->supports() checks the resource type if specified'); + $this->assertFalse($this->loader->supports($fixture, 'foo'), '->supports() checks the resource type if specified'); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php b/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php new file mode 100644 index 00000000..5d963f86 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Loader\ClosureLoader; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class ClosureLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new ClosureLoader(); + + $closure = function () {}; + + $this->assertTrue($loader->supports($closure), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports($closure, 'closure'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports($closure, 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoad() + { + $loader = new ClosureLoader(); + + $route = new Route('/'); + $routes = $loader->load(function () use ($route) { + $routes = new RouteCollection(); + + $routes->add('foo', $route); + + return $routes; + }); + + $this->assertEquals($route, $routes->get('foo'), '->load() loads a \Closure resource'); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php b/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php new file mode 100644 index 00000000..fc29d371 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Routing\Loader\DirectoryLoader; +use Symfony\Component\Routing\Loader\YamlFileLoader; +use Symfony\Component\Routing\Loader\AnnotationFileLoader; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\RouteCollection; + +class DirectoryLoaderTest extends AbstractAnnotationLoaderTest +{ + private $loader; + private $reader; + + protected function setUp() + { + parent::setUp(); + + $locator = new FileLocator(); + $this->reader = $this->getReader(); + $this->loader = new DirectoryLoader($locator); + $resolver = new LoaderResolver(array( + new YamlFileLoader($locator), + new AnnotationFileLoader($locator, $this->getClassLoader($this->reader)), + $this->loader, + )); + $this->loader->setResolver($resolver); + } + + public function testLoadDirectory() + { + $collection = $this->loader->load(__DIR__.'/../Fixtures/directory', 'directory'); + $this->verifyCollection($collection); + } + + public function testImportDirectory() + { + $collection = $this->loader->load(__DIR__.'/../Fixtures/directory_import', 'directory'); + $this->verifyCollection($collection); + } + + private function verifyCollection(RouteCollection $collection) + { + $routes = $collection->all(); + + $this->assertCount(3, $routes, 'Three routes are loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + for ($i = 1; $i <= 3; ++$i) { + $this->assertSame('/route/'.$i, $routes['route'.$i]->getPath()); + } + } + + public function testSupports() + { + $fixturesDir = __DIR__.'/../Fixtures'; + + $this->assertFalse($this->loader->supports($fixturesDir), '->supports(*) returns false'); + + $this->assertTrue($this->loader->supports($fixturesDir, 'directory'), '->supports(*, "directory") returns true'); + $this->assertFalse($this->loader->supports($fixturesDir, 'foo'), '->supports(*, "foo") returns false'); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php b/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php new file mode 100644 index 00000000..408fa0b4 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Loader\ObjectRouteLoader; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class ObjectRouteLoaderTest extends TestCase +{ + public function testLoadCallsServiceAndReturnsCollection() + { + $loader = new ObjectRouteLoaderForTest(); + + // create a basic collection that will be returned + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $loader->loaderMap = array( + 'my_route_provider_service' => new RouteService($collection), + ); + + $actualRoutes = $loader->load( + 'my_route_provider_service:loadRoutes', + 'service' + ); + + $this->assertSame($collection, $actualRoutes); + // the service file should be listed as a resource + $this->assertNotEmpty($actualRoutes->getResources()); + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getBadResourceStrings + */ + public function testExceptionWithoutSyntax($resourceString) + { + $loader = new ObjectRouteLoaderForTest(); + $loader->load($resourceString); + } + + public function getBadResourceStrings() + { + return array( + array('Foo'), + array('Bar::baz'), + array('Foo:Bar:baz'), + ); + } + + /** + * @expectedException \LogicException + */ + public function testExceptionOnNoObjectReturned() + { + $loader = new ObjectRouteLoaderForTest(); + $loader->loaderMap = array('my_service' => 'NOT_AN_OBJECT'); + $loader->load('my_service:method'); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testExceptionOnBadMethod() + { + $loader = new ObjectRouteLoaderForTest(); + $loader->loaderMap = array('my_service' => new \stdClass()); + $loader->load('my_service:method'); + } + + /** + * @expectedException \LogicException + */ + public function testExceptionOnMethodNotReturningCollection() + { + $service = $this->getMockBuilder('stdClass') + ->setMethods(array('loadRoutes')) + ->getMock(); + $service->expects($this->once()) + ->method('loadRoutes') + ->will($this->returnValue('NOT_A_COLLECTION')); + + $loader = new ObjectRouteLoaderForTest(); + $loader->loaderMap = array('my_service' => $service); + $loader->load('my_service:loadRoutes'); + } +} + +class ObjectRouteLoaderForTest extends ObjectRouteLoader +{ + public $loaderMap = array(); + + protected function getServiceObject($id) + { + return isset($this->loaderMap[$id]) ? $this->loaderMap[$id] : null; + } +} + +class RouteService +{ + private $collection; + + public function __construct($collection) + { + $this->collection = $collection; + } + + public function loadRoutes() + { + return $this->collection; + } +} diff --git a/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php b/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php new file mode 100644 index 00000000..bda64236 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\PhpFileLoader; + +class PhpFileLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new PhpFileLoader($this->getMockBuilder('Symfony\Component\Config\FileLocator')->getMock()); + + $this->assertTrue($loader->supports('foo.php'), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports('foo.php', 'php'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports('foo.php', 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoadWithRoute() + { + $loader = new PhpFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validpattern.php'); + $routes = $routeCollection->all(); + + $this->assertCount(1, $routes, 'One route is loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('MyBlogBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('{locale}.example.com', $route->getHost()); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); + $this->assertEquals(array('https'), $route->getSchemes()); + } + } + + public function testLoadWithImport() + { + $loader = new PhpFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validresource.php'); + $routes = $routeCollection->all(); + + $this->assertCount(1, $routes, 'One route is loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/prefix/blog/{slug}', $route->getPath()); + $this->assertSame('MyBlogBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('{locale}.example.com', $route->getHost()); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); + $this->assertEquals(array('https'), $route->getSchemes()); + } + } + + public function testThatDefiningVariableInConfigFileHasNoSideEffects() + { + $locator = new FileLocator(array(__DIR__.'/../Fixtures')); + $loader = new PhpFileLoader($locator); + $routeCollection = $loader->load('with_define_path_variable.php'); + $resources = $routeCollection->getResources(); + $this->assertCount(1, $resources); + $this->assertContainsOnly('Symfony\Component\Config\Resource\ResourceInterface', $resources); + $fileResource = reset($resources); + $this->assertSame( + realpath($locator->locate('with_define_path_variable.php')), + (string) $fileResource + ); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php b/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php new file mode 100644 index 00000000..d24ec79a --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php @@ -0,0 +1,290 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\XmlFileLoader; +use Symfony\Component\Routing\Tests\Fixtures\CustomXmlFileLoader; + +class XmlFileLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new XmlFileLoader($this->getMockBuilder('Symfony\Component\Config\FileLocator')->getMock()); + + $this->assertTrue($loader->supports('foo.xml'), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports('foo.xml', 'xml'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports('foo.xml', 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoadWithRoute() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validpattern.xml'); + $route = $routeCollection->get('blog_show'); + + $this->assertInstanceOf('Symfony\Component\Routing\Route', $route); + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('{locale}.example.com', $route->getHost()); + $this->assertSame('MyBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('\w+', $route->getRequirement('locale')); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); + $this->assertEquals(array('https'), $route->getSchemes()); + $this->assertEquals('context.getMethod() == "GET"', $route->getCondition()); + } + + public function testLoadWithNamespacePrefix() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('namespaceprefix.xml'); + + $this->assertCount(1, $routeCollection->all(), 'One route is loaded'); + + $route = $routeCollection->get('blog_show'); + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('{_locale}.example.com', $route->getHost()); + $this->assertSame('MyBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('\w+', $route->getRequirement('slug')); + $this->assertSame('en|fr|de', $route->getRequirement('_locale')); + $this->assertNull($route->getDefault('slug')); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertSame(1, $route->getDefault('page')); + } + + public function testLoadWithImport() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validresource.xml'); + $routes = $routeCollection->all(); + + $this->assertCount(2, $routes, 'Two routes are loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/{foo}/blog/{slug}', $route->getPath()); + $this->assertSame('123', $route->getDefault('foo')); + $this->assertSame('\d+', $route->getRequirement('foo')); + $this->assertSame('bar', $route->getOption('foo')); + $this->assertSame('', $route->getHost()); + $this->assertSame('context.getMethod() == "POST"', $route->getCondition()); + } + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getPathsToInvalidFiles + */ + public function testLoadThrowsExceptionWithInvalidFile($filePath) + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load($filePath); + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getPathsToInvalidFiles + */ + public function testLoadThrowsExceptionWithInvalidFileEvenWithoutSchemaValidation($filePath) + { + $loader = new CustomXmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load($filePath); + } + + public function getPathsToInvalidFiles() + { + return array(array('nonvalidnode.xml'), array('nonvalidroute.xml'), array('nonvalid.xml'), array('missing_id.xml'), array('missing_path.xml')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Document types are not allowed. + */ + public function testDocTypeIsNotAllowed() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load('withdoctype.xml'); + } + + public function testNullValues() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('null_values.xml'); + $route = $routeCollection->get('blog_show'); + + $this->assertTrue($route->hasDefault('foo')); + $this->assertNull($route->getDefault('foo')); + $this->assertTrue($route->hasDefault('bar')); + $this->assertNull($route->getDefault('bar')); + $this->assertEquals('foo', $route->getDefault('foobar')); + $this->assertEquals('bar', $route->getDefault('baz')); + } + + public function testScalarDataTypeDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('scalar_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'slug' => null, + 'published' => true, + 'page' => 1, + 'price' => 3.5, + 'archived' => false, + 'free' => true, + 'locked' => false, + 'foo' => null, + 'bar' => null, + ), + $route->getDefaults() + ); + } + + public function testListDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('list_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array(true, 1, 3.5, 'foo'), + ), + $route->getDefaults() + ); + } + + public function testListInListDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('list_in_list_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array(array(true, 1, 3.5, 'foo')), + ), + $route->getDefaults() + ); + } + + public function testListInMapDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('list_in_map_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array('list' => array(true, 1, 3.5, 'foo')), + ), + $route->getDefaults() + ); + } + + public function testMapDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('map_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array( + 'public' => true, + 'page' => 1, + 'price' => 3.5, + 'title' => 'foo', + ), + ), + $route->getDefaults() + ); + } + + public function testMapInListDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('map_in_list_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array(array( + 'public' => true, + 'page' => 1, + 'price' => 3.5, + 'title' => 'foo', + )), + ), + $route->getDefaults() + ); + } + + public function testMapInMapDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('map_in_map_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array('map' => array( + 'public' => true, + 'page' => 1, + 'price' => 3.5, + 'title' => 'foo', + )), + ), + $route->getDefaults() + ); + } + + public function testNullValuesInList() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('list_null_values.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame(array(null, null, null, null, null, null), $route->getDefault('list')); + } + + public function testNullValuesInMap() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('map_null_values.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + 'boolean' => null, + 'integer' => null, + 'float' => null, + 'string' => null, + 'list' => null, + 'map' => null, + ), + $route->getDefault('map') + ); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php b/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php new file mode 100644 index 00000000..8342c266 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\YamlFileLoader; +use Symfony\Component\Config\Resource\FileResource; + +class YamlFileLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new YamlFileLoader($this->getMockBuilder('Symfony\Component\Config\FileLocator')->getMock()); + + $this->assertTrue($loader->supports('foo.yml'), '->supports() returns true if the resource is loadable'); + $this->assertTrue($loader->supports('foo.yaml'), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports('foo.yml', 'yaml'), '->supports() checks the resource type if specified'); + $this->assertTrue($loader->supports('foo.yaml', 'yaml'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports('foo.yml', 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoadDoesNothingIfEmpty() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $collection = $loader->load('empty.yml'); + + $this->assertEquals(array(), $collection->all()); + $this->assertEquals(array(new FileResource(realpath(__DIR__.'/../Fixtures/empty.yml'))), $collection->getResources()); + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getPathsToInvalidFiles + */ + public function testLoadThrowsExceptionWithInvalidFile($filePath) + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load($filePath); + } + + public function getPathsToInvalidFiles() + { + return array( + array('nonvalid.yml'), + array('nonvalid2.yml'), + array('incomplete.yml'), + array('nonvalidkeys.yml'), + array('nonesense_resource_plus_path.yml'), + array('nonesense_type_without_resource.yml'), + array('bad_format.yml'), + ); + } + + public function testLoadSpecialRouteName() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('special_route_name.yml'); + $route = $routeCollection->get('#$péß^a|'); + + $this->assertInstanceOf('Symfony\Component\Routing\Route', $route); + $this->assertSame('/true', $route->getPath()); + } + + public function testLoadWithRoute() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validpattern.yml'); + $route = $routeCollection->get('blog_show'); + + $this->assertInstanceOf('Symfony\Component\Routing\Route', $route); + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('{locale}.example.com', $route->getHost()); + $this->assertSame('MyBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('\w+', $route->getRequirement('locale')); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); + $this->assertEquals(array('https'), $route->getSchemes()); + $this->assertEquals('context.getMethod() == "GET"', $route->getCondition()); + } + + public function testLoadWithResource() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validresource.yml'); + $routes = $routeCollection->all(); + + $this->assertCount(2, $routes, 'Two routes are loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/{foo}/blog/{slug}', $route->getPath()); + $this->assertSame('123', $route->getDefault('foo')); + $this->assertSame('\d+', $route->getRequirement('foo')); + $this->assertSame('bar', $route->getOption('foo')); + $this->assertSame('', $route->getHost()); + $this->assertSame('context.getMethod() == "POST"', $route->getCondition()); + } + } +} diff --git a/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php b/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php new file mode 100644 index 00000000..823efdb8 --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Matcher\Dumper\DumperCollection; + +class DumperCollectionTest extends TestCase +{ + public function testGetRoot() + { + $a = new DumperCollection(); + + $b = new DumperCollection(); + $a->add($b); + + $c = new DumperCollection(); + $b->add($c); + + $d = new DumperCollection(); + $c->add($d); + + $this->assertSame($a, $c->getRoot()); + } +} diff --git a/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php b/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php new file mode 100644 index 00000000..4fb65f4a --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php @@ -0,0 +1,386 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class PhpMatcherDumperTest extends TestCase +{ + /** + * @expectedException \LogicException + */ + public function testDumpWhenSchemeIsUsedWithoutAProperDumper() + { + $collection = new RouteCollection(); + $collection->add('secure', new Route( + '/secure', + array(), + array(), + array(), + '', + array('https') + )); + $dumper = new PhpMatcherDumper($collection); + $dumper->dump(); + } + + /** + * @dataProvider getRouteCollections + */ + public function testDump(RouteCollection $collection, $fixture, $options = array()) + { + $basePath = __DIR__.'/../../Fixtures/dumper/'; + + $dumper = new PhpMatcherDumper($collection); + $this->assertStringEqualsFile($basePath.$fixture, $dumper->dump($options), '->dump() correctly dumps routes as optimized PHP code.'); + } + + public function getRouteCollections() + { + /* test case 1 */ + + $collection = new RouteCollection(); + + $collection->add('overridden', new Route('/overridden')); + + // defaults and requirements + $collection->add('foo', new Route( + '/foo/{bar}', + array('def' => 'test'), + array('bar' => 'baz|symfony') + )); + // method requirement + $collection->add('bar', new Route( + '/bar/{foo}', + array(), + array(), + array(), + '', + array(), + array('GET', 'head') + )); + // GET method requirement automatically adds HEAD as valid + $collection->add('barhead', new Route( + '/barhead/{foo}', + array(), + array(), + array(), + '', + array(), + array('GET') + )); + // simple + $collection->add('baz', new Route( + '/test/baz' + )); + // simple with extension + $collection->add('baz2', new Route( + '/test/baz.html' + )); + // trailing slash + $collection->add('baz3', new Route( + '/test/baz3/' + )); + // trailing slash with variable + $collection->add('baz4', new Route( + '/test/{foo}/' + )); + // trailing slash and method + $collection->add('baz5', new Route( + '/test/{foo}/', + array(), + array(), + array(), + '', + array(), + array('post') + )); + // complex name + $collection->add('baz.baz6', new Route( + '/test/{foo}/', + array(), + array(), + array(), + '', + array(), + array('put') + )); + // defaults without variable + $collection->add('foofoo', new Route( + '/foofoo', + array('def' => 'test') + )); + // pattern with quotes + $collection->add('quoter', new Route( + '/{quoter}', + array(), + array('quoter' => '[\']+') + )); + // space in pattern + $collection->add('space', new Route( + '/spa ce' + )); + + // prefixes + $collection1 = new RouteCollection(); + $collection1->add('overridden', new Route('/overridden1')); + $collection1->add('foo1', new Route('/{foo}')); + $collection1->add('bar1', new Route('/{bar}')); + $collection1->addPrefix('/b\'b'); + $collection2 = new RouteCollection(); + $collection2->addCollection($collection1); + $collection2->add('overridden', new Route('/{var}', array(), array('var' => '.*'))); + $collection1 = new RouteCollection(); + $collection1->add('foo2', new Route('/{foo1}')); + $collection1->add('bar2', new Route('/{bar1}')); + $collection1->addPrefix('/b\'b'); + $collection2->addCollection($collection1); + $collection2->addPrefix('/a'); + $collection->addCollection($collection2); + + // overridden through addCollection() and multiple sub-collections with no own prefix + $collection1 = new RouteCollection(); + $collection1->add('overridden2', new Route('/old')); + $collection1->add('helloWorld', new Route('/hello/{who}', array('who' => 'World!'))); + $collection2 = new RouteCollection(); + $collection3 = new RouteCollection(); + $collection3->add('overridden2', new Route('/new')); + $collection3->add('hey', new Route('/hey/')); + $collection2->addCollection($collection3); + $collection1->addCollection($collection2); + $collection1->addPrefix('/multi'); + $collection->addCollection($collection1); + + // "dynamic" prefix + $collection1 = new RouteCollection(); + $collection1->add('foo3', new Route('/{foo}')); + $collection1->add('bar3', new Route('/{bar}')); + $collection1->addPrefix('/b'); + $collection1->addPrefix('{_locale}'); + $collection->addCollection($collection1); + + // route between collections + $collection->add('ababa', new Route('/ababa')); + + // collection with static prefix but only one route + $collection1 = new RouteCollection(); + $collection1->add('foo4', new Route('/{foo}')); + $collection1->addPrefix('/aba'); + $collection->addCollection($collection1); + + // prefix and host + + $collection1 = new RouteCollection(); + + $route1 = new Route('/route1', array(), array(), array(), 'a.example.com'); + $collection1->add('route1', $route1); + + $route2 = new Route('/c2/route2', array(), array(), array(), 'a.example.com'); + $collection1->add('route2', $route2); + + $route3 = new Route('/c2/route3', array(), array(), array(), 'b.example.com'); + $collection1->add('route3', $route3); + + $route4 = new Route('/route4', array(), array(), array(), 'a.example.com'); + $collection1->add('route4', $route4); + + $route5 = new Route('/route5', array(), array(), array(), 'c.example.com'); + $collection1->add('route5', $route5); + + $route6 = new Route('/route6', array(), array(), array(), null); + $collection1->add('route6', $route6); + + $collection->addCollection($collection1); + + // host and variables + + $collection1 = new RouteCollection(); + + $route11 = new Route('/route11', array(), array(), array(), '{var1}.example.com'); + $collection1->add('route11', $route11); + + $route12 = new Route('/route12', array('var1' => 'val'), array(), array(), '{var1}.example.com'); + $collection1->add('route12', $route12); + + $route13 = new Route('/route13/{name}', array(), array(), array(), '{var1}.example.com'); + $collection1->add('route13', $route13); + + $route14 = new Route('/route14/{name}', array('var1' => 'val'), array(), array(), '{var1}.example.com'); + $collection1->add('route14', $route14); + + $route15 = new Route('/route15/{name}', array(), array(), array(), 'c.example.com'); + $collection1->add('route15', $route15); + + $route16 = new Route('/route16/{name}', array('var1' => 'val'), array(), array(), null); + $collection1->add('route16', $route16); + + $route17 = new Route('/route17', array(), array(), array(), null); + $collection1->add('route17', $route17); + + $collection->addCollection($collection1); + + // multiple sub-collections with a single route and a prefix each + $collection1 = new RouteCollection(); + $collection1->add('a', new Route('/a...')); + $collection2 = new RouteCollection(); + $collection2->add('b', new Route('/{var}')); + $collection3 = new RouteCollection(); + $collection3->add('c', new Route('/{var}')); + $collection3->addPrefix('/c'); + $collection2->addCollection($collection3); + $collection2->addPrefix('/b'); + $collection1->addCollection($collection2); + $collection1->addPrefix('/a'); + $collection->addCollection($collection1); + + /* test case 2 */ + + $redirectCollection = clone $collection; + + // force HTTPS redirection + $redirectCollection->add('secure', new Route( + '/secure', + array(), + array(), + array(), + '', + array('https') + )); + + // force HTTP redirection + $redirectCollection->add('nonsecure', new Route( + '/nonsecure', + array(), + array(), + array(), + '', + array('http') + )); + + /* test case 3 */ + + $rootprefixCollection = new RouteCollection(); + $rootprefixCollection->add('static', new Route('/test')); + $rootprefixCollection->add('dynamic', new Route('/{var}')); + $rootprefixCollection->addPrefix('rootprefix'); + $route = new Route('/with-condition'); + $route->setCondition('context.getMethod() == "GET"'); + $rootprefixCollection->add('with-condition', $route); + + /* test case 4 */ + $headMatchCasesCollection = new RouteCollection(); + $headMatchCasesCollection->add('just_head', new Route( + '/just_head', + array(), + array(), + array(), + '', + array(), + array('HEAD') + )); + $headMatchCasesCollection->add('head_and_get', new Route( + '/head_and_get', + array(), + array(), + array(), + '', + array(), + array('HEAD', 'GET') + )); + $headMatchCasesCollection->add('get_and_head', new Route( + '/get_and_head', + array(), + array(), + array(), + '', + array(), + array('GET', 'HEAD') + )); + $headMatchCasesCollection->add('post_and_head', new Route( + '/post_and_get', + array(), + array(), + array(), + '', + array(), + array('POST', 'HEAD') + )); + $headMatchCasesCollection->add('put_and_post', new Route( + '/put_and_post', + array(), + array(), + array(), + '', + array(), + array('PUT', 'POST') + )); + $headMatchCasesCollection->add('put_and_get_and_head', new Route( + '/put_and_post', + array(), + array(), + array(), + '', + array(), + array('PUT', 'GET', 'HEAD') + )); + + /* test case 5 */ + $groupOptimisedCollection = new RouteCollection(); + $groupOptimisedCollection->add('a_first', new Route('/a/11')); + $groupOptimisedCollection->add('a_second', new Route('/a/22')); + $groupOptimisedCollection->add('a_third', new Route('/a/333')); + $groupOptimisedCollection->add('a_wildcard', new Route('/{param}')); + $groupOptimisedCollection->add('a_fourth', new Route('/a/44/')); + $groupOptimisedCollection->add('a_fifth', new Route('/a/55/')); + $groupOptimisedCollection->add('a_sixth', new Route('/a/66/')); + $groupOptimisedCollection->add('nested_wildcard', new Route('/nested/{param}')); + $groupOptimisedCollection->add('nested_a', new Route('/nested/group/a/')); + $groupOptimisedCollection->add('nested_b', new Route('/nested/group/b/')); + $groupOptimisedCollection->add('nested_c', new Route('/nested/group/c/')); + + $groupOptimisedCollection->add('slashed_a', new Route('/slashed/group/')); + $groupOptimisedCollection->add('slashed_b', new Route('/slashed/group/b/')); + $groupOptimisedCollection->add('slashed_c', new Route('/slashed/group/c/')); + + $trailingSlashCollection = new RouteCollection(); + $trailingSlashCollection->add('simple_trailing_slash_no_methods', new Route('/trailing/simple/no-methods/', array(), array(), array(), '', array(), array())); + $trailingSlashCollection->add('simple_trailing_slash_GET_method', new Route('/trailing/simple/get-method/', array(), array(), array(), '', array(), array('GET'))); + $trailingSlashCollection->add('simple_trailing_slash_HEAD_method', new Route('/trailing/simple/head-method/', array(), array(), array(), '', array(), array('HEAD'))); + $trailingSlashCollection->add('simple_trailing_slash_POST_method', new Route('/trailing/simple/post-method/', array(), array(), array(), '', array(), array('POST'))); + $trailingSlashCollection->add('regex_trailing_slash_no_methods', new Route('/trailing/regex/no-methods/{param}/', array(), array(), array(), '', array(), array())); + $trailingSlashCollection->add('regex_trailing_slash_GET_method', new Route('/trailing/regex/get-method/{param}/', array(), array(), array(), '', array(), array('GET'))); + $trailingSlashCollection->add('regex_trailing_slash_HEAD_method', new Route('/trailing/regex/head-method/{param}/', array(), array(), array(), '', array(), array('HEAD'))); + $trailingSlashCollection->add('regex_trailing_slash_POST_method', new Route('/trailing/regex/post-method/{param}/', array(), array(), array(), '', array(), array('POST'))); + + $trailingSlashCollection->add('simple_not_trailing_slash_no_methods', new Route('/not-trailing/simple/no-methods', array(), array(), array(), '', array(), array())); + $trailingSlashCollection->add('simple_not_trailing_slash_GET_method', new Route('/not-trailing/simple/get-method', array(), array(), array(), '', array(), array('GET'))); + $trailingSlashCollection->add('simple_not_trailing_slash_HEAD_method', new Route('/not-trailing/simple/head-method', array(), array(), array(), '', array(), array('HEAD'))); + $trailingSlashCollection->add('simple_not_trailing_slash_POST_method', new Route('/not-trailing/simple/post-method', array(), array(), array(), '', array(), array('POST'))); + $trailingSlashCollection->add('regex_not_trailing_slash_no_methods', new Route('/not-trailing/regex/no-methods/{param}', array(), array(), array(), '', array(), array())); + $trailingSlashCollection->add('regex_not_trailing_slash_GET_method', new Route('/not-trailing/regex/get-method/{param}', array(), array(), array(), '', array(), array('GET'))); + $trailingSlashCollection->add('regex_not_trailing_slash_HEAD_method', new Route('/not-trailing/regex/head-method/{param}', array(), array(), array(), '', array(), array('HEAD'))); + $trailingSlashCollection->add('regex_not_trailing_slash_POST_method', new Route('/not-trailing/regex/post-method/{param}', array(), array(), array(), '', array(), array('POST'))); + + return array( + array($collection, 'url_matcher1.php', array()), + array($redirectCollection, 'url_matcher2.php', array('base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher')), + array($rootprefixCollection, 'url_matcher3.php', array()), + array($headMatchCasesCollection, 'url_matcher4.php', array()), + array($groupOptimisedCollection, 'url_matcher5.php', array('base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher')), + array($trailingSlashCollection, 'url_matcher6.php', array()), + array($trailingSlashCollection, 'url_matcher7.php', array('base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher')), + ); + } +} diff --git a/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php b/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php new file mode 100644 index 00000000..37419e77 --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php @@ -0,0 +1,175 @@ +compile()->getStaticPrefix(); + $collection->addRoute($staticPrefix, $name); + } + + $collection->optimizeGroups(); + $dumped = $this->dumpCollection($collection); + $this->assertEquals($expected, $dumped); + } + + public function routeProvider() + { + return array( + 'Simple - not nested' => array( + array( + array('/', 'root'), + array('/prefix/segment/', 'prefix_segment'), + array('/leading/segment/', 'leading_segment'), + ), + << array( + array( + array('/', 'root'), + array('/prefix/segment/aa', 'prefix_segment'), + array('/prefix/segment/bb', 'leading_segment'), + ), + << array( + array( + array('/', 'root'), + array('/prefix/segment/', 'prefix_segment'), + array('/prefix/segment/bb', 'leading_segment'), + ), + << /prefix/segment prefix_segment +-> /prefix/segment/bb leading_segment +EOF + ), + 'Simple one level nesting' => array( + array( + array('/', 'root'), + array('/group/segment/', 'nested_segment'), + array('/group/thing/', 'some_segment'), + array('/group/other/', 'other_segment'), + ), + << /group/segment nested_segment +-> /group/thing some_segment +-> /group/other other_segment +EOF + ), + 'Retain matching order with groups' => array( + array( + array('/group/aa/', 'aa'), + array('/group/bb/', 'bb'), + array('/group/cc/', 'cc'), + array('/', 'root'), + array('/group/dd/', 'dd'), + array('/group/ee/', 'ee'), + array('/group/ff/', 'ff'), + ), + << /group/aa aa +-> /group/bb bb +-> /group/cc cc +/ root +/group +-> /group/dd dd +-> /group/ee ee +-> /group/ff ff +EOF + ), + 'Retain complex matching order with groups at base' => array( + array( + array('/aaa/111/', 'first_aaa'), + array('/prefixed/group/aa/', 'aa'), + array('/prefixed/group/bb/', 'bb'), + array('/prefixed/group/cc/', 'cc'), + array('/prefixed/', 'root'), + array('/prefixed/group/dd/', 'dd'), + array('/prefixed/group/ee/', 'ee'), + array('/prefixed/group/ff/', 'ff'), + array('/aaa/222/', 'second_aaa'), + array('/aaa/333/', 'third_aaa'), + ), + << /aaa/111 first_aaa +-> /aaa/222 second_aaa +-> /aaa/333 third_aaa +/prefixed +-> /prefixed/group +-> -> /prefixed/group/aa aa +-> -> /prefixed/group/bb bb +-> -> /prefixed/group/cc cc +-> /prefixed root +-> /prefixed/group +-> -> /prefixed/group/dd dd +-> -> /prefixed/group/ee ee +-> -> /prefixed/group/ff ff +EOF + ), + + 'Group regardless of segments' => array( + array( + array('/aaa-111/', 'a1'), + array('/aaa-222/', 'a2'), + array('/aaa-333/', 'a3'), + array('/group-aa/', 'g1'), + array('/group-bb/', 'g2'), + array('/group-cc/', 'g3'), + ), + << /aaa-111 a1 +-> /aaa-222 a2 +-> /aaa-333 a3 +/group- +-> /group-aa g1 +-> /group-bb g2 +-> /group-cc g3 +EOF + ), + ); + } + + private function dumpCollection(StaticPrefixCollection $collection, $prefix = '') + { + $lines = array(); + + foreach ($collection->getItems() as $item) { + if ($item instanceof StaticPrefixCollection) { + $lines[] = $prefix.$item->getPrefix(); + $lines[] = $this->dumpCollection($item, $prefix.'-> '); + } else { + $lines[] = $prefix.implode(' ', $item); + } + } + + return implode("\n", $lines); + } +} diff --git a/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php b/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php new file mode 100644 index 00000000..ba4c6e97 --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; + +class RedirectableUrlMatcherTest extends TestCase +{ + public function testRedirectWhenNoSlash() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/')); + + $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, new RequestContext())); + $matcher->expects($this->once())->method('redirect'); + $matcher->match('/foo'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testRedirectWhenNoSlashForNonSafeMethod() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/')); + + $context = new RequestContext(); + $context->setMethod('POST'); + $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, $context)); + $matcher->match('/foo'); + } + + public function testSchemeRedirectRedirectsToFirstScheme() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('FTP', 'HTTPS'))); + + $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, new RequestContext())); + $matcher + ->expects($this->once()) + ->method('redirect') + ->with('/foo', 'foo', 'ftp') + ->will($this->returnValue(array('_route' => 'foo'))) + ; + $matcher->match('/foo'); + } + + public function testNoSchemaRedirectIfOnOfMultipleSchemesMatches() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https', 'http'))); + + $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, new RequestContext())); + $matcher + ->expects($this->never()) + ->method('redirect') + ; + $matcher->match('/foo'); + } +} diff --git a/vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.php b/vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.php new file mode 100644 index 00000000..9f0529e2 --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Matcher\TraceableUrlMatcher; + +class TraceableUrlMatcherTest extends TestCase +{ + public function test() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('POST'))); + $coll->add('bar', new Route('/bar/{id}', array(), array('id' => '\d+'))); + $coll->add('bar1', new Route('/bar/{name}', array(), array('id' => '\w+'), array(), '', array(), array('POST'))); + $coll->add('bar2', new Route('/foo', array(), array(), array(), 'baz')); + $coll->add('bar3', new Route('/foo1', array(), array(), array(), 'baz')); + $coll->add('bar4', new Route('/foo2', array(), array(), array(), 'baz', array(), array(), 'context.getMethod() == "GET"')); + + $context = new RequestContext(); + $context->setHost('baz'); + + $matcher = new TraceableUrlMatcher($coll, $context); + $traces = $matcher->getTraces('/babar'); + $this->assertSame(array(0, 0, 0, 0, 0, 0), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/foo'); + $this->assertSame(array(1, 0, 0, 2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/bar/12'); + $this->assertSame(array(0, 2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/bar/dd'); + $this->assertSame(array(0, 1, 1, 0, 0, 0), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/foo1'); + $this->assertSame(array(0, 0, 0, 0, 2), $this->getLevels($traces)); + + $context->setMethod('POST'); + $traces = $matcher->getTraces('/foo'); + $this->assertSame(array(2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/bar/dd'); + $this->assertSame(array(0, 1, 2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/foo2'); + $this->assertSame(array(0, 0, 0, 0, 0, 1), $this->getLevels($traces)); + } + + public function testMatchRouteOnMultipleHosts() + { + $routes = new RouteCollection(); + $routes->add('first', new Route( + '/mypath/', + array('_controller' => 'MainBundle:Info:first'), + array(), + array(), + 'some.example.com' + )); + + $routes->add('second', new Route( + '/mypath/', + array('_controller' => 'MainBundle:Info:second'), + array(), + array(), + 'another.example.com' + )); + + $context = new RequestContext(); + $context->setHost('baz'); + + $matcher = new TraceableUrlMatcher($routes, $context); + + $traces = $matcher->getTraces('/mypath/'); + $this->assertSame( + array(TraceableUrlMatcher::ROUTE_ALMOST_MATCHES, TraceableUrlMatcher::ROUTE_ALMOST_MATCHES), + $this->getLevels($traces) + ); + } + + public function getLevels($traces) + { + $levels = array(); + foreach ($traces as $trace) { + $levels[] = $trace['level']; + } + + return $levels; + } + + public function testRoutesWithConditions() + { + $routes = new RouteCollection(); + $routes->add('foo', new Route('/foo', array(), array(), array(), 'baz', array(), array(), "request.headers.get('User-Agent') matches '/firefox/i'")); + + $context = new RequestContext(); + $context->setHost('baz'); + + $matcher = new TraceableUrlMatcher($routes, $context); + + $notMatchingRequest = Request::create('/foo', 'GET'); + $traces = $matcher->getTracesForRequest($notMatchingRequest); + $this->assertEquals("Condition \"request.headers.get('User-Agent') matches '/firefox/i'\" does not evaluate to \"true\"", $traces[0]['log']); + + $matchingRequest = Request::create('/foo', 'GET', array(), array(), array(), array('HTTP_USER_AGENT' => 'Firefox')); + $traces = $matcher->getTracesForRequest($matchingRequest); + $this->assertEquals('Route matches!', $traces[0]['log']); + } +} diff --git a/vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php b/vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php new file mode 100644 index 00000000..1eeb5d43 --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php @@ -0,0 +1,430 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; + +class UrlMatcherTest extends TestCase +{ + public function testNoMethodSoAllowed() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo')); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertInternalType('array', $matcher->match('/foo')); + } + + public function testMethodNotAllowed() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('post'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + + try { + $matcher->match('/foo'); + $this->fail(); + } catch (MethodNotAllowedException $e) { + $this->assertEquals(array('POST'), $e->getAllowedMethods()); + } + } + + public function testHeadAllowedWhenRequirementContainsGet() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('get'))); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'head')); + $this->assertInternalType('array', $matcher->match('/foo')); + } + + public function testMethodNotAllowedAggregatesAllowedMethods() + { + $coll = new RouteCollection(); + $coll->add('foo1', new Route('/foo', array(), array(), array(), '', array(), array('post'))); + $coll->add('foo2', new Route('/foo', array(), array(), array(), '', array(), array('put', 'delete'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + + try { + $matcher->match('/foo'); + $this->fail(); + } catch (MethodNotAllowedException $e) { + $this->assertEquals(array('POST', 'PUT', 'DELETE'), $e->getAllowedMethods()); + } + } + + public function testMatch() + { + // test the patterns are matched and parameters are returned + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo/{bar}')); + $matcher = new UrlMatcher($collection, new RequestContext()); + try { + $matcher->match('/no-match'); + $this->fail(); + } catch (ResourceNotFoundException $e) { + } + $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz'), $matcher->match('/foo/baz')); + + // test that defaults are merged + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo/{bar}', array('def' => 'test'))); + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz', 'def' => 'test'), $matcher->match('/foo/baz')); + + // test that route "method" is ignored if no method is given in the context + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('get', 'head'))); + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertInternalType('array', $matcher->match('/foo')); + + // route does not match with POST method context + $matcher = new UrlMatcher($collection, new RequestContext('', 'post')); + try { + $matcher->match('/foo'); + $this->fail(); + } catch (MethodNotAllowedException $e) { + } + + // route does match with GET or HEAD method context + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertInternalType('array', $matcher->match('/foo')); + $matcher = new UrlMatcher($collection, new RequestContext('', 'head')); + $this->assertInternalType('array', $matcher->match('/foo')); + + // route with an optional variable as the first segment + $collection = new RouteCollection(); + $collection->add('bar', new Route('/{bar}/foo', array('bar' => 'bar'), array('bar' => 'foo|bar'))); + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'bar'), $matcher->match('/bar/foo')); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'foo'), $matcher->match('/foo/foo')); + + $collection = new RouteCollection(); + $collection->add('bar', new Route('/{bar}', array('bar' => 'bar'), array('bar' => 'foo|bar'))); + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'foo'), $matcher->match('/foo')); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'bar'), $matcher->match('/')); + + // route with only optional variables + $collection = new RouteCollection(); + $collection->add('bar', new Route('/{foo}/{bar}', array('foo' => 'foo', 'bar' => 'bar'), array())); + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'foo', 'bar' => 'bar'), $matcher->match('/')); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'a', 'bar' => 'bar'), $matcher->match('/a')); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'a', 'bar' => 'b'), $matcher->match('/a/b')); + } + + public function testMatchWithPrefixes() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{foo}')); + $collection->addPrefix('/b'); + $collection->addPrefix('/a'); + + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'foo', 'foo' => 'foo'), $matcher->match('/a/b/foo')); + } + + public function testMatchWithDynamicPrefix() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{foo}')); + $collection->addPrefix('/b'); + $collection->addPrefix('/{_locale}'); + + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_locale' => 'fr', '_route' => 'foo', 'foo' => 'foo'), $matcher->match('/fr/b/foo')); + } + + public function testMatchSpecialRouteName() + { + $collection = new RouteCollection(); + $collection->add('$péß^a|', new Route('/bar')); + + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => '$péß^a|'), $matcher->match('/bar')); + } + + public function testMatchNonAlpha() + { + $collection = new RouteCollection(); + $chars = '!"$%éà &\'()*+,./:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\[]^_`abcdefghijklmnopqrstuvwxyz{|}~-'; + $collection->add('foo', new Route('/{foo}/bar', array(), array('foo' => '['.preg_quote($chars).']+'), array('utf8' => true))); + + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'foo', 'foo' => $chars), $matcher->match('/'.rawurlencode($chars).'/bar')); + $this->assertEquals(array('_route' => 'foo', 'foo' => $chars), $matcher->match('/'.strtr($chars, array('%' => '%25')).'/bar')); + } + + public function testMatchWithDotMetacharacterInRequirements() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{foo}/bar', array(), array('foo' => '.+'))); + + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'foo', 'foo' => "\n"), $matcher->match('/'.urlencode("\n").'/bar'), 'linefeed character is matched'); + } + + public function testMatchOverriddenRoute() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('foo', new Route('/foo1')); + + $collection->addCollection($collection1); + + $matcher = new UrlMatcher($collection, new RequestContext()); + + $this->assertEquals(array('_route' => 'foo'), $matcher->match('/foo1')); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\ResourceNotFoundException'); + $this->assertEquals(array(), $matcher->match('/foo')); + } + + public function testMatchRegression() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}')); + $coll->add('bar', new Route('/foo/bar/{foo}')); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertEquals(array('foo' => 'bar', '_route' => 'bar'), $matcher->match('/foo/bar/bar')); + + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{bar}')); + $matcher = new UrlMatcher($collection, new RequestContext()); + try { + $matcher->match('/'); + $this->fail(); + } catch (ResourceNotFoundException $e) { + } + } + + public function testDefaultRequirementForOptionalVariables() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}', array('page' => 'index', '_format' => 'html'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertEquals(array('page' => 'my-page', '_format' => 'xml', '_route' => 'test'), $matcher->match('/my-page.xml')); + } + + public function testMatchingIsEager() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{foo}-{bar}-', array(), array('foo' => '.+', 'bar' => '.+'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertEquals(array('foo' => 'text1-text2-text3', 'bar' => 'text4', '_route' => 'test'), $matcher->match('/text1-text2-text3-text4-')); + } + + public function testAdjacentVariables() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{w}{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => 'y|Y'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + // 'w' eagerly matches as much as possible and the other variables match the remaining chars. + // This also shows that the variables w-z must all exclude the separating char (the dot '.' in this case) by default requirement. + // Otherwise they would also consume '.xml' and _format would never match as it's an optional variable. + $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'Y', 'z' => 'Z', '_format' => 'xml', '_route' => 'test'), $matcher->match('/wwwwwxYZ.xml')); + // As 'y' has custom requirement and can only be of value 'y|Y', it will leave 'ZZZ' to variable z. + // So with carefully chosen requirements adjacent variables, can be useful. + $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'y', 'z' => 'ZZZ', '_format' => 'html', '_route' => 'test'), $matcher->match('/wwwwwxyZZZ')); + // z and _format are optional. + $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'y', 'z' => 'default-z', '_format' => 'html', '_route' => 'test'), $matcher->match('/wwwwwxy')); + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\ResourceNotFoundException'); + $matcher->match('/wxy.html'); + } + + public function testOptionalVariableWithNoRealSeparator() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/get{what}', array('what' => 'All'))); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $this->assertEquals(array('what' => 'All', '_route' => 'test'), $matcher->match('/get')); + $this->assertEquals(array('what' => 'Sites', '_route' => 'test'), $matcher->match('/getSites')); + + // Usually the character in front of an optional parameter can be left out, e.g. with pattern '/get/{what}' just '/get' would match. + // But here the 't' in 'get' is not a separating character, so it makes no sense to match without it. + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\ResourceNotFoundException'); + $matcher->match('/ge'); + } + + public function testRequiredVariableWithNoRealSeparator() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/get{what}Suffix')); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $this->assertEquals(array('what' => 'Sites', '_route' => 'test'), $matcher->match('/getSitesSuffix')); + } + + public function testDefaultRequirementOfVariable() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}')); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $this->assertEquals(array('page' => 'index', '_format' => 'mobile.html', '_route' => 'test'), $matcher->match('/index.mobile.html')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testDefaultRequirementOfVariableDisallowsSlash() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}')); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $matcher->match('/index.sl/ash'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testDefaultRequirementOfVariableDisallowsNextSeparator() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}', array(), array('_format' => 'html|xml'))); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $matcher->match('/do.t.html'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testSchemeRequirement() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https'))); + $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher->match('/foo'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testCondition() + { + $coll = new RouteCollection(); + $route = new Route('/foo'); + $route->setCondition('context.getMethod() == "POST"'); + $coll->add('foo', $route); + $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher->match('/foo'); + } + + public function testRequestCondition() + { + $coll = new RouteCollection(); + $route = new Route('/foo/{bar}'); + $route->setCondition('request.getBaseUrl() == "/sub/front.php" and request.getPathInfo() == "/foo/bar"'); + $coll->add('foo', $route); + $matcher = new UrlMatcher($coll, new RequestContext('/sub/front.php')); + $this->assertEquals(array('bar' => 'bar', '_route' => 'foo'), $matcher->match('/foo/bar')); + } + + public function testDecodeOnce() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}')); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertEquals(array('foo' => 'bar%23', '_route' => 'foo'), $matcher->match('/foo/bar%2523')); + } + + public function testCannotRelyOnPrefix() + { + $coll = new RouteCollection(); + + $subColl = new RouteCollection(); + $subColl->add('bar', new Route('/bar')); + $subColl->addPrefix('/prefix'); + // overwrite the pattern, so the prefix is not valid anymore for this route in the collection + $subColl->get('bar')->setPath('/new'); + + $coll->addCollection($subColl); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertEquals(array('_route' => 'bar'), $matcher->match('/new')); + } + + public function testWithHost() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com')); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar')); + } + + public function testWithHostOnRouteCollection() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}')); + $coll->add('bar', new Route('/bar/{foo}', array(), array(), array(), '{locale}.example.net')); + $coll->setHost('{locale}.example.com'); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar')); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $this->assertEquals(array('foo' => 'bar', '_route' => 'bar', 'locale' => 'en'), $matcher->match('/bar/bar')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testWithOutHostHostDoesNotMatch() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com')); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'example.com')); + $matcher->match('/foo/bar'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testPathIsCaseSensitive() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/locale', array(), array('locale' => 'EN|FR|DE'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher->match('/en'); + } + + public function testHostIsCaseInsensitive() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/', array(), array('locale' => 'EN|FR|DE'), array(), '{locale}.example.com')); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $this->assertEquals(array('_route' => 'foo', 'locale' => 'en'), $matcher->match('/')); + } +} diff --git a/vendor/symfony/routing/Tests/RequestContextTest.php b/vendor/symfony/routing/Tests/RequestContextTest.php new file mode 100644 index 00000000..ffe29d1a --- /dev/null +++ b/vendor/symfony/routing/Tests/RequestContextTest.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\RequestContext; + +class RequestContextTest extends TestCase +{ + public function testConstruct() + { + $requestContext = new RequestContext( + 'foo', + 'post', + 'foo.bar', + 'HTTPS', + 8080, + 444, + '/baz', + 'bar=foobar' + ); + + $this->assertEquals('foo', $requestContext->getBaseUrl()); + $this->assertEquals('POST', $requestContext->getMethod()); + $this->assertEquals('foo.bar', $requestContext->getHost()); + $this->assertEquals('https', $requestContext->getScheme()); + $this->assertSame(8080, $requestContext->getHttpPort()); + $this->assertSame(444, $requestContext->getHttpsPort()); + $this->assertEquals('/baz', $requestContext->getPathInfo()); + $this->assertEquals('bar=foobar', $requestContext->getQueryString()); + } + + public function testFromRequest() + { + $request = Request::create('https://test.com:444/foo?bar=baz'); + $requestContext = new RequestContext(); + $requestContext->setHttpPort(123); + $requestContext->fromRequest($request); + + $this->assertEquals('', $requestContext->getBaseUrl()); + $this->assertEquals('GET', $requestContext->getMethod()); + $this->assertEquals('test.com', $requestContext->getHost()); + $this->assertEquals('https', $requestContext->getScheme()); + $this->assertEquals('/foo', $requestContext->getPathInfo()); + $this->assertEquals('bar=baz', $requestContext->getQueryString()); + $this->assertSame(123, $requestContext->getHttpPort()); + $this->assertSame(444, $requestContext->getHttpsPort()); + + $request = Request::create('http://test.com:8080/foo?bar=baz'); + $requestContext = new RequestContext(); + $requestContext->setHttpsPort(567); + $requestContext->fromRequest($request); + + $this->assertSame(8080, $requestContext->getHttpPort()); + $this->assertSame(567, $requestContext->getHttpsPort()); + } + + public function testGetParameters() + { + $requestContext = new RequestContext(); + $this->assertEquals(array(), $requestContext->getParameters()); + + $requestContext->setParameters(array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $requestContext->getParameters()); + } + + public function testHasParameter() + { + $requestContext = new RequestContext(); + $requestContext->setParameters(array('foo' => 'bar')); + + $this->assertTrue($requestContext->hasParameter('foo')); + $this->assertFalse($requestContext->hasParameter('baz')); + } + + public function testGetParameter() + { + $requestContext = new RequestContext(); + $requestContext->setParameters(array('foo' => 'bar')); + + $this->assertEquals('bar', $requestContext->getParameter('foo')); + $this->assertNull($requestContext->getParameter('baz')); + } + + public function testSetParameter() + { + $requestContext = new RequestContext(); + $requestContext->setParameter('foo', 'bar'); + + $this->assertEquals('bar', $requestContext->getParameter('foo')); + } + + public function testMethod() + { + $requestContext = new RequestContext(); + $requestContext->setMethod('post'); + + $this->assertSame('POST', $requestContext->getMethod()); + } + + public function testScheme() + { + $requestContext = new RequestContext(); + $requestContext->setScheme('HTTPS'); + + $this->assertSame('https', $requestContext->getScheme()); + } + + public function testHost() + { + $requestContext = new RequestContext(); + $requestContext->setHost('eXampLe.com'); + + $this->assertSame('example.com', $requestContext->getHost()); + } + + public function testQueryString() + { + $requestContext = new RequestContext(); + $requestContext->setQueryString(null); + + $this->assertSame('', $requestContext->getQueryString()); + } + + public function testPort() + { + $requestContext = new RequestContext(); + $requestContext->setHttpPort('123'); + $requestContext->setHttpsPort('456'); + + $this->assertSame(123, $requestContext->getHttpPort()); + $this->assertSame(456, $requestContext->getHttpsPort()); + } + + public function testFluentInterface() + { + $requestContext = new RequestContext(); + + $this->assertSame($requestContext, $requestContext->setBaseUrl('/app.php')); + $this->assertSame($requestContext, $requestContext->setPathInfo('/index')); + $this->assertSame($requestContext, $requestContext->setMethod('POST')); + $this->assertSame($requestContext, $requestContext->setScheme('https')); + $this->assertSame($requestContext, $requestContext->setHost('example.com')); + $this->assertSame($requestContext, $requestContext->setQueryString('foo=bar')); + $this->assertSame($requestContext, $requestContext->setHttpPort(80)); + $this->assertSame($requestContext, $requestContext->setHttpsPort(443)); + $this->assertSame($requestContext, $requestContext->setParameters(array())); + $this->assertSame($requestContext, $requestContext->setParameter('foo', 'bar')); + } +} diff --git a/vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php b/vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php new file mode 100644 index 00000000..058a100d --- /dev/null +++ b/vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php @@ -0,0 +1,325 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RouteCollectionBuilder; + +class RouteCollectionBuilderTest extends TestCase +{ + public function testImport() + { + $resolvedLoader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + $resolver = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderResolverInterface')->getMock(); + $resolver->expects($this->once()) + ->method('resolve') + ->with('admin_routing.yml', 'yaml') + ->will($this->returnValue($resolvedLoader)); + + $originalRoute = new Route('/foo/path'); + $expectedCollection = new RouteCollection(); + $expectedCollection->add('one_test_route', $originalRoute); + $expectedCollection->addResource(new FileResource(__DIR__.'/Fixtures/file_resource.yml')); + + $resolvedLoader + ->expects($this->once()) + ->method('load') + ->with('admin_routing.yml', 'yaml') + ->will($this->returnValue($expectedCollection)); + + $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + $loader->expects($this->any()) + ->method('getResolver') + ->will($this->returnValue($resolver)); + + // import the file! + $routes = new RouteCollectionBuilder($loader); + $importedRoutes = $routes->import('admin_routing.yml', '/', 'yaml'); + + // we should get back a RouteCollectionBuilder + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollectionBuilder', $importedRoutes); + + // get the collection back so we can look at it + $addedCollection = $importedRoutes->build(); + $route = $addedCollection->get('one_test_route'); + $this->assertSame($originalRoute, $route); + // should return file_resource.yml, which is in the original collection + $this->assertCount(1, $addedCollection->getResources()); + + // make sure the routes were imported into the top-level builder + $this->assertCount(1, $routes->build()); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testImportWithoutLoaderThrowsException() + { + $collectionBuilder = new RouteCollectionBuilder(); + $collectionBuilder->import('routing.yml'); + } + + public function testAdd() + { + $collectionBuilder = new RouteCollectionBuilder(); + + $addedRoute = $collectionBuilder->add('/checkout', 'AppBundle:Order:checkout'); + $addedRoute2 = $collectionBuilder->add('/blogs', 'AppBundle:Blog:list', 'blog_list'); + $this->assertInstanceOf('Symfony\Component\Routing\Route', $addedRoute); + $this->assertEquals('AppBundle:Order:checkout', $addedRoute->getDefault('_controller')); + + $finalCollection = $collectionBuilder->build(); + $this->assertSame($addedRoute2, $finalCollection->get('blog_list')); + } + + public function testFlushOrdering() + { + $importedCollection = new RouteCollection(); + $importedCollection->add('imported_route1', new Route('/imported/foo1')); + $importedCollection->add('imported_route2', new Route('/imported/foo2')); + + $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + // make this loader able to do the import - keeps mocking simple + $loader->expects($this->any()) + ->method('supports') + ->will($this->returnValue(true)); + $loader + ->expects($this->once()) + ->method('load') + ->will($this->returnValue($importedCollection)); + + $routes = new RouteCollectionBuilder($loader); + + // 1) Add a route + $routes->add('/checkout', 'AppBundle:Order:checkout', 'checkout_route'); + // 2) Import from a file + $routes->mount('/', $routes->import('admin_routing.yml')); + // 3) Add another route + $routes->add('/', 'AppBundle:Default:homepage', 'homepage'); + // 4) Add another route + $routes->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard'); + + // set a default value + $routes->setDefault('_locale', 'fr'); + + $actualCollection = $routes->build(); + + $this->assertCount(5, $actualCollection); + $actualRouteNames = array_keys($actualCollection->all()); + $this->assertEquals(array( + 'checkout_route', + 'imported_route1', + 'imported_route2', + 'homepage', + 'admin_dashboard', + ), $actualRouteNames); + + // make sure the defaults were set + $checkoutRoute = $actualCollection->get('checkout_route'); + $defaults = $checkoutRoute->getDefaults(); + $this->assertArrayHasKey('_locale', $defaults); + $this->assertEquals('fr', $defaults['_locale']); + } + + public function testFlushSetsRouteNames() + { + $collectionBuilder = new RouteCollectionBuilder(); + + // add a "named" route + $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard'); + // add an unnamed route + $collectionBuilder->add('/blogs', 'AppBundle:Blog:list') + ->setMethods(array('GET')); + + // integer route names are allowed - they don't confuse things + $collectionBuilder->add('/products', 'AppBundle:Product:list', 100); + + $actualCollection = $collectionBuilder->build(); + $actualRouteNames = array_keys($actualCollection->all()); + $this->assertEquals(array( + 'admin_dashboard', + 'GET_blogs', + '100', + ), $actualRouteNames); + } + + public function testFlushSetsDetailsOnChildrenRoutes() + { + $routes = new RouteCollectionBuilder(); + + $routes->add('/blogs/{page}', 'listAction', 'blog_list') + // unique things for the route + ->setDefault('page', 1) + ->setRequirement('id', '\d+') + ->setOption('expose', true) + // things that the collection will try to override (but won't) + ->setDefault('_format', 'html') + ->setRequirement('_format', 'json|xml') + ->setOption('fooBar', true) + ->setHost('example.com') + ->setCondition('request.isSecure()') + ->setSchemes(array('https')) + ->setMethods(array('POST')); + + // a simple route, nothing added to it + $routes->add('/blogs/{id}', 'editAction', 'blog_edit'); + + // configure the collection itself + $routes + // things that will not override the child route + ->setDefault('_format', 'json') + ->setRequirement('_format', 'xml') + ->setOption('fooBar', false) + ->setHost('symfony.com') + ->setCondition('request.query.get("page")==1') + // some unique things that should be set on the child + ->setDefault('_locale', 'fr') + ->setRequirement('_locale', 'fr|en') + ->setOption('niceRoute', true) + ->setSchemes(array('http')) + ->setMethods(array('GET', 'POST')); + + $collection = $routes->build(); + $actualListRoute = $collection->get('blog_list'); + + $this->assertEquals(1, $actualListRoute->getDefault('page')); + $this->assertEquals('\d+', $actualListRoute->getRequirement('id')); + $this->assertTrue($actualListRoute->getOption('expose')); + // none of these should be overridden + $this->assertEquals('html', $actualListRoute->getDefault('_format')); + $this->assertEquals('json|xml', $actualListRoute->getRequirement('_format')); + $this->assertTrue($actualListRoute->getOption('fooBar')); + $this->assertEquals('example.com', $actualListRoute->getHost()); + $this->assertEquals('request.isSecure()', $actualListRoute->getCondition()); + $this->assertEquals(array('https'), $actualListRoute->getSchemes()); + $this->assertEquals(array('POST'), $actualListRoute->getMethods()); + // inherited from the main collection + $this->assertEquals('fr', $actualListRoute->getDefault('_locale')); + $this->assertEquals('fr|en', $actualListRoute->getRequirement('_locale')); + $this->assertTrue($actualListRoute->getOption('niceRoute')); + + $actualEditRoute = $collection->get('blog_edit'); + // inherited from the collection + $this->assertEquals('symfony.com', $actualEditRoute->getHost()); + $this->assertEquals('request.query.get("page")==1', $actualEditRoute->getCondition()); + $this->assertEquals(array('http'), $actualEditRoute->getSchemes()); + $this->assertEquals(array('GET', 'POST'), $actualEditRoute->getMethods()); + } + + /** + * @dataProvider providePrefixTests + */ + public function testFlushPrefixesPaths($collectionPrefix, $routePath, $expectedPath) + { + $routes = new RouteCollectionBuilder(); + + $routes->add($routePath, 'someController', 'test_route'); + + $outerRoutes = new RouteCollectionBuilder(); + $outerRoutes->mount($collectionPrefix, $routes); + + $collection = $outerRoutes->build(); + + $this->assertEquals($expectedPath, $collection->get('test_route')->getPath()); + } + + public function providePrefixTests() + { + $tests = array(); + // empty prefix is of course ok + $tests[] = array('', '/foo', '/foo'); + // normal prefix - does not matter if it's a wildcard + $tests[] = array('/{admin}', '/foo', '/{admin}/foo'); + // shows that a prefix will always be given the starting slash + $tests[] = array('0', '/foo', '/0/foo'); + + // spaces are ok, and double slahses at the end are cleaned + $tests[] = array('/ /', '/foo', '/ /foo'); + + return $tests; + } + + public function testFlushSetsPrefixedWithMultipleLevels() + { + $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + $routes = new RouteCollectionBuilder($loader); + + $routes->add('homepage', 'MainController::homepageAction', 'homepage'); + + $adminRoutes = $routes->createBuilder(); + $adminRoutes->add('/dashboard', 'AdminController::dashboardAction', 'admin_dashboard'); + + // embedded collection under /admin + $adminBlogRoutes = $routes->createBuilder(); + $adminBlogRoutes->add('/new', 'BlogController::newAction', 'admin_blog_new'); + // mount into admin, but before the parent collection has been mounted + $adminRoutes->mount('/blog', $adminBlogRoutes); + + // now mount the /admin routes, above should all still be /blog/admin + $routes->mount('/admin', $adminRoutes); + // add a route after mounting + $adminRoutes->add('/users', 'AdminController::userAction', 'admin_users'); + + // add another sub-collection after the mount + $otherAdminRoutes = $routes->createBuilder(); + $otherAdminRoutes->add('/sales', 'StatsController::indexAction', 'admin_stats_sales'); + $adminRoutes->mount('/stats', $otherAdminRoutes); + + // add a normal collection and see that it is also prefixed + $importedCollection = new RouteCollection(); + $importedCollection->add('imported_route', new Route('/foo')); + // make this loader able to do the import - keeps mocking simple + $loader->expects($this->any()) + ->method('supports') + ->will($this->returnValue(true)); + $loader + ->expects($this->any()) + ->method('load') + ->will($this->returnValue($importedCollection)); + // import this from the /admin route builder + $adminRoutes->import('admin.yml', '/imported'); + + $collection = $routes->build(); + $this->assertEquals('/admin/dashboard', $collection->get('admin_dashboard')->getPath(), 'Routes before mounting have the prefix'); + $this->assertEquals('/admin/users', $collection->get('admin_users')->getPath(), 'Routes after mounting have the prefix'); + $this->assertEquals('/admin/blog/new', $collection->get('admin_blog_new')->getPath(), 'Sub-collections receive prefix even if mounted before parent prefix'); + $this->assertEquals('/admin/stats/sales', $collection->get('admin_stats_sales')->getPath(), 'Sub-collections receive prefix if mounted after parent prefix'); + $this->assertEquals('/admin/imported/foo', $collection->get('imported_route')->getPath(), 'Normal RouteCollections are also prefixed properly'); + } + + public function testAutomaticRouteNamesDoNotConflict() + { + $routes = new RouteCollectionBuilder(); + + $adminRoutes = $routes->createBuilder(); + // route 1 + $adminRoutes->add('/dashboard', ''); + + $accountRoutes = $routes->createBuilder(); + // route 2 + $accountRoutes->add('/dashboard', '') + ->setMethods(array('GET')); + // route 3 + $accountRoutes->add('/dashboard', '') + ->setMethods(array('POST')); + + $routes->mount('/admin', $adminRoutes); + $routes->mount('/account', $accountRoutes); + + $collection = $routes->build(); + // there are 2 routes (i.e. with non-conflicting names) + $this->assertCount(3, $collection->all()); + } +} diff --git a/vendor/symfony/routing/Tests/RouteCollectionTest.php b/vendor/symfony/routing/Tests/RouteCollectionTest.php new file mode 100644 index 00000000..83457ff1 --- /dev/null +++ b/vendor/symfony/routing/Tests/RouteCollectionTest.php @@ -0,0 +1,305 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Config\Resource\FileResource; + +class RouteCollectionTest extends TestCase +{ + public function testRoute() + { + $collection = new RouteCollection(); + $route = new Route('/foo'); + $collection->add('foo', $route); + $this->assertEquals(array('foo' => $route), $collection->all(), '->add() adds a route'); + $this->assertEquals($route, $collection->get('foo'), '->get() returns a route by name'); + $this->assertNull($collection->get('bar'), '->get() returns null if a route does not exist'); + } + + public function testOverriddenRoute() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + $collection->add('foo', new Route('/foo1')); + + $this->assertEquals('/foo1', $collection->get('foo')->getPath()); + } + + public function testDeepOverriddenRoute() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('foo', new Route('/foo1')); + + $collection2 = new RouteCollection(); + $collection2->add('foo', new Route('/foo2')); + + $collection1->addCollection($collection2); + $collection->addCollection($collection1); + + $this->assertEquals('/foo2', $collection1->get('foo')->getPath()); + $this->assertEquals('/foo2', $collection->get('foo')->getPath()); + } + + public function testIterator() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', $bar = new Route('/bar')); + $collection1->add('foo', $foo = new Route('/foo-new')); + $collection->addCollection($collection1); + $collection->add('last', $last = new Route('/last')); + + $this->assertInstanceOf('\ArrayIterator', $collection->getIterator()); + $this->assertSame(array('bar' => $bar, 'foo' => $foo, 'last' => $last), $collection->getIterator()->getArrayCopy()); + } + + public function testCount() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', new Route('/bar')); + $collection->addCollection($collection1); + + $this->assertCount(2, $collection); + } + + public function testAddCollection() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', $bar = new Route('/bar')); + $collection1->add('foo', $foo = new Route('/foo-new')); + + $collection2 = new RouteCollection(); + $collection2->add('grandchild', $grandchild = new Route('/grandchild')); + + $collection1->addCollection($collection2); + $collection->addCollection($collection1); + $collection->add('last', $last = new Route('/last')); + + $this->assertSame(array('bar' => $bar, 'foo' => $foo, 'grandchild' => $grandchild, 'last' => $last), $collection->all(), + '->addCollection() imports routes of another collection, overrides if necessary and adds them at the end'); + } + + public function testAddCollectionWithResources() + { + $collection = new RouteCollection(); + $collection->addResource($foo = new FileResource(__DIR__.'/Fixtures/foo.xml')); + $collection1 = new RouteCollection(); + $collection1->addResource($foo1 = new FileResource(__DIR__.'/Fixtures/foo1.xml')); + $collection->addCollection($collection1); + $this->assertEquals(array($foo, $foo1), $collection->getResources(), '->addCollection() merges resources'); + } + + public function testAddDefaultsAndRequirementsAndOptions() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{placeholder}')); + $collection1 = new RouteCollection(); + $collection1->add('bar', new Route('/{placeholder}', + array('_controller' => 'fixed', 'placeholder' => 'default'), array('placeholder' => '.+'), array('option' => 'value')) + ); + $collection->addCollection($collection1); + + $collection->addDefaults(array('placeholder' => 'new-default')); + $this->assertEquals(array('placeholder' => 'new-default'), $collection->get('foo')->getDefaults(), '->addDefaults() adds defaults to all routes'); + $this->assertEquals(array('_controller' => 'fixed', 'placeholder' => 'new-default'), $collection->get('bar')->getDefaults(), + '->addDefaults() adds defaults to all routes and overwrites existing ones'); + + $collection->addRequirements(array('placeholder' => '\d+')); + $this->assertEquals(array('placeholder' => '\d+'), $collection->get('foo')->getRequirements(), '->addRequirements() adds requirements to all routes'); + $this->assertEquals(array('placeholder' => '\d+'), $collection->get('bar')->getRequirements(), + '->addRequirements() adds requirements to all routes and overwrites existing ones'); + + $collection->addOptions(array('option' => 'new-value')); + $this->assertEquals( + array('option' => 'new-value', 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler'), + $collection->get('bar')->getOptions(), '->addOptions() adds options to all routes and overwrites existing ones' + ); + } + + public function testAddPrefix() + { + $collection = new RouteCollection(); + $collection->add('foo', $foo = new Route('/foo')); + $collection2 = new RouteCollection(); + $collection2->add('bar', $bar = new Route('/bar')); + $collection->addCollection($collection2); + $collection->addPrefix(' / '); + $this->assertSame('/foo', $collection->get('foo')->getPath(), '->addPrefix() trims the prefix and a single slash has no effect'); + $collection->addPrefix('/{admin}', array('admin' => 'admin'), array('admin' => '\d+')); + $this->assertEquals('/{admin}/foo', $collection->get('foo')->getPath(), '->addPrefix() adds a prefix to all routes'); + $this->assertEquals('/{admin}/bar', $collection->get('bar')->getPath(), '->addPrefix() adds a prefix to all routes'); + $this->assertEquals(array('admin' => 'admin'), $collection->get('foo')->getDefaults(), '->addPrefix() adds defaults to all routes'); + $this->assertEquals(array('admin' => 'admin'), $collection->get('bar')->getDefaults(), '->addPrefix() adds defaults to all routes'); + $this->assertEquals(array('admin' => '\d+'), $collection->get('foo')->getRequirements(), '->addPrefix() adds requirements to all routes'); + $this->assertEquals(array('admin' => '\d+'), $collection->get('bar')->getRequirements(), '->addPrefix() adds requirements to all routes'); + $collection->addPrefix('0'); + $this->assertEquals('/0/{admin}/foo', $collection->get('foo')->getPath(), '->addPrefix() ensures a prefix must start with a slash and must not end with a slash'); + $collection->addPrefix('/ /'); + $this->assertSame('/ /0/{admin}/foo', $collection->get('foo')->getPath(), '->addPrefix() can handle spaces if desired'); + $this->assertSame('/ /0/{admin}/bar', $collection->get('bar')->getPath(), 'the route pattern of an added collection is in synch with the added prefix'); + } + + public function testAddPrefixOverridesDefaultsAndRequirements() + { + $collection = new RouteCollection(); + $collection->add('foo', $foo = new Route('/foo.{_format}')); + $collection->add('bar', $bar = new Route('/bar.{_format}', array(), array('_format' => 'json'))); + $collection->addPrefix('/admin', array(), array('_format' => 'html')); + + $this->assertEquals('html', $collection->get('foo')->getRequirement('_format'), '->addPrefix() overrides existing requirements'); + $this->assertEquals('html', $collection->get('bar')->getRequirement('_format'), '->addPrefix() overrides existing requirements'); + } + + public function testResource() + { + $collection = new RouteCollection(); + $collection->addResource($foo = new FileResource(__DIR__.'/Fixtures/foo.xml')); + $collection->addResource($bar = new FileResource(__DIR__.'/Fixtures/bar.xml')); + $collection->addResource(new FileResource(__DIR__.'/Fixtures/foo.xml')); + + $this->assertEquals(array($foo, $bar), $collection->getResources(), + '->addResource() adds a resource and getResources() only returns unique ones by comparing the string representation'); + } + + public function testUniqueRouteWithGivenName() + { + $collection1 = new RouteCollection(); + $collection1->add('foo', new Route('/old')); + $collection2 = new RouteCollection(); + $collection3 = new RouteCollection(); + $collection3->add('foo', $new = new Route('/new')); + + $collection2->addCollection($collection3); + $collection1->addCollection($collection2); + + $this->assertSame($new, $collection1->get('foo'), '->get() returns new route that overrode previous one'); + // size of 1 because collection1 contains /new but not /old anymore + $this->assertCount(1, $collection1->getIterator(), '->addCollection() removes previous routes when adding new routes with the same name'); + } + + public function testGet() + { + $collection1 = new RouteCollection(); + $collection1->add('a', $a = new Route('/a')); + $collection2 = new RouteCollection(); + $collection2->add('b', $b = new Route('/b')); + $collection1->addCollection($collection2); + $collection1->add('$péß^a|', $c = new Route('/special')); + + $this->assertSame($b, $collection1->get('b'), '->get() returns correct route in child collection'); + $this->assertSame($c, $collection1->get('$péß^a|'), '->get() can handle special characters'); + $this->assertNull($collection2->get('a'), '->get() does not return the route defined in parent collection'); + $this->assertNull($collection1->get('non-existent'), '->get() returns null when route does not exist'); + $this->assertNull($collection1->get(0), '->get() does not disclose internal child RouteCollection'); + } + + public function testRemove() + { + $collection = new RouteCollection(); + $collection->add('foo', $foo = new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', $bar = new Route('/bar')); + $collection->addCollection($collection1); + $collection->add('last', $last = new Route('/last')); + + $collection->remove('foo'); + $this->assertSame(array('bar' => $bar, 'last' => $last), $collection->all(), '->remove() can remove a single route'); + $collection->remove(array('bar', 'last')); + $this->assertSame(array(), $collection->all(), '->remove() accepts an array and can remove multiple routes at once'); + } + + public function testSetHost() + { + $collection = new RouteCollection(); + $routea = new Route('/a'); + $routeb = new Route('/b', array(), array(), array(), '{locale}.example.net'); + $collection->add('a', $routea); + $collection->add('b', $routeb); + + $collection->setHost('{locale}.example.com'); + + $this->assertEquals('{locale}.example.com', $routea->getHost()); + $this->assertEquals('{locale}.example.com', $routeb->getHost()); + } + + public function testSetCondition() + { + $collection = new RouteCollection(); + $routea = new Route('/a'); + $routeb = new Route('/b', array(), array(), array(), '{locale}.example.net', array(), array(), 'context.getMethod() == "GET"'); + $collection->add('a', $routea); + $collection->add('b', $routeb); + + $collection->setCondition('context.getMethod() == "POST"'); + + $this->assertEquals('context.getMethod() == "POST"', $routea->getCondition()); + $this->assertEquals('context.getMethod() == "POST"', $routeb->getCondition()); + } + + public function testClone() + { + $collection = new RouteCollection(); + $collection->add('a', new Route('/a')); + $collection->add('b', new Route('/b', array('placeholder' => 'default'), array('placeholder' => '.+'))); + + $clonedCollection = clone $collection; + + $this->assertCount(2, $clonedCollection); + $this->assertEquals($collection->get('a'), $clonedCollection->get('a')); + $this->assertNotSame($collection->get('a'), $clonedCollection->get('a')); + $this->assertEquals($collection->get('b'), $clonedCollection->get('b')); + $this->assertNotSame($collection->get('b'), $clonedCollection->get('b')); + } + + public function testSetSchemes() + { + $collection = new RouteCollection(); + $routea = new Route('/a', array(), array(), array(), '', 'http'); + $routeb = new Route('/b'); + $collection->add('a', $routea); + $collection->add('b', $routeb); + + $collection->setSchemes(array('http', 'https')); + + $this->assertEquals(array('http', 'https'), $routea->getSchemes()); + $this->assertEquals(array('http', 'https'), $routeb->getSchemes()); + } + + public function testSetMethods() + { + $collection = new RouteCollection(); + $routea = new Route('/a', array(), array(), array(), '', array(), array('GET', 'POST')); + $routeb = new Route('/b'); + $collection->add('a', $routea); + $collection->add('b', $routeb); + + $collection->setMethods('PUT'); + + $this->assertEquals(array('PUT'), $routea->getMethods()); + $this->assertEquals(array('PUT'), $routeb->getMethods()); + } +} diff --git a/vendor/symfony/routing/Tests/RouteCompilerTest.php b/vendor/symfony/routing/Tests/RouteCompilerTest.php new file mode 100644 index 00000000..54006d7e --- /dev/null +++ b/vendor/symfony/routing/Tests/RouteCompilerTest.php @@ -0,0 +1,389 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCompiler; + +class RouteCompilerTest extends TestCase +{ + /** + * @dataProvider provideCompileData + */ + public function testCompile($name, $arguments, $prefix, $regex, $variables, $tokens) + { + $r = new \ReflectionClass('Symfony\\Component\\Routing\\Route'); + $route = $r->newInstanceArgs($arguments); + + $compiled = $route->compile(); + $this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)'); + $this->assertEquals($regex, $compiled->getRegex(), $name.' (regex)'); + $this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)'); + $this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)'); + } + + public function provideCompileData() + { + return array( + array( + 'Static route', + array('/foo'), + '/foo', '#^/foo$#s', array(), array( + array('text', '/foo'), + ), + ), + + array( + 'Route with a variable', + array('/foo/{bar}'), + '/foo', '#^/foo/(?P[^/]++)$#s', array('bar'), array( + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with a variable that has a default value', + array('/foo/{bar}', array('bar' => 'bar')), + '/foo', '#^/foo(?:/(?P[^/]++))?$#s', array('bar'), array( + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with several variables', + array('/foo/{bar}/{foobar}'), + '/foo', '#^/foo/(?P[^/]++)/(?P[^/]++)$#s', array('bar', 'foobar'), array( + array('variable', '/', '[^/]++', 'foobar'), + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with several variables that have default values', + array('/foo/{bar}/{foobar}', array('bar' => 'bar', 'foobar' => '')), + '/foo', '#^/foo(?:/(?P[^/]++)(?:/(?P[^/]++))?)?$#s', array('bar', 'foobar'), array( + array('variable', '/', '[^/]++', 'foobar'), + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with several variables but some of them have no default values', + array('/foo/{bar}/{foobar}', array('bar' => 'bar')), + '/foo', '#^/foo/(?P[^/]++)/(?P[^/]++)$#s', array('bar', 'foobar'), array( + array('variable', '/', '[^/]++', 'foobar'), + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with an optional variable as the first segment', + array('/{bar}', array('bar' => 'bar')), + '', '#^/(?P[^/]++)?$#s', array('bar'), array( + array('variable', '/', '[^/]++', 'bar'), + ), + ), + + array( + 'Route with a requirement of 0', + array('/{bar}', array('bar' => null), array('bar' => '0')), + '', '#^/(?P0)?$#s', array('bar'), array( + array('variable', '/', '0', 'bar'), + ), + ), + + array( + 'Route with an optional variable as the first segment with requirements', + array('/{bar}', array('bar' => 'bar'), array('bar' => '(foo|bar)')), + '', '#^/(?P(foo|bar))?$#s', array('bar'), array( + array('variable', '/', '(foo|bar)', 'bar'), + ), + ), + + array( + 'Route with only optional variables', + array('/{foo}/{bar}', array('foo' => 'foo', 'bar' => 'bar')), + '', '#^/(?P[^/]++)?(?:/(?P[^/]++))?$#s', array('foo', 'bar'), array( + array('variable', '/', '[^/]++', 'bar'), + array('variable', '/', '[^/]++', 'foo'), + ), + ), + + array( + 'Route with a variable in last position', + array('/foo-{bar}'), + '/foo-', '#^/foo\-(?P[^/]++)$#s', array('bar'), array( + array('variable', '-', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with nested placeholders', + array('/{static{var}static}'), + '/{static', '#^/\{static(?P[^/]+)static\}$#s', array('var'), array( + array('text', 'static}'), + array('variable', '', '[^/]+', 'var'), + array('text', '/{static'), + ), + ), + + array( + 'Route without separator between variables', + array('/{w}{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => '(y|Y)')), + '', '#^/(?P[^/\.]+)(?P[^/\.]+)(?P(y|Y))(?:(?P[^/\.]++)(?:\.(?P<_format>[^/]++))?)?$#s', array('w', 'x', 'y', 'z', '_format'), array( + array('variable', '.', '[^/]++', '_format'), + array('variable', '', '[^/\.]++', 'z'), + array('variable', '', '(y|Y)', 'y'), + array('variable', '', '[^/\.]+', 'x'), + array('variable', '/', '[^/\.]+', 'w'), + ), + ), + + array( + 'Route with a format', + array('/foo/{bar}.{_format}'), + '/foo', '#^/foo/(?P[^/\.]++)\.(?P<_format>[^/]++)$#s', array('bar', '_format'), array( + array('variable', '.', '[^/]++', '_format'), + array('variable', '/', '[^/\.]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Static non UTF-8 route', + array("/fo\xE9"), + "/fo\xE9", "#^/fo\xE9$#s", array(), array( + array('text', "/fo\xE9"), + ), + ), + + array( + 'Route with an explicit UTF-8 requirement', + array('/{bar}', array('bar' => null), array('bar' => '.'), array('utf8' => true)), + '', '#^/(?P.)?$#su', array('bar'), array( + array('variable', '/', '.', 'bar', true), + ), + ), + ); + } + + /** + * @group legacy + * @dataProvider provideCompileImplicitUtf8Data + * @expectedDeprecation Using UTF-8 route %s without setting the "utf8" option is deprecated %s. + */ + public function testCompileImplicitUtf8Data($name, $arguments, $prefix, $regex, $variables, $tokens, $deprecationType) + { + $r = new \ReflectionClass('Symfony\\Component\\Routing\\Route'); + $route = $r->newInstanceArgs($arguments); + + $compiled = $route->compile(); + $this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)'); + $this->assertEquals($regex, $compiled->getRegex(), $name.' (regex)'); + $this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)'); + $this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)'); + } + + public function provideCompileImplicitUtf8Data() + { + return array( + array( + 'Static UTF-8 route', + array('/foé'), + '/foé', '#^/foé$#su', array(), array( + array('text', '/foé'), + ), + 'patterns', + ), + + array( + 'Route with an implicit UTF-8 requirement', + array('/{bar}', array('bar' => null), array('bar' => 'é')), + '', '#^/(?Pé)?$#su', array('bar'), array( + array('variable', '/', 'é', 'bar', true), + ), + 'requirements', + ), + + array( + 'Route with a UTF-8 class requirement', + array('/{bar}', array('bar' => null), array('bar' => '\pM')), + '', '#^/(?P\pM)?$#su', array('bar'), array( + array('variable', '/', '\pM', 'bar', true), + ), + 'requirements', + ), + + array( + 'Route with a UTF-8 separator', + array('/foo/{bar}§{_format}', array(), array(), array('compiler_class' => Utf8RouteCompiler::class)), + '/foo', '#^/foo/(?P[^/§]++)§(?P<_format>[^/]++)$#su', array('bar', '_format'), array( + array('variable', '§', '[^/]++', '_format', true), + array('variable', '/', '[^/§]++', 'bar', true), + array('text', '/foo'), + ), + 'patterns', + ), + ); + } + + /** + * @expectedException \LogicException + */ + public function testRouteWithSameVariableTwice() + { + $route = new Route('/{name}/{name}'); + + $compiled = $route->compile(); + } + + /** + * @expectedException \LogicException + */ + public function testRouteCharsetMismatch() + { + $route = new Route("/\xE9/{bar}", array(), array('bar' => '.'), array('utf8' => true)); + + $compiled = $route->compile(); + } + + /** + * @expectedException \LogicException + */ + public function testRequirementCharsetMismatch() + { + $route = new Route('/foo/{bar}', array(), array('bar' => "\xE9"), array('utf8' => true)); + + $compiled = $route->compile(); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRouteWithFragmentAsPathParameter() + { + $route = new Route('/{_fragment}'); + + $compiled = $route->compile(); + } + + /** + * @dataProvider getVariableNamesStartingWithADigit + * @expectedException \DomainException + */ + public function testRouteWithVariableNameStartingWithADigit($name) + { + $route = new Route('/{'.$name.'}'); + $route->compile(); + } + + public function getVariableNamesStartingWithADigit() + { + return array( + array('09'), + array('123'), + array('1e2'), + ); + } + + /** + * @dataProvider provideCompileWithHostData + */ + public function testCompileWithHost($name, $arguments, $prefix, $regex, $variables, $pathVariables, $tokens, $hostRegex, $hostVariables, $hostTokens) + { + $r = new \ReflectionClass('Symfony\\Component\\Routing\\Route'); + $route = $r->newInstanceArgs($arguments); + + $compiled = $route->compile(); + $this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)'); + $this->assertEquals($regex, str_replace(array("\n", ' '), '', $compiled->getRegex()), $name.' (regex)'); + $this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)'); + $this->assertEquals($pathVariables, $compiled->getPathVariables(), $name.' (path variables)'); + $this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)'); + $this->assertEquals($hostRegex, str_replace(array("\n", ' '), '', $compiled->getHostRegex()), $name.' (host regex)'); + $this->assertEquals($hostVariables, $compiled->getHostVariables(), $name.' (host variables)'); + $this->assertEquals($hostTokens, $compiled->getHostTokens(), $name.' (host tokens)'); + } + + public function provideCompileWithHostData() + { + return array( + array( + 'Route with host pattern', + array('/hello', array(), array(), array(), 'www.example.com'), + '/hello', '#^/hello$#s', array(), array(), array( + array('text', '/hello'), + ), + '#^www\.example\.com$#si', array(), array( + array('text', 'www.example.com'), + ), + ), + array( + 'Route with host pattern and some variables', + array('/hello/{name}', array(), array(), array(), 'www.example.{tld}'), + '/hello', '#^/hello/(?P[^/]++)$#s', array('tld', 'name'), array('name'), array( + array('variable', '/', '[^/]++', 'name'), + array('text', '/hello'), + ), + '#^www\.example\.(?P[^\.]++)$#si', array('tld'), array( + array('variable', '.', '[^\.]++', 'tld'), + array('text', 'www.example'), + ), + ), + array( + 'Route with variable at beginning of host', + array('/hello', array(), array(), array(), '{locale}.example.{tld}'), + '/hello', '#^/hello$#s', array('locale', 'tld'), array(), array( + array('text', '/hello'), + ), + '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#si', array('locale', 'tld'), array( + array('variable', '.', '[^\.]++', 'tld'), + array('text', '.example'), + array('variable', '', '[^\.]++', 'locale'), + ), + ), + array( + 'Route with host variables that has a default value', + array('/hello', array('locale' => 'a', 'tld' => 'b'), array(), array(), '{locale}.example.{tld}'), + '/hello', '#^/hello$#s', array('locale', 'tld'), array(), array( + array('text', '/hello'), + ), + '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#si', array('locale', 'tld'), array( + array('variable', '.', '[^\.]++', 'tld'), + array('text', '.example'), + array('variable', '', '[^\.]++', 'locale'), + ), + ), + ); + } + + /** + * @expectedException \DomainException + */ + public function testRouteWithTooLongVariableName() + { + $route = new Route(sprintf('/{%s}', str_repeat('a', RouteCompiler::VARIABLE_MAXIMUM_LENGTH + 1))); + $route->compile(); + } +} + +class Utf8RouteCompiler extends RouteCompiler +{ + const SEPARATORS = '/§'; +} diff --git a/vendor/symfony/routing/Tests/RouteTest.php b/vendor/symfony/routing/Tests/RouteTest.php new file mode 100644 index 00000000..ff7e320c --- /dev/null +++ b/vendor/symfony/routing/Tests/RouteTest.php @@ -0,0 +1,258 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Route; + +class RouteTest extends TestCase +{ + public function testConstructor() + { + $route = new Route('/{foo}', array('foo' => 'bar'), array('foo' => '\d+'), array('foo' => 'bar'), '{locale}.example.com'); + $this->assertEquals('/{foo}', $route->getPath(), '__construct() takes a path as its first argument'); + $this->assertEquals(array('foo' => 'bar'), $route->getDefaults(), '__construct() takes defaults as its second argument'); + $this->assertEquals(array('foo' => '\d+'), $route->getRequirements(), '__construct() takes requirements as its third argument'); + $this->assertEquals('bar', $route->getOption('foo'), '__construct() takes options as its fourth argument'); + $this->assertEquals('{locale}.example.com', $route->getHost(), '__construct() takes a host pattern as its fifth argument'); + + $route = new Route('/', array(), array(), array(), '', array('Https'), array('POST', 'put'), 'context.getMethod() == "GET"'); + $this->assertEquals(array('https'), $route->getSchemes(), '__construct() takes schemes as its sixth argument and lowercases it'); + $this->assertEquals(array('POST', 'PUT'), $route->getMethods(), '__construct() takes methods as its seventh argument and uppercases it'); + $this->assertEquals('context.getMethod() == "GET"', $route->getCondition(), '__construct() takes a condition as its eight argument'); + + $route = new Route('/', array(), array(), array(), '', 'Https', 'Post'); + $this->assertEquals(array('https'), $route->getSchemes(), '__construct() takes a single scheme as its sixth argument'); + $this->assertEquals(array('POST'), $route->getMethods(), '__construct() takes a single method as its seventh argument'); + } + + public function testPath() + { + $route = new Route('/{foo}'); + $route->setPath('/{bar}'); + $this->assertEquals('/{bar}', $route->getPath(), '->setPath() sets the path'); + $route->setPath(''); + $this->assertEquals('/', $route->getPath(), '->setPath() adds a / at the beginning of the path if needed'); + $route->setPath('bar'); + $this->assertEquals('/bar', $route->getPath(), '->setPath() adds a / at the beginning of the path if needed'); + $this->assertEquals($route, $route->setPath(''), '->setPath() implements a fluent interface'); + $route->setPath('//path'); + $this->assertEquals('/path', $route->getPath(), '->setPath() does not allow two slashes "//" at the beginning of the path as it would be confused with a network path when generating the path from the route'); + } + + public function testOptions() + { + $route = new Route('/{foo}'); + $route->setOptions(array('foo' => 'bar')); + $this->assertEquals(array_merge(array( + 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler', + ), array('foo' => 'bar')), $route->getOptions(), '->setOptions() sets the options'); + $this->assertEquals($route, $route->setOptions(array()), '->setOptions() implements a fluent interface'); + + $route->setOptions(array('foo' => 'foo')); + $route->addOptions(array('bar' => 'bar')); + $this->assertEquals($route, $route->addOptions(array()), '->addOptions() implements a fluent interface'); + $this->assertEquals(array('foo' => 'foo', 'bar' => 'bar', 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler'), $route->getOptions(), '->addDefaults() keep previous defaults'); + } + + public function testOption() + { + $route = new Route('/{foo}'); + $this->assertFalse($route->hasOption('foo'), '->hasOption() return false if option is not set'); + $this->assertEquals($route, $route->setOption('foo', 'bar'), '->setOption() implements a fluent interface'); + $this->assertEquals('bar', $route->getOption('foo'), '->setOption() sets the option'); + $this->assertTrue($route->hasOption('foo'), '->hasOption() return true if option is set'); + } + + public function testDefaults() + { + $route = new Route('/{foo}'); + $route->setDefaults(array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $route->getDefaults(), '->setDefaults() sets the defaults'); + $this->assertEquals($route, $route->setDefaults(array()), '->setDefaults() implements a fluent interface'); + + $route->setDefault('foo', 'bar'); + $this->assertEquals('bar', $route->getDefault('foo'), '->setDefault() sets a default value'); + + $route->setDefault('foo2', 'bar2'); + $this->assertEquals('bar2', $route->getDefault('foo2'), '->getDefault() return the default value'); + $this->assertNull($route->getDefault('not_defined'), '->getDefault() return null if default value is not set'); + + $route->setDefault('_controller', $closure = function () { return 'Hello'; }); + $this->assertEquals($closure, $route->getDefault('_controller'), '->setDefault() sets a default value'); + + $route->setDefaults(array('foo' => 'foo')); + $route->addDefaults(array('bar' => 'bar')); + $this->assertEquals($route, $route->addDefaults(array()), '->addDefaults() implements a fluent interface'); + $this->assertEquals(array('foo' => 'foo', 'bar' => 'bar'), $route->getDefaults(), '->addDefaults() keep previous defaults'); + } + + public function testRequirements() + { + $route = new Route('/{foo}'); + $route->setRequirements(array('foo' => '\d+')); + $this->assertEquals(array('foo' => '\d+'), $route->getRequirements(), '->setRequirements() sets the requirements'); + $this->assertEquals('\d+', $route->getRequirement('foo'), '->getRequirement() returns a requirement'); + $this->assertNull($route->getRequirement('bar'), '->getRequirement() returns null if a requirement is not defined'); + $route->setRequirements(array('foo' => '^\d+$')); + $this->assertEquals('\d+', $route->getRequirement('foo'), '->getRequirement() removes ^ and $ from the path'); + $this->assertEquals($route, $route->setRequirements(array()), '->setRequirements() implements a fluent interface'); + + $route->setRequirements(array('foo' => '\d+')); + $route->addRequirements(array('bar' => '\d+')); + $this->assertEquals($route, $route->addRequirements(array()), '->addRequirements() implements a fluent interface'); + $this->assertEquals(array('foo' => '\d+', 'bar' => '\d+'), $route->getRequirements(), '->addRequirement() keep previous requirements'); + } + + public function testRequirement() + { + $route = new Route('/{foo}'); + $this->assertFalse($route->hasRequirement('foo'), '->hasRequirement() return false if requirement is not set'); + $route->setRequirement('foo', '^\d+$'); + $this->assertEquals('\d+', $route->getRequirement('foo'), '->setRequirement() removes ^ and $ from the path'); + $this->assertTrue($route->hasRequirement('foo'), '->hasRequirement() return true if requirement is set'); + } + + /** + * @dataProvider getInvalidRequirements + * @expectedException \InvalidArgumentException + */ + public function testSetInvalidRequirement($req) + { + $route = new Route('/{foo}'); + $route->setRequirement('foo', $req); + } + + public function getInvalidRequirements() + { + return array( + array(''), + array(array()), + array('^$'), + array('^'), + array('$'), + ); + } + + public function testHost() + { + $route = new Route('/'); + $route->setHost('{locale}.example.net'); + $this->assertEquals('{locale}.example.net', $route->getHost(), '->setHost() sets the host pattern'); + } + + public function testScheme() + { + $route = new Route('/'); + $this->assertEquals(array(), $route->getSchemes(), 'schemes is initialized with array()'); + $this->assertFalse($route->hasScheme('http')); + $route->setSchemes('hTTp'); + $this->assertEquals(array('http'), $route->getSchemes(), '->setSchemes() accepts a single scheme string and lowercases it'); + $this->assertTrue($route->hasScheme('htTp')); + $this->assertFalse($route->hasScheme('httpS')); + $route->setSchemes(array('HttpS', 'hTTp')); + $this->assertEquals(array('https', 'http'), $route->getSchemes(), '->setSchemes() accepts an array of schemes and lowercases them'); + $this->assertTrue($route->hasScheme('htTp')); + $this->assertTrue($route->hasScheme('httpS')); + } + + public function testMethod() + { + $route = new Route('/'); + $this->assertEquals(array(), $route->getMethods(), 'methods is initialized with array()'); + $route->setMethods('gEt'); + $this->assertEquals(array('GET'), $route->getMethods(), '->setMethods() accepts a single method string and uppercases it'); + $route->setMethods(array('gEt', 'PosT')); + $this->assertEquals(array('GET', 'POST'), $route->getMethods(), '->setMethods() accepts an array of methods and uppercases them'); + } + + public function testCondition() + { + $route = new Route('/'); + $this->assertSame('', $route->getCondition()); + $route->setCondition('context.getMethod() == "GET"'); + $this->assertSame('context.getMethod() == "GET"', $route->getCondition()); + } + + public function testCompile() + { + $route = new Route('/{foo}'); + $this->assertInstanceOf('Symfony\Component\Routing\CompiledRoute', $compiled = $route->compile(), '->compile() returns a compiled route'); + $this->assertSame($compiled, $route->compile(), '->compile() only compiled the route once if unchanged'); + $route->setRequirement('foo', '.*'); + $this->assertNotSame($compiled, $route->compile(), '->compile() recompiles if the route was modified'); + } + + public function testSerialize() + { + $route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+')); + + $serialized = serialize($route); + $unserialized = unserialize($serialized); + + $this->assertEquals($route, $unserialized); + $this->assertNotSame($route, $unserialized); + } + + /** + * Tests that the compiled version is also serialized to prevent the overhead + * of compiling it again after unserialize. + */ + public function testSerializeWhenCompiled() + { + $route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+')); + $route->setHost('{locale}.example.net'); + $route->compile(); + + $serialized = serialize($route); + $unserialized = unserialize($serialized); + + $this->assertEquals($route, $unserialized); + $this->assertNotSame($route, $unserialized); + } + + /** + * Tests that unserialization does not fail when the compiled Route is of a + * class other than CompiledRoute, such as a subclass of it. + */ + public function testSerializeWhenCompiledWithClass() + { + $route = new Route('/', array(), array(), array('compiler_class' => '\Symfony\Component\Routing\Tests\Fixtures\CustomRouteCompiler')); + $this->assertInstanceOf('\Symfony\Component\Routing\Tests\Fixtures\CustomCompiledRoute', $route->compile(), '->compile() returned a proper route'); + + $serialized = serialize($route); + try { + $unserialized = unserialize($serialized); + $this->assertInstanceOf('\Symfony\Component\Routing\Tests\Fixtures\CustomCompiledRoute', $unserialized->compile(), 'the unserialized route compiled successfully'); + } catch (\Exception $e) { + $this->fail('unserializing a route which uses a custom compiled route class'); + } + } + + /** + * Tests that the serialized representation of a route in one symfony version + * also works in later symfony versions, i.e. the unserialized route is in the + * same state as another, semantically equivalent, route. + */ + public function testSerializedRepresentationKeepsWorking() + { + $serialized = 'C:31:"Symfony\Component\Routing\Route":934:{a:8:{s:4:"path";s:13:"/prefix/{foo}";s:4:"host";s:20:"{locale}.example.net";s:8:"defaults";a:1:{s:3:"foo";s:7:"default";}s:12:"requirements";a:1:{s:3:"foo";s:3:"\d+";}s:7:"options";a:1:{s:14:"compiler_class";s:39:"Symfony\Component\Routing\RouteCompiler";}s:7:"schemes";a:0:{}s:7:"methods";a:0:{}s:8:"compiled";C:39:"Symfony\Component\Routing\CompiledRoute":569:{a:8:{s:4:"vars";a:2:{i:0;s:6:"locale";i:1;s:3:"foo";}s:11:"path_prefix";s:7:"/prefix";s:10:"path_regex";s:30:"#^/prefix(?:/(?P\d+))?$#s";s:11:"path_tokens";a:2:{i:0;a:4:{i:0;s:8:"variable";i:1;s:1:"/";i:2;s:3:"\d+";i:3;s:3:"foo";}i:1;a:2:{i:0;s:4:"text";i:1;s:7:"/prefix";}}s:9:"path_vars";a:1:{i:0;s:3:"foo";}s:10:"host_regex";s:39:"#^(?P[^\.]++)\.example\.net$#si";s:11:"host_tokens";a:2:{i:0;a:2:{i:0;s:4:"text";i:1;s:12:".example.net";}i:1;a:4:{i:0;s:8:"variable";i:1;s:0:"";i:2;s:7:"[^\.]++";i:3;s:6:"locale";}}s:9:"host_vars";a:1:{i:0;s:6:"locale";}}}}}'; + $unserialized = unserialize($serialized); + + $route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+')); + $route->setHost('{locale}.example.net'); + $route->compile(); + + $this->assertEquals($route, $unserialized); + $this->assertNotSame($route, $unserialized); + } +} diff --git a/vendor/symfony/routing/Tests/RouterTest.php b/vendor/symfony/routing/Tests/RouterTest.php new file mode 100644 index 00000000..409959ee --- /dev/null +++ b/vendor/symfony/routing/Tests/RouterTest.php @@ -0,0 +1,162 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Router; +use Symfony\Component\HttpFoundation\Request; + +class RouterTest extends TestCase +{ + private $router = null; + + private $loader = null; + + protected function setUp() + { + $this->loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + $this->router = new Router($this->loader, 'routing.yml'); + } + + public function testSetOptionsWithSupportedOptions() + { + $this->router->setOptions(array( + 'cache_dir' => './cache', + 'debug' => true, + 'resource_type' => 'ResourceType', + )); + + $this->assertSame('./cache', $this->router->getOption('cache_dir')); + $this->assertTrue($this->router->getOption('debug')); + $this->assertSame('ResourceType', $this->router->getOption('resource_type')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The Router does not support the following options: "option_foo", "option_bar" + */ + public function testSetOptionsWithUnsupportedOptions() + { + $this->router->setOptions(array( + 'cache_dir' => './cache', + 'option_foo' => true, + 'option_bar' => 'baz', + 'resource_type' => 'ResourceType', + )); + } + + public function testSetOptionWithSupportedOption() + { + $this->router->setOption('cache_dir', './cache'); + + $this->assertSame('./cache', $this->router->getOption('cache_dir')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The Router does not support the "option_foo" option + */ + public function testSetOptionWithUnsupportedOption() + { + $this->router->setOption('option_foo', true); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The Router does not support the "option_foo" option + */ + public function testGetOptionWithUnsupportedOption() + { + $this->router->getOption('option_foo', true); + } + + public function testThatRouteCollectionIsLoaded() + { + $this->router->setOption('resource_type', 'ResourceType'); + + $routeCollection = $this->getMockBuilder('Symfony\Component\Routing\RouteCollection')->getMock(); + + $this->loader->expects($this->once()) + ->method('load')->with('routing.yml', 'ResourceType') + ->will($this->returnValue($routeCollection)); + + $this->assertSame($routeCollection, $this->router->getRouteCollection()); + } + + /** + * @dataProvider provideMatcherOptionsPreventingCaching + */ + public function testMatcherIsCreatedIfCacheIsNotConfigured($option) + { + $this->router->setOption($option, null); + + $this->loader->expects($this->once()) + ->method('load')->with('routing.yml', null) + ->will($this->returnValue($this->getMockBuilder('Symfony\Component\Routing\RouteCollection')->getMock())); + + $this->assertInstanceOf('Symfony\\Component\\Routing\\Matcher\\UrlMatcher', $this->router->getMatcher()); + } + + public function provideMatcherOptionsPreventingCaching() + { + return array( + array('cache_dir'), + array('matcher_cache_class'), + ); + } + + /** + * @dataProvider provideGeneratorOptionsPreventingCaching + */ + public function testGeneratorIsCreatedIfCacheIsNotConfigured($option) + { + $this->router->setOption($option, null); + + $this->loader->expects($this->once()) + ->method('load')->with('routing.yml', null) + ->will($this->returnValue($this->getMockBuilder('Symfony\Component\Routing\RouteCollection')->getMock())); + + $this->assertInstanceOf('Symfony\\Component\\Routing\\Generator\\UrlGenerator', $this->router->getGenerator()); + } + + public function provideGeneratorOptionsPreventingCaching() + { + return array( + array('cache_dir'), + array('generator_cache_class'), + ); + } + + public function testMatchRequestWithUrlMatcherInterface() + { + $matcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\UrlMatcherInterface')->getMock(); + $matcher->expects($this->once())->method('match'); + + $p = new \ReflectionProperty($this->router, 'matcher'); + $p->setAccessible(true); + $p->setValue($this->router, $matcher); + + $this->router->matchRequest(Request::create('/')); + } + + public function testMatchRequestWithRequestMatcherInterface() + { + $matcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $matcher->expects($this->once())->method('matchRequest'); + + $p = new \ReflectionProperty($this->router, 'matcher'); + $p->setAccessible(true); + $p->setValue($this->router, $matcher); + + $this->router->matchRequest(Request::create('/')); + } +} diff --git a/vendor/symfony/routing/composer.json b/vendor/symfony/routing/composer.json new file mode 100644 index 00000000..bb4f6a55 --- /dev/null +++ b/vendor/symfony/routing/composer.json @@ -0,0 +1,56 @@ +{ + "name": "symfony/routing", + "type": "library", + "description": "Symfony Routing Component", + "keywords": ["routing", "router", "URL", "URI"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "require-dev": { + "symfony/config": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/yaml": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.3" + }, + "suggest": { + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/yaml": "For using the YAML loader", + "symfony/expression-language": "For using expression matching", + "doctrine/annotations": "For using the annotation loader", + "symfony/dependency-injection": "For loading routes from a service" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Routing\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/vendor/symfony/routing/phpunit.xml.dist b/vendor/symfony/routing/phpunit.xml.dist new file mode 100644 index 00000000..bcc09595 --- /dev/null +++ b/vendor/symfony/routing/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + +