From d11a8c9ecdef07195c15ba5f85f8da98b25491b1 Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Tue, 17 Nov 2015 13:58:43 +0100 Subject: [PATCH] Added PHP-DI in order to phase out SingletonFactory --- .../lib/system/SingletonFactory.class.php | 30 +- .../install/files/lib/system/WCF.class.php | 28 +- .../install/files/lib/system/api/autoload.php | 7 + .../files/lib/system/api/composer.json | 8 + .../files/lib/system/api/composer.lock | 183 ++++++++ .../lib/system/api/composer/ClassLoader.php | 413 ++++++++++++++++++ .../files/lib/system/api/composer/LICENSE | 21 + .../system/api/composer/autoload_classmap.php | 9 + .../system/api/composer/autoload_files.php | 10 + .../api/composer/autoload_namespaces.php | 9 + .../lib/system/api/composer/autoload_psr4.php | 13 + .../lib/system/api/composer/autoload_real.php | 55 +++ .../lib/system/api/composer/installed.json | 174 ++++++++ .../container-interop/.gitignore | 3 + .../container-interop/LICENSE | 20 + .../container-interop/README.md | 85 ++++ .../container-interop/composer.json | 11 + .../docs/ContainerInterface-meta.md | 114 +++++ .../docs/ContainerInterface.md | 153 +++++++ .../docs/Delegate-lookup-meta.md | 259 +++++++++++ .../container-interop/docs/Delegate-lookup.md | 60 +++ .../docs/images/interoperating_containers.png | Bin 0 -> 35971 bytes .../docs/images/priority.png | Bin 0 -> 22949 bytes .../docs/images/side_by_side_containers.png | Bin 0 -> 22519 bytes .../Interop/Container/ContainerInterface.php | 37 ++ .../Exception/ContainerException.php | 13 + .../Container/Exception/NotFoundException.php | 13 + .../system/api/php-di/invoker/CONTRIBUTING.md | 15 + .../lib/system/api/php-di/invoker/LICENSE | 21 + .../lib/system/api/php-di/invoker/README.md | 234 ++++++++++ .../system/api/php-di/invoker/composer.json | 25 ++ .../php-di/invoker/doc/parameter-resolvers.md | 109 +++++ .../php-di/invoker/src/CallableResolver.php | 131 ++++++ .../src/Exception/InvocationException.php | 12 + .../src/Exception/NotCallableException.php | 12 + .../NotEnoughParametersException.php | 12 + .../system/api/php-di/invoker/src/Invoker.php | 122 ++++++ .../php-di/invoker/src/InvokerInterface.php | 29 ++ .../AssociativeArrayResolver.php | 39 ++ .../ParameterNameContainerResolver.php | 51 +++ .../Container/TypeHintContainerResolver.php | 51 +++ .../DefaultValueResolver.php | 40 ++ .../NumericArrayResolver.php | 39 ++ .../ParameterResolver/ParameterResolver.php | 33 ++ .../src/ParameterResolver/ResolverChain.php | 69 +++ .../src/Reflection/CallableReflection.php | 57 +++ .../system/api/php-di/php-di/.coveralls.yml | 2 + .../system/api/php-di/php-di/.gitattributes | 8 + .../lib/system/api/php-di/php-di/.gitignore | 9 + .../lib/system/api/php-di/php-di/.travis.yml | 26 ++ .../files/lib/system/api/php-di/php-di/404.md | 3 + .../system/api/php-di/php-di/CONTRIBUTING.md | 52 +++ .../lib/system/api/php-di/php-di/LICENSE | 18 + .../lib/system/api/php-di/php-di/README.md | 21 + .../system/api/php-di/php-di/change-log.md | 303 +++++++++++++ .../system/api/php-di/php-di/composer.json | 43 ++ .../lib/system/api/php-di/php-di/couscous.yml | 110 +++++ .../system/api/php-di/php-di/phpunit.xml.dist | 30 ++ .../php-di/src/DI/Annotation/Inject.php | 95 ++++ .../php-di/src/DI/Annotation/Injectable.php | 74 ++++ .../php-di/php-di/src/DI/Cache/ArrayCache.php | 79 ++++ .../api/php-di/php-di/src/DI/Container.php | 341 +++++++++++++++ .../php-di/php-di/src/DI/ContainerBuilder.php | 258 +++++++++++ .../system/api/php-di/php-di/src/DI/Debug.php | 39 ++ .../src/DI/Definition/AliasDefinition.php | 66 +++ .../src/DI/Definition/ArrayDefinition.php | 66 +++ .../Definition/ArrayDefinitionExtension.php | 61 +++ .../src/DI/Definition/CacheableDefinition.php | 19 + .../src/DI/Definition/DecoratorDefinition.php | 48 ++ .../php-di/src/DI/Definition/Definition.php | 32 ++ .../Dumper/AliasDefinitionDumper.php | 48 ++ .../Dumper/ArrayDefinitionDumper.php | 65 +++ .../Dumper/DecoratorDefinitionDumper.php | 37 ++ .../DI/Definition/Dumper/DefinitionDumper.php | 30 ++ .../Dumper/DefinitionDumperDispatcher.php | 68 +++ .../EnvironmentVariableDefinitionDumper.php | 63 +++ .../Dumper/FactoryDefinitionDumper.php | 37 ++ .../Dumper/ObjectDefinitionDumper.php | 158 +++++++ .../Dumper/StringDefinitionDumper.php | 37 ++ .../Dumper/ValueDefinitionDumper.php | 44 ++ .../src/DI/Definition/EntryReference.php | 52 +++ .../EnvironmentVariableDefinition.php | 116 +++++ .../Exception/AnnotationException.php | 19 + .../Exception/DefinitionException.php | 30 ++ .../src/DI/Definition/FactoryDefinition.php | 75 ++++ .../src/DI/Definition/HasSubDefinition.php | 28 ++ .../Helper/ArrayDefinitionExtensionHelper.php | 46 ++ .../DI/Definition/Helper/DefinitionHelper.php | 24 + .../EnvironmentVariableDefinitionHelper.php | 64 +++ .../Helper/FactoryDefinitionHelper.php | 72 +++ .../Helper/ObjectDefinitionHelper.php | 279 ++++++++++++ .../Helper/StringDefinitionHelper.php | 39 ++ .../Helper/ValueDefinitionHelper.php | 42 ++ .../src/DI/Definition/InstanceDefinition.php | 76 ++++ .../src/DI/Definition/ObjectDefinition.php | 337 ++++++++++++++ .../ObjectDefinition/MethodInjection.php | 94 ++++ .../ObjectDefinition/PropertyInjection.php | 74 ++++ .../DI/Definition/Resolver/AliasResolver.php | 63 +++ .../DI/Definition/Resolver/ArrayResolver.php | 91 ++++ .../Definition/Resolver/DecoratorResolver.php | 93 ++++ .../Resolver/DefinitionResolver.php | 44 ++ .../Resolver/EnvironmentVariableResolver.php | 81 ++++ .../Definition/Resolver/FactoryResolver.php | 82 ++++ .../Definition/Resolver/InstanceInjector.php | 53 +++ .../DI/Definition/Resolver/ObjectCreator.php | 258 +++++++++++ .../Definition/Resolver/ParameterResolver.php | 140 ++++++ .../Resolver/ResolverDispatcher.php | 138 ++++++ .../DI/Definition/Resolver/StringResolver.php | 81 ++++ .../DI/Definition/Resolver/ValueResolver.php | 44 ++ .../DI/Definition/Source/AnnotationReader.php | 280 ++++++++++++ .../src/DI/Definition/Source/Autowiring.php | 68 +++ .../Source/CachedDefinitionSource.php | 104 +++++ .../DI/Definition/Source/DefinitionArray.php | 142 ++++++ .../DI/Definition/Source/DefinitionFile.php | 74 ++++ .../DI/Definition/Source/DefinitionSource.php | 31 ++ .../Source/MutableDefinitionSource.php | 15 + .../src/DI/Definition/Source/SourceChain.php | 108 +++++ .../src/DI/Definition/StringDefinition.php | 66 +++ .../src/DI/Definition/ValueDefinition.php | 67 +++ .../php-di/src/DI/DependencyException.php | 19 + .../php-di/php-di/src/DI/FactoryInterface.php | 34 ++ .../Invoker/DefinitionParameterResolver.php | 66 +++ .../php-di/php-di/src/DI/InvokerInterface.php | 19 + .../php-di/src/DI/NotFoundException.php | 19 + .../php-di/src/DI/Proxy/ProxyFactory.php | 88 ++++ .../Reflection/CallableReflectionFactory.php | 51 +++ .../system/api/php-di/php-di/src/DI/Scope.php | 54 +++ .../api/php-di/php-di/src/DI/functions.php | 181 ++++++++ .../api/php-di/phpdoc-reader/.gitattributes | 7 + .../api/php-di/phpdoc-reader/.gitignore | 5 + .../system/api/php-di/phpdoc-reader/LICENSE | 16 + .../system/api/php-di/phpdoc-reader/README.md | 59 +++ .../api/php-di/phpdoc-reader/composer.json | 23 + .../src/PhpDocReader/AnnotationException.php | 10 + .../src/PhpDocReader/PhpDocReader.php | 270 ++++++++++++ .../PhpDocReader/PhpParser/TokenParser.php | 159 +++++++ .../PhpParser/UseStatementParser.php | 68 +++ 137 files changed, 9926 insertions(+), 31 deletions(-) create mode 100644 wcfsetup/install/files/lib/system/api/autoload.php create mode 100644 wcfsetup/install/files/lib/system/api/composer.json create mode 100644 wcfsetup/install/files/lib/system/api/composer.lock create mode 100644 wcfsetup/install/files/lib/system/api/composer/ClassLoader.php create mode 100644 wcfsetup/install/files/lib/system/api/composer/LICENSE create mode 100644 wcfsetup/install/files/lib/system/api/composer/autoload_classmap.php create mode 100644 wcfsetup/install/files/lib/system/api/composer/autoload_files.php create mode 100644 wcfsetup/install/files/lib/system/api/composer/autoload_namespaces.php create mode 100644 wcfsetup/install/files/lib/system/api/composer/autoload_psr4.php create mode 100644 wcfsetup/install/files/lib/system/api/composer/autoload_real.php create mode 100644 wcfsetup/install/files/lib/system/api/composer/installed.json create mode 100644 wcfsetup/install/files/lib/system/api/container-interop/container-interop/.gitignore create mode 100644 wcfsetup/install/files/lib/system/api/container-interop/container-interop/LICENSE create mode 100644 wcfsetup/install/files/lib/system/api/container-interop/container-interop/README.md create mode 100644 wcfsetup/install/files/lib/system/api/container-interop/container-interop/composer.json create mode 100644 wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/ContainerInterface-meta.md create mode 100644 wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/ContainerInterface.md create mode 100644 wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/Delegate-lookup-meta.md create mode 100644 wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/Delegate-lookup.md create mode 100644 wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/interoperating_containers.png create mode 100644 wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/priority.png create mode 100644 wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/side_by_side_containers.png create mode 100644 wcfsetup/install/files/lib/system/api/container-interop/container-interop/src/Interop/Container/ContainerInterface.php create mode 100644 wcfsetup/install/files/lib/system/api/container-interop/container-interop/src/Interop/Container/Exception/ContainerException.php create mode 100644 wcfsetup/install/files/lib/system/api/container-interop/container-interop/src/Interop/Container/Exception/NotFoundException.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/CONTRIBUTING.md create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/LICENSE create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/README.md create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/composer.json create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/doc/parameter-resolvers.md create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/src/CallableResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/InvocationException.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/NotCallableException.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/NotEnoughParametersException.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/src/Invoker.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/src/InvokerInterface.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/AssociativeArrayResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/Container/ParameterNameContainerResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/Container/TypeHintContainerResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/DefaultValueResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/NumericArrayResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/ParameterResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/ResolverChain.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/invoker/src/Reflection/CallableReflection.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/.coveralls.yml create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/.gitattributes create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/.gitignore create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/.travis.yml create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/404.md create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/CONTRIBUTING.md create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/LICENSE create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/README.md create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/change-log.md create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/composer.json create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/couscous.yml create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/phpunit.xml.dist create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Annotation/Inject.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Annotation/Injectable.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Cache/ArrayCache.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Container.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/ContainerBuilder.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Debug.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/AliasDefinition.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ArrayDefinition.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ArrayDefinitionExtension.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/CacheableDefinition.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/DecoratorDefinition.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Definition.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/AliasDefinitionDumper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ArrayDefinitionDumper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DecoratorDefinitionDumper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumperDispatcher.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/EnvironmentVariableDefinitionDumper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/FactoryDefinitionDumper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ObjectDefinitionDumper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/StringDefinitionDumper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ValueDefinitionDumper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/EntryReference.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/EnvironmentVariableDefinition.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Exception/AnnotationException.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Exception/DefinitionException.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/FactoryDefinition.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/HasSubDefinition.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ArrayDefinitionExtensionHelper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/DefinitionHelper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/EnvironmentVariableDefinitionHelper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/FactoryDefinitionHelper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ObjectDefinitionHelper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/StringDefinitionHelper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ValueDefinitionHelper.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/InstanceDefinition.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition/MethodInjection.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition/PropertyInjection.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/AliasResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ArrayResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/DecoratorResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/DefinitionResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/EnvironmentVariableResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/FactoryResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/InstanceInjector.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ObjectCreator.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ParameterResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ResolverDispatcher.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/StringResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ValueResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/AnnotationReader.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/Autowiring.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/CachedDefinitionSource.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionArray.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionFile.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionSource.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/MutableDefinitionSource.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/SourceChain.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/StringDefinition.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ValueDefinition.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/DependencyException.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/FactoryInterface.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Invoker/DefinitionParameterResolver.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/InvokerInterface.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/NotFoundException.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Proxy/ProxyFactory.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Reflection/CallableReflectionFactory.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Scope.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/functions.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/.gitattributes create mode 100644 wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/.gitignore create mode 100644 wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/LICENSE create mode 100644 wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/README.md create mode 100644 wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/composer.json create mode 100644 wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/AnnotationException.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpDocReader.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpParser/TokenParser.php create mode 100644 wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpParser/UseStatementParser.php diff --git a/wcfsetup/install/files/lib/system/SingletonFactory.class.php b/wcfsetup/install/files/lib/system/SingletonFactory.class.php index 2c90207a0e..d15fc3f2f6 100644 --- a/wcfsetup/install/files/lib/system/SingletonFactory.class.php +++ b/wcfsetup/install/files/lib/system/SingletonFactory.class.php @@ -13,17 +13,11 @@ use wcf\system\exception\SystemException; * @category Community Framework */ abstract class SingletonFactory { - /** - * list of singletons - * @var array - */ - protected static $__singletonObjects = array(); - /** * Singletons do not support a public constructor. Override init() if * your class needs to initialize components on creation. */ - protected final function __construct() { + public final function __construct() { $this->init(); } @@ -50,26 +44,6 @@ abstract class SingletonFactory { * @return static */ public static final function getInstance() { - $className = get_called_class(); - if (!array_key_exists($className, self::$__singletonObjects)) { - self::$__singletonObjects[$className] = null; - self::$__singletonObjects[$className] = new $className(); - } - else if (self::$__singletonObjects[$className] === null) { - throw new SystemException("Infinite loop detected while trying to retrieve object for '".$className."'"); - } - - return self::$__singletonObjects[$className]; - } - - /** - * Returns whether this singleton is already initialized. - * - * @return boolean - */ - public static final function isInitialized() { - $className = get_called_class(); - - return isset(self::$__singletonObjects[$className]); + return WCF::getDIContainer()->get(get_called_class()); } } diff --git a/wcfsetup/install/files/lib/system/WCF.class.php b/wcfsetup/install/files/lib/system/WCF.class.php index 92be155773..dd4209d835 100644 --- a/wcfsetup/install/files/lib/system/WCF.class.php +++ b/wcfsetup/install/files/lib/system/WCF.class.php @@ -1,5 +1,6 @@ build(); + // start initialization $this->initDB(); $this->loadOptions(); @@ -148,6 +159,15 @@ class WCF { EventHandler::getInstance()->fireAction($this, 'initialized'); } + /** + * Returns the dependency injection container. + * + * @return \DI\Container + */ + public static final function getDIContainer() { + return self::$diContainer; + } + /** * Flushes the output, closes the session, performs background tasks and more. * @@ -308,7 +328,7 @@ class WCF { $factory = new SessionFactory(); $factory->load(); - self::$sessionObj = SessionHandler::getInstance(); + self::$sessionObj = self::$diContainer->get(SessionHandler::class); self::$sessionObj->setHasValidCookie($factory->hasValidCookie()); } @@ -333,7 +353,7 @@ class WCF { * Initialises the template engine. */ protected function initTPL() { - self::$tplObj = TemplateEngine::getInstance(); + self::$tplObj = self::$diContainer->get(TemplateEngine::class); self::getTPL()->setLanguageID(self::getLanguage()->languageID); $this->assignDefaultTemplateVariables(); @@ -348,7 +368,9 @@ class WCF { self::getSession()->setStyleID(intval($_REQUEST['styleID'])); } - StyleHandler::getInstance()->changeStyle(self::getSession()->getStyleID()); + /** @var $styleHandler \wcf\system\style\StyleHandler */ + $styleHandler = self::$diContainer->get(StyleHandler::class); + $styleHandler->changeStyle(self::getSession()->getStyleID()); } /** diff --git a/wcfsetup/install/files/lib/system/api/autoload.php b/wcfsetup/install/files/lib/system/api/autoload.php new file mode 100644 index 0000000000..4694b275f6 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/autoload.php @@ -0,0 +1,7 @@ +=5.4.0", + "php-di/invoker": "^1.0.1", + "php-di/phpdoc-reader": "~2.0" + }, + "replace": { + "mnapoli/php-di": "*" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "doctrine/cache": "~1.4", + "mnapoli/phpunit-easymock": "~0.1.4", + "ocramius/proxy-manager": "~1.0", + "phpunit/phpunit": "~4.5" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "doctrine/cache": "Install it if you want to use the cache (version ~1.4)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~1.0)" + }, + "type": "library", + "autoload": { + "psr-4": { + "DI\\": "src/DI/" + }, + "files": [ + "src/DI/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "http://php-di.org/", + "keywords": [ + "container", + "dependency injection", + "di" + ], + "time": "2015-09-08 08:56:29" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "21dce5e29f640d655e7b4583ecfb7d166127a5da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/21dce5e29f640d655e7b4583ecfb7d166127a5da", + "reference": "21dce5e29f640d655e7b4583ecfb7d166127a5da", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "time": "2015-06-01 14:23:20" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/wcfsetup/install/files/lib/system/api/composer/ClassLoader.php b/wcfsetup/install/files/lib/system/api/composer/ClassLoader.php new file mode 100644 index 0000000000..5e1469e830 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/composer/ClassLoader.php @@ -0,0 +1,413 @@ + + * 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 class loader + * + * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md + * + * $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 + */ +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; + + 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-0 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; + } + + /** + * 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) + { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative) { + return false; + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if ($file === null && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if ($file === null) { + // Remember that this class does not exist. + return $this->classMap[$class] = false; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + 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; + } + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/wcfsetup/install/files/lib/system/api/composer/LICENSE b/wcfsetup/install/files/lib/system/api/composer/LICENSE new file mode 100644 index 0000000000..c8d57af8b2 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) 2015 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/wcfsetup/install/files/lib/system/api/composer/autoload_classmap.php b/wcfsetup/install/files/lib/system/api/composer/autoload_classmap.php new file mode 100644 index 0000000000..1bd6482f9e --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + array($vendorDir . '/php-di/phpdoc-reader/src/PhpDocReader'), + 'Invoker\\' => array($vendorDir . '/php-di/invoker/src'), + 'Interop\\Container\\' => array($vendorDir . '/container-interop/container-interop/src/Interop/Container'), + 'DI\\' => array($vendorDir . '/php-di/php-di/src/DI'), +); diff --git a/wcfsetup/install/files/lib/system/api/composer/autoload_real.php b/wcfsetup/install/files/lib/system/api/composer/autoload_real.php new file mode 100644 index 0000000000..cf860b92b3 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/composer/autoload_real.php @@ -0,0 +1,55 @@ + $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); + + $includeFiles = require __DIR__ . '/autoload_files.php'; + foreach ($includeFiles as $file) { + composerRequirebf0ab6890db693133cef0043fae5b370($file); + } + + return $loader; + } +} + +function composerRequirebf0ab6890db693133cef0043fae5b370($file) +{ + require $file; +} diff --git a/wcfsetup/install/files/lib/system/api/composer/installed.json b/wcfsetup/install/files/lib/system/api/composer/installed.json new file mode 100644 index 0000000000..6b6c2056aa --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/composer/installed.json @@ -0,0 +1,174 @@ +[ + { + "name": "php-di/phpdoc-reader", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "21dce5e29f640d655e7b4583ecfb7d166127a5da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/21dce5e29f640d655e7b4583ecfb7d166127a5da", + "reference": "21dce5e29f640d655e7b4583ecfb7d166127a5da", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.6" + }, + "time": "2015-06-01 14:23:20", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ] + }, + { + "name": "container-interop/container-interop", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/fc08354828f8fd3245f77a66b9e23a6bca48297e", + "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e", + "shasum": "" + }, + "time": "2014-12-30 15:22:37", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)" + }, + { + "name": "php-di/invoker", + "version": "1.2.0", + "version_normalized": "1.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "9949fff87fcf14e8f2ccfbe36dac1e5921944c48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/9949fff87fcf14e8f2ccfbe36dac1e5921944c48", + "reference": "9949fff87fcf14e8f2ccfbe36dac1e5921944c48", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "~1.1" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "phpunit/phpunit": "~4.5" + }, + "time": "2015-10-22 19:49:23", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ] + }, + { + "name": "php-di/php-di", + "version": "5.1.0", + "version_normalized": "5.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "f4a8088fa4eb480ee66c51b5ee4e4e30cce78489" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/f4a8088fa4eb480ee66c51b5ee4e4e30cce78489", + "reference": "f4a8088fa4eb480ee66c51b5ee4e4e30cce78489", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "~1.0", + "php": ">=5.4.0", + "php-di/invoker": "^1.0.1", + "php-di/phpdoc-reader": "~2.0" + }, + "replace": { + "mnapoli/php-di": "*" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "doctrine/cache": "~1.4", + "mnapoli/phpunit-easymock": "~0.1.4", + "ocramius/proxy-manager": "~1.0", + "phpunit/phpunit": "~4.5" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "doctrine/cache": "Install it if you want to use the cache (version ~1.4)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~1.0)" + }, + "time": "2015-09-08 08:56:29", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "DI\\": "src/DI/" + }, + "files": [ + "src/DI/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "http://php-di.org/", + "keywords": [ + "container", + "dependency injection", + "di" + ] + } +] diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/.gitignore b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/.gitignore new file mode 100644 index 0000000000..b2395aa055 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/.gitignore @@ -0,0 +1,3 @@ +composer.lock +composer.phar +/vendor/ diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/LICENSE b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/LICENSE new file mode 100644 index 0000000000..7671d9020f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 container-interop + +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/wcfsetup/install/files/lib/system/api/container-interop/container-interop/README.md b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/README.md new file mode 100644 index 0000000000..ec434d0f26 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/README.md @@ -0,0 +1,85 @@ +# Container Interoperability + +[![Latest Stable Version](https://poser.pugx.org/container-interop/container-interop/v/stable.png)](https://packagist.org/packages/container-interop/container-interop) + +*container-interop* tries to identify and standardize features in *container* objects (service locators, +dependency injection containers, etc.) to achieve interopererability. + +Through discussions and trials, we try to create a standard, made of common interfaces but also recommendations. + +If PHP projects that provide container implementations begin to adopt these common standards, then PHP +applications and projects that use containers can depend on the common interfaces instead of specific +implementations. This facilitates a high-level of interoperability and flexibility that allows users to consume +*any* container implementation that can be adapted to these interfaces. + +The work done in this project is not officially endorsed by the [PHP-FIG](http://www.php-fig.org/), but it is being +worked on by members of PHP-FIG and other good developers. We adhere to the spirit and ideals of PHP-FIG, and hope +this project will pave the way for one or more future PSRs. + + +## Installation + +You can install this package through Composer: + +```json +{ + "require": { + "container-interop/container-interop": "~1.0" + } +} +``` + +The packages adheres to the [SemVer](http://semver.org/) specification, and there will be full backward compatibility +between minor versions. + +## Standards + +### Available + +- [`ContainerInterface`](src/Interop/Container/ContainerInterface.php). +[Description](docs/ContainerInterface.md) [Meta Document](docs/ContainerInterface-meta.md). +Describes the interface of a container that exposes methods to read its entries. +- [*Delegate lookup feature*](docs/Delegate-lookup.md). +[Meta Document](docs/Delegate-lookup-meta.md). +Describes the ability for a container to delegate the lookup of its dependencies to a third-party container. This +feature lets several containers work together in a single application. + +### Proposed + +View open [request for comments](https://github.com/container-interop/container-interop/labels/RFC) + +## Compatible projects + +### Projects implementing `ContainerInterface` + +- [Acclimate](https://github.com/jeremeamia/acclimate-container) +- [dcp-di](https://github.com/estelsmith/dcp-di) +- [Mouf](http://mouf-php.com) +- [Njasm Container](https://github.com/njasm/container) +- [PHP-DI](http://php-di.org) +- [PimpleInterop](https://github.com/moufmouf/pimple-interop) +- [XStatic](https://github.com/jeremeamia/xstatic) + +### Projects implementing the *delegate lookup* feature + +- [Mouf](http://mouf-php.com) +- [PHP-DI](http://php-di.org) +- [PimpleInterop](https://github.com/moufmouf/pimple-interop) + +## Workflow + +Everyone is welcome to join and contribute. + +The general workflow looks like this: + +1. Someone opens a discussion (GitHub issue) to suggest an interface +1. Feedback is gathered +1. The interface is added to a development branch +1. We release alpha versions so that the interface can be experimented with +1. Discussions and edits ensue until the interface is deemed stable by a general consensus +1. A new minor version of the package is released + +We try to not break BC by creating new interfaces instead of editing existing ones. + +While we currently work on interfaces, we are open to anything that might help towards interoperability, may that +be code, best practices, etc. diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/composer.json b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/composer.json new file mode 100644 index 0000000000..84f3875282 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/composer.json @@ -0,0 +1,11 @@ +{ + "name": "container-interop/container-interop", + "type": "library", + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "license": "MIT", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + } +} diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/ContainerInterface-meta.md b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/ContainerInterface-meta.md new file mode 100644 index 0000000000..90711c9051 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/ContainerInterface-meta.md @@ -0,0 +1,114 @@ +# ContainerInterface Meta Document + +## Introduction + +This document describes the process and discussions that lead to the `ContainerInterface`. +Its goal is to explain the reasons behind each decision. + +## Goal + +The goal set by `ContainerInterface` is to standardize how frameworks and libraries make use of a +container to obtain objects and parameters. + +By standardizing such a behavior, frameworks and libraries using the `ContainerInterface` +could work with any compatible container. +That would allow end users to choose their own container based on their own preferences. + +It is important to distinguish the two usages of a container: + +- configuring entries +- fetching entries + +Most of the time, those two sides are not used by the same party. +While it is often end users who tend to configure entries, it is generally the framework that fetch +entries to build the application. + +This is why this interface focuses only on how entries can be fetched from a container. + +## Interface name + +The interface name has been thoroughly discussed and was decided by a vote. + +The list of options considered with their respective votes are: + +- `ContainerInterface`: +8 +- `ProviderInterface`: +2 +- `LocatorInterface`: 0 +- `ReadableContainerInterface`: -5 +- `ServiceLocatorInterface`: -6 +- `ObjectFactory`: -6 +- `ObjectStore`: -8 +- `ConsumerInterface`: -9 + +[Full results of the vote](https://github.com/container-interop/container-interop/wiki/%231-interface-name:-Vote) + +The complete discussion can be read in [the issue #1](https://github.com/container-interop/container-interop/issues/1). + +## Interface methods + +The choice of which methods the interface would contain was made after a statistical analysis of existing containers. +The results of this analysis are available [in this document](https://gist.github.com/mnapoli/6159681). + +The summary of the analysis showed that: + +- all containers offer a method to get an entry by its id +- a large majority name such method `get()` +- for all containers, the `get()` method has 1 mandatory parameter of type string +- some containers have an optional additional argument for `get()`, but it doesn't same the same purpose between containers +- a large majority of the containers offer a method to test if it can return an entry by its id +- a majority name such method `has()` +- for all containers offering `has()`, the method has exactly 1 parameter of type string +- a large majority of the containers throw an exception rather than returning null when an entry is not found in `get()` +- a large majority of the containers don't implement `ArrayAccess` + +The question of whether to include methods to define entries has been discussed in +[issue #1](https://github.com/container-interop/container-interop/issues/1). +It has been judged that such methods do not belong in the interface described here because it is out of its scope +(see the "Goal" section). + +As a result, the `ContainerInterface` contains two methods: + +- `get()`, returning anything, with one mandatory string parameter. Should throw an exception if the entry is not found. +- `has()`, returning a boolean, with one mandatory string parameter. + +### Number of parameters in `get()` method + +While `ContainerInterface` only defines one mandatory parameter in `get()`, it is not incompatible with +existing containers that have additional optional parameters. PHP allows an implementation to offer more parameters +as long as they are optional, because the implementation *does* satisfy the interface. + +This issue has been discussed in [issue #6](https://github.com/container-interop/container-interop/issues/6). + +### Type of the `$id` parameter + +The type of the `$id` parameter in `get()` and `has()` has been discussed in +[issue #6](https://github.com/container-interop/container-interop/issues/6). +While `string` is used in all the containers that were analyzed, it was suggested that allowing +anything (such as objects) could allow containers to offer a more advanced query API. + +An example given was to use the container as an object builder. The `$id` parameter would then be an +object that would describe how to create an instance. + +The conclusion of the discussion was that this was beyond the scope of getting entries from a container without +knowing how the container provided them, and it was more fit for a factory. + +## Contributors + +Are listed here all people that contributed in the discussions or votes, by alphabetical order: + +- [Amy Stephen](https://github.com/AmyStephen) +- [David Négrier](https://github.com/moufmouf) +- [Don Gilbert](https://github.com/dongilbert) +- [Jason Judge](https://github.com/judgej) +- [Jeremy Lindblom](https://github.com/jeremeamia) +- [Marco Pivetta](https://github.com/Ocramius) +- [Matthieu Napoli](https://github.com/mnapoli) +- [Paul M. Jones](https://github.com/pmjones) +- [Stephan Hochdörfer](https://github.com/shochdoerfer) +- [Taylor Otwell](https://github.com/taylorotwell) + +## Relevant links + +- [`ContainerInterface.php`](https://github.com/container-interop/container-interop/blob/master/src/Interop/Container/ContainerInterface.php) +- [List of all issues](https://github.com/container-interop/container-interop/issues?labels=ContainerInterface&milestone=&page=1&state=closed) +- [Vote for the interface name](https://github.com/container-interop/container-interop/wiki/%231-interface-name:-Vote) diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/ContainerInterface.md b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/ContainerInterface.md new file mode 100644 index 0000000000..9f609674c8 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/ContainerInterface.md @@ -0,0 +1,153 @@ +Container interface +=================== + +This document describes a common interface for dependency injection containers. + +The goal set by `ContainerInterface` is to standardize how frameworks and libraries make use of a +container to obtain objects and parameters (called *entries* in the rest of this document). + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in [RFC 2119][]. + +The word `implementor` in this document is to be interpreted as someone +implementing the `ContainerInterface` in a depency injection-related library or framework. +Users of dependency injections containers (DIC) are refered to as `user`. + +[RFC 2119]: http://tools.ietf.org/html/rfc2119 + +1. Specification +----------------- + +### 1.1 Basics + +- The `Interop\Container\ContainerInterface` exposes two methods : `get` and `has`. + +- `get` takes one mandatory parameter: an entry identifier. It MUST be a string. + A call to `get` can return anything (a *mixed* value), or throws an exception if the identifier + is not known to the container. Two successive calls to `get` with the same + identifier SHOULD return the same value. However, depending on the `implementor` + design and/or `user` configuration, different values might be returned, so + `user` SHOULD NOT rely on getting the same value on 2 successive calls. + While `ContainerInterface` only defines one mandatory parameter in `get()`, implementations + MAY accept additional optional parameters. + +- `has` takes one unique parameter: an entry identifier. It MUST return `true` + if an entry identifier is known to the container and `false` if it is not. + +### 1.2 Exceptions + +Exceptions directly thrown by the container MUST implement the +[`Interop\Container\Exception\ContainerException`](../src/Interop/Container/Exception/ContainerException.php). + +A call to the `get` method with a non-existing id should throw a +[`Interop\Container\Exception\NotFoundException`](../src/Interop/Container/Exception/NotFoundException.php). + +### 1.3 Additional features + +This section describes additional features that MAY be added to a container. Containers are not +required to implement these features to respect the ContainerInterface. + +#### 1.3.1 Delegate lookup feature + +The goal of the *delegate lookup* feature is to allow several containers to share entries. +Containers implementing this feature can perform dependency lookups in other containers. + +Containers implementing this feature will offer a greater lever of interoperability +with other containers. Implementation of this feature is therefore RECOMMENDED. + +A container implementing this feature: + +- MUST implement the `ContainerInterface` +- MUST provide a way to register a delegate container (using a constructor parameter, or a setter, + or any possible way). The delegate container MUST implement the `ContainerInterface`. + +When a container is configured to use a delegate container for dependencies: + +- Calls to the `get` method should only return an entry if the entry is part of the container. + If the entry is not part of the container, an exception should be thrown + (as requested by the `ContainerInterface`). +- Calls to the `has` method should only return `true` if the entry is part of the container. + If the entry is not part of the container, `false` should be returned. +- If the fetched entry has dependencies, **instead** of performing + the dependency lookup in the container, the lookup is performed on the *delegate container*. + +Important! By default, the lookup SHOULD be performed on the delegate container **only**, not on the container itself. + +It is however allowed for containers to provide exception cases for special entries, and a way to lookup +into the same container (or another container) instead of the delegate container. + +2. Package +---------- + +The interfaces and classes described as well as relevant exception are provided as part of the +[container-interop/container-interop](https://packagist.org/packages/container-interop/container-interop) package. + +3. `Interop\Container\ContainerInterface` +----------------------------------------- + +```php +setParentContainer($this); + } + } + ... + } +} + +``` + +**Cons:** + +Cons have been extensively discussed [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-51721777). +Basically, forcing a setter into an interface is a bad idea. Setters are similar to constructor arguments, +and it's a bad idea to standardize a constructor: how the delegate container is configured into a container is an implementation detail. This outweights the benefits of the interface. + +### 4.4 Alternative: no exception case for delegate lookups + +Originally, the proposed wording for delegate lookup calls was: + +> Important! The lookup MUST be performed on the delegate container **only**, not on the container itself. + +This was later replaced by: + +> Important! By default, the lookup SHOULD be performed on the delegate container **only**, not on the container itself. +> +> It is however allowed for containers to provide exception cases for special entries, and a way to lookup +> into the same container (or another container) instead of the delegate container. + +Exception cases have been allowed to avoid breaking dependencies with some services that must be provided +by the container (on @njasm proposal). This was proposed here: https://github.com/container-interop/container-interop/pull/20#issuecomment-56597235 + +### 4.5 Alternative: having one of the containers act as the composite container + +In real-life scenarios, we usually have a big framework (Symfony 2, Zend Framework 2, etc...) and we want to +add another DI container to this container. Most of the time, the "big" framework will be responsible for +creating the controller's instances, using it's own DI container. Until *container-interop* is fully adopted, +the "big" framework will not be aware of the existence of a composite container that it should use instead +of its own container. + +For this real-life use cases, @mnapoli and @moufmouf proposed to extend the "big" framework's DI container +to make it act as a composite container. + +This has been discussed [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-40367194) +and [here](http://mouf-php.com/container-interop-whats-next#solution4). + +This was implemented in Symfony 2 using: + +- [interop.symfony.di](https://github.com/thecodingmachine/interop.symfony.di/tree/v0.1.0) +- [framework interop](https://github.com/mnapoli/framework-interop/) + +This was implemented in Silex using: + +- [interop.silex.di](https://github.com/thecodingmachine/interop.silex.di) + +Having a container act as the composite container is not part of the delegate lookup standard because it is +simply a temporary design pattern used to make existing frameworks that do not support yet ContainerInterop +play nice with other DI containers. + + +5. Implementations +------------------ + +The following projects already implement the delegate lookup feature: + +- [Mouf](http://mouf-php.com), through the [`setDelegateLookupContainer` method](https://github.com/thecodingmachine/mouf/blob/2.0/src/Mouf/MoufManager.php#L2120) +- [PHP-DI](http://php-di.org/), through the [`$wrapperContainer` parameter of the constructor](https://github.com/mnapoli/PHP-DI/blob/master/src/DI/Container.php#L72) +- [pimple-interop](https://github.com/moufmouf/pimple-interop), through the [`$container` parameter of the constructor](https://github.com/moufmouf/pimple-interop/blob/master/src/Interop/Container/Pimple/PimpleInterop.php#L62) + +6. People +--------- + +Are listed here all people that contributed in the discussions, by alphabetical order: + +- [Alexandru Pătrănescu](https://github.com/drealecs) +- [Ben Peachey](https://github.com/potherca) +- [David Négrier](https://github.com/moufmouf) +- [Jeremy Lindblom](https://github.com/jeremeamia) +- [Marco Pivetta](https://github.com/Ocramius) +- [Matthieu Napoli](https://github.com/mnapoli) +- [Nelson J Morais](https://github.com/njasm) +- [Phil Sturgeon](https://github.com/philsturgeon) +- [Stephan Hochdörfer](https://github.com/shochdoerfer) + +7. Relevant Links +----------------- + +_**Note:** Order descending chronologically._ + +- [Pull request on the delegate lookup feature](https://github.com/container-interop/container-interop/pull/20) +- [Pull request on the interface idea](https://github.com/container-interop/container-interop/pull/8) +- [Original article exposing the delegate lookup idea along many others](http://mouf-php.com/container-interop-whats-next) + diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/Delegate-lookup.md b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/Delegate-lookup.md new file mode 100644 index 0000000000..04eb3aea02 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/Delegate-lookup.md @@ -0,0 +1,60 @@ +Delegate lookup feature +======================= + +This document describes a standard for dependency injection containers. + +The goal set by the *delegate lookup* feature is to allow several containers to share entries. +Containers implementing this feature can perform dependency lookups in other containers. + +Containers implementing this feature will offer a greater lever of interoperability +with other containers. Implementation of this feature is therefore RECOMMENDED. + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in [RFC 2119][]. + +The word `implementor` in this document is to be interpreted as someone +implementing the delegate lookup feature in a dependency injection-related library or framework. +Users of dependency injections containers (DIC) are refered to as `user`. + +[RFC 2119]: http://tools.ietf.org/html/rfc2119 + +1. Vocabulary +------------- + +In a dependency injection container, the container is used to fetch entries. +Entries can have dependencies on other entries. Usually, these other entries are fetched by the container. + +The *delegate lookup* feature is the ability for a container to fetch dependencies in +another container. In the rest of the document, the word "container" will reference the container +implemented by the implementor. The word "delegate container" will reference the container we are +fetching the dependencies from. + +2. Specification +---------------- + +A container implementing the *delegate lookup* feature: + +- MUST implement the [`ContainerInterface`](ContainerInterface.md) +- MUST provide a way to register a delegate container (using a constructor parameter, or a setter, + or any possible way). The delegate container MUST implement the [`ContainerInterface`](ContainerInterface.md). + +When a container is configured to use a delegate container for dependencies: + +- Calls to the `get` method should only return an entry if the entry is part of the container. + If the entry is not part of the container, an exception should be thrown + (as requested by the [`ContainerInterface`](ContainerInterface.md)). +- Calls to the `has` method should only return `true` if the entry is part of the container. + If the entry is not part of the container, `false` should be returned. +- If the fetched entry has dependencies, **instead** of performing + the dependency lookup in the container, the lookup is performed on the *delegate container*. + +Important: By default, the dependency lookups SHOULD be performed on the delegate container **only**, not on the container itself. + +It is however allowed for containers to provide exception cases for special entries, and a way to lookup +into the same container (or another container) instead of the delegate container. + +3. Package / Interface +---------------------- + +This feature is not tied to any code, interface or package. diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/interoperating_containers.png b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/interoperating_containers.png new file mode 100644 index 0000000000000000000000000000000000000000..9c672e16c72a08708ea5bdeb51757c2951d3ee66 GIT binary patch literal 35971 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 literal 0 HcmV?d00001 diff --git a/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/priority.png b/wcfsetup/install/files/lib/system/api/container-interop/container-interop/docs/images/priority.png new file mode 100644 index 0000000000000000000000000000000000000000..5760dc71b557fea7ec5bd5699adfd1c228b9cdec GIT binary patch literal 22949 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'/project/{project}/issue/{issue}', function ($project, $issue) { + // ... +}); +``` + +Or this command defined with [Silly](https://github.com/mnapoli/silly#usage): + +```php +$app->command('greet [name] [--yell]', function ($name, $yell) { + // ... +}); +``` + +Same pattern in [Slim](http://www.slimframework.com): + +```php +$app->get('/hello/:name', function ($name) { + // ... +}); +``` + +You get the point. These frameworks invoke the controller/command/handler using something akin to named parameters: whatever the order of the parameters, they are matched by their name. + +**This library allows to invoke callables with named parameters in a generic and extensible way.** + +### Dependency injection + +Anyone familiar with AngularJS is familiar with how dependency injection is performed: + +```js +angular.controller('MyController', ['dep1', 'dep2', function(dep1, dep2) { + // ... +}]); +``` + +In PHP we find this pattern again in some frameworks and DI containers with partial to full support. For example in Silex you can type-hint the application to get it injected, but it only works with `Silex\Application`: + +```php +$app->get('/hello/{name}', function (Silex\Application $app, $name) { + // ... +}); +``` + +In Silly, it only works with `OutputInterface` to inject the application output: + +```php +$app->command('greet [name]', function ($name, OutputInterface $output) { + // ... +}); +``` + +[PHP-DI](http://php-di.org/doc/container.html) provides a way to invoke a callable and resolve all dependencies from the container using type-hints: + +```php +$container->call(function (Logger $logger, EntityManager $em) { + // ... +}); +``` + +**This library provides clear extension points to let frameworks implement any kind of dependency injection support they want.** + +### TL/DR + +In short, this library is meant to be a base building block for calling a function with named parameters and/or dependency injection. + +## Installation + +```sh +$ composer require PHP-DI/invoker +``` + +## Usage + +### Default behavior + +By default the `Invoker` can call using named parameters: + +```php +$invoker = new Invoker\Invoker; + +$invoker->call(function () { + echo 'Hello world!'; +}); + +// Simple parameter array +$invoker->call(function ($name) { + echo 'Hello ' . $name; +}, ['John']); + +// Named parameters +$invoker->call(function ($name) { + echo 'Hello ' . $name; +}, [ + 'name' => 'John' +]); + +// Use the default value +$invoker->call(function ($name = 'world') { + echo 'Hello ' . $name; +}); + +// Invoke any PHP callable +$invoker->call(['MyClass', 'myStaticMethod']); + +// Using Class::method syntax +$invoker->call('MyClass::myStaticMethod'); +``` + +Dependency injection in parameters is supported but needs to be configured with your container. Read on or jump to [*Built-in support for dependency injection*](#built-in-support-for-dependency-injection) if you are impatient. + +Additionally, callables can also be resolved from your container. Read on or jump to [*Resolving callables from a container*](#resolving-callables-from-a-container) if you are impatient. + +### Parameter resolvers + +Extending the behavior of the `Invoker` is easy and is done by implementing a [`ParameterResolver`](https://github.com/PHP-DI/Invoker/blob/master/src/ParameterResolver/ParameterResolver.php). + +This is explained in details the [Parameter resolvers documentation](doc/parameter-resolvers.md). + +#### Built-in support for dependency injection + +Rather than have you re-implement support for dependency injection with different containers every time, this package ships with 2 optional resolvers: + +- [`TypeHintContainerResolver`](https://github.com/PHP-DI/Invoker/blob/master/src/ParameterResolver/Container/TypeHintContainerResolver.php) + + This resolver will inject container entries by searching for the class name using the type-hint: + + ```php + $invoker->call(function (Psr\Logger\LoggerInterface $logger) { + // ... + }); + ``` + + In this example it will `->get('Psr\Logger\LoggerInterface')` from the container and inject it. + + This resolver is only useful if you store objects in your container using the class (or interface) name. Silex or Symfony for example store services under a custom name (e.g. `twig`, `db`, etc.) instead of the class name: in that case use the resolver shown below. + +- [`ParameterNameContainerResolver`](https://github.com/PHP-DI/Invoker/blob/master/src/ParameterResolver/Container/ParameterNameContainerResolver.php) + + This resolver will inject container entries by searching for the name of the parameter: + + ```php + $invoker->call(function ($twig) { + // ... + }); + ``` + + In this example it will `->get('twig')` from the container and inject it. + +These resolvers can work with any dependency injection container compliant with [container-interop](https://github.com/container-interop/container-interop). If you container is not compliant you can use the [Acclimate](https://github.com/jeremeamia/acclimate-container) package. + +Setting up those resolvers is simple: + +```php +// $container must be an instance of Interop\Container\ContainerInterface +$container = ... + +$containerResolver = new TypeHintContainerResolver($container); +// or +$containerResolver = new ParameterNameContainerResolver($container); + +$invoker = new Invoker\Invoker; +// Register it before all the other parameter resolvers +$invoker->getParameterResolver()->prependResolver($containerResolver); +``` + +You can also register both resolvers at the same time if you wish by prepending both. Implementing support for more tricky things is easy and up to you! + +### Resolving callables from a container + +The `Invoker` can be wired to your DI container to resolve the callables. + +For example with an invokable class: + +```php +class MyHandler +{ + public function __invoke() + { + // ... + } +} + +// By default this doesn't work: an instance of the class should be provided +$invoker->call('MyHandler'); + +// If we set up the container to use +$invoker = new Invoker\Invoker(null, $container); +// Now 'MyHandler' is resolved using the container! +$invoker->call('MyHandler'); +``` + +The same works for a class method: + +```php +class WelcomeController +{ + public function home() + { + // ... + } +} + +// By default this doesn't work: home() is not a static method +$invoker->call(['WelcomeController', 'home']); + +// If we set up the container to use +$invoker = new Invoker\Invoker(null, $container); +// Now 'WelcomeController' is resolved using the container! +$invoker->call(['WelcomeController', 'home']); +// Alternatively we can use the Class::method syntax +$invoker->call('WelcomeController::home'); +``` + +That feature can be used as the base building block for a framework's dispatcher. + +Again, any [container-interop](https://github.com/container-interop/container-interop) compliant container can be provided, and [Acclimate](https://github.com/jeremeamia/acclimate-container) can be used for incompatible containers. diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/composer.json b/wcfsetup/install/files/lib/system/api/php-di/invoker/composer.json new file mode 100644 index 0000000000..3f0c04169b --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/composer.json @@ -0,0 +1,25 @@ +{ + "name": "php-di/invoker", + "description": "Generic and extensible callable invoker", + "keywords": ["invoker", "dependency-injection", "dependency", "injection", "callable", "invoke"], + "homepage": "https://github.com/PHP-DI/Invoker", + "license": "MIT", + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Invoker\\Test\\": "tests/" + } + }, + "require": { + "container-interop/container-interop": "~1.1" + }, + "require-dev": { + "phpunit/phpunit": "~4.5", + "athletic/athletic": "~0.1.8" + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/doc/parameter-resolvers.md b/wcfsetup/install/files/lib/system/api/php-di/invoker/doc/parameter-resolvers.md new file mode 100644 index 0000000000..bfccb7f10e --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/doc/parameter-resolvers.md @@ -0,0 +1,109 @@ +# Parameter resolvers + +Extending the behavior of the `Invoker` is easy and is done by implementing a [`ParameterResolver`](https://github.com/PHP-DI/Invoker/blob/master/src/ParameterResolver/ParameterResolver.php): + +```php +interface ParameterResolver +{ + public function getParameters( + ReflectionFunctionAbstract $reflection, + array $providedParameters, + array $resolvedParameters + ); +} +``` + +- `$providedParameters` contains the parameters provided by the user when calling `$invoker->call($callable, $parameters)` +- `$resolvedParameters` contains parameters that have already been resolved by other parameter resolvers + +An `Invoker` can chain multiple parameter resolvers to mix behaviors, e.g. you can mix "named parameters" support with "dependency injection" support. This is why a `ParameterResolver` should skip parameters that are already resolved in `$resolvedParameters`. + +Here is an implementation example for dumb dependency injection that creates a new instance of the classes type-hinted: + +```php +class MyParameterResolver implements ParameterResolver +{ + public function getParameters( + ReflectionFunctionAbstract $reflection, + array $providedParameters, + array $resolvedParameters + ) { + foreach ($reflection->getParameters() as $index => $parameter) { + if (array_key_exists($index, $resolvedParameters)) { + // Skip already resolved parameters + continue; + } + + $class = $parameter->getClass(); + + if ($class) { + $resolvedParameters[$index] = $class->newInstance(); + } + } + + return $resolvedParameters; + } +} +``` + +To use it: + +```php +$invoker = new Invoker\Invoker(new MyParameterResolver); + +$invoker->call(function (ArticleManager $articleManager) { + $articleManager->publishArticle('Hello world', 'This is the article content.'); +}); +``` + +A new instance of `ArticleManager` will be created by our parameter resolver. + +## Chaining parameter resolvers + +The fun starts to happen when we want to add support for many things: + +- named parameters +- dependency injection for type-hinted parameters +- ... + +This is where we should use the [`ResolverChain`](https://github.com/PHP-DI/Invoker/blob/master/src/ParameterResolver/ResolverChain.php). This resolver implements the [Chain of responsibility](http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern) design pattern. + +For example the default chain is: + +```php +$parameterResolver = new ResolverChain([ + new NumericArrayResolver, + new AssociativeArrayResolver, + new DefaultValueResolver, +]); +``` + +It allows to support even the weirdest use cases like: + +```php +$parameters = []; + +// First parameter will receive "Welcome" +$parameters[] = 'Welcome'; + +// Parameter named "content" will receive "Hello world!" +$parameters['content'] = 'Hello world!'; + +// $published is not defined so it will use its default value +$invoker->call(function ($title, $content, $published = true) { + // ... +}, $parameters); +``` + +We can put our custom parameter resolver in the list and created a super-duper invoker that also supports basic dependency injection: + +```php +$parameterResolver = new ResolverChain([ + new MyParameterResolver, // Our resolver is at the top for highest priority + new NumericArrayResolver, + new AssociativeArrayResolver, + new DefaultValueResolver, +]); + +$invoker = new Invoker\Invoker($parameterResolver); +``` diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/CallableResolver.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/CallableResolver.php new file mode 100644 index 0000000000..eab8c0efce --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/CallableResolver.php @@ -0,0 +1,131 @@ + + */ +class CallableResolver +{ + /** + * @var ContainerInterface + */ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * Resolve the given callable into a real PHP callable. + * + * @param callable|string|array $callable + * + * @return callable Real PHP callable. + * + * @throws NotCallableException + */ + public function resolve($callable) + { + if (is_string($callable) && strpos($callable, '::') !== false) { + $callable = explode('::', $callable, 2); + } + + $callable = $this->resolveFromContainer($callable); + + if (! is_callable($callable)) { + throw new NotCallableException(sprintf( + '%s is not a callable', + is_object($callable) ? 'Instance of ' . get_class($callable) : var_export($callable, true) + )); + } + + return $callable; + } + + /** + * @param callable|string|array $callable + * @return callable + * @throws NotCallableException + */ + private function resolveFromContainer($callable) + { + // Shortcut for a very common use case + if ($callable instanceof \Closure) { + return $callable; + } + + $isStaticCallToNonStaticMethod = false; + + // If it's already a callable there is nothing to do + if (is_callable($callable)) { + $isStaticCallToNonStaticMethod = $this->isStaticCallToNonStaticMethod($callable); + if (! $isStaticCallToNonStaticMethod) { + return $callable; + } + } + + // The callable is a container entry name + if (is_string($callable)) { + if ($this->container->has($callable)) { + return $this->container->get($callable); + } else { + throw new NotCallableException(sprintf( + '"%s" is neither a callable nor a valid container entry', + $callable + )); + } + } + + // The callable is an array whose first item is a container entry name + // e.g. ['some-container-entry', 'methodToCall'] + if (is_array($callable) && is_string($callable[0])) { + if ($this->container->has($callable[0])) { + // Replace the container entry name by the actual object + $callable[0] = $this->container->get($callable[0]); + return $callable; + } elseif ($isStaticCallToNonStaticMethod) { + throw new NotCallableException(sprintf( + 'Cannot call %s::%s() because %s() is not a static method and "%s" is not a container entry', + $callable[0], + $callable[1], + $callable[1], + $callable[0] + )); + } else { + throw new NotCallableException(sprintf( + 'Cannot call %s on %s because it is not a class nor a valid container entry', + $callable[1], + $callable[0] + )); + } + } + + // Unrecognized stuff, we let it fail later + return $callable; + } + + /** + * Check if the callable represents a static call to a non-static method. + * + * @param mixed $callable + * @return bool + */ + private function isStaticCallToNonStaticMethod($callable) + { + if (is_array($callable) && is_string($callable[0])) { + list($class, $method) = $callable; + $reflection = new \ReflectionMethod($class, $method); + + return ! $reflection->isStatic(); + } + + return false; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/InvocationException.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/InvocationException.php new file mode 100644 index 0000000000..a5f6ffcd12 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/InvocationException.php @@ -0,0 +1,12 @@ + + */ +class InvocationException extends \Exception +{ +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/NotCallableException.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/NotCallableException.php new file mode 100644 index 0000000000..1f3e35246a --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/NotCallableException.php @@ -0,0 +1,12 @@ + + */ +class NotCallableException extends InvocationException +{ +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/NotEnoughParametersException.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/NotEnoughParametersException.php new file mode 100644 index 0000000000..1be4eb98ca --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Exception/NotEnoughParametersException.php @@ -0,0 +1,12 @@ + + */ +class NotEnoughParametersException extends InvocationException +{ +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Invoker.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Invoker.php new file mode 100644 index 0000000000..ffdcd0bf20 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Invoker.php @@ -0,0 +1,122 @@ + + */ +class Invoker implements InvokerInterface +{ + /** + * @var CallableResolver|null + */ + private $callableResolver; + + /** + * @var ParameterResolver + */ + private $parameterResolver; + + /** + * @var ContainerInterface|null + */ + private $container; + + public function __construct(ParameterResolver $parameterResolver = null, ContainerInterface $container = null) + { + $this->parameterResolver = $parameterResolver ?: $this->createParameterResolver(); + $this->container = $container; + + if ($container) { + $this->callableResolver = new CallableResolver($container); + } + } + + /** + * {@inheritdoc} + */ + public function call($callable, array $parameters = array()) + { + if ($this->callableResolver) { + $callable = $this->callableResolver->resolve($callable); + } + + if (! is_callable($callable)) { + throw new NotCallableException(sprintf( + '%s is not a callable', + is_object($callable) ? 'Instance of ' . get_class($callable) : var_export($callable, true) + )); + } + + $callableReflection = CallableReflection::create($callable); + + $args = $this->parameterResolver->getParameters($callableReflection, $parameters, array()); + + // Sort by array key because call_user_func_array ignores numeric keys + ksort($args); + + // Check all parameters are resolved + $diff = array_diff_key($callableReflection->getParameters(), $args); + if (! empty($diff)) { + /** @var \ReflectionParameter $parameter */ + $parameter = reset($diff); + throw new NotEnoughParametersException(sprintf( + 'Unable to invoke the callable because no value was given for parameter %d ($%s)', + $parameter->getPosition() + 1, + $parameter->name + )); + } + + return call_user_func_array($callable, $args); + } + + /** + * Create the default parameter resolver. + * + * @return ParameterResolver + */ + private function createParameterResolver() + { + return new ResolverChain(array( + new NumericArrayResolver, + new AssociativeArrayResolver, + new DefaultValueResolver, + )); + } + + /** + * @return ParameterResolver By default it's a ResolverChain + */ + public function getParameterResolver() + { + return $this->parameterResolver; + } + + /** + * @return ContainerInterface|null + */ + public function getContainer() + { + return $this->container; + } + + /** + * @return CallableResolver|null Returns null if no container was given in the constructor. + */ + public function getCallableResolver() + { + return $this->callableResolver; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/InvokerInterface.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/InvokerInterface.php new file mode 100644 index 0000000000..cd6980e2d7 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/InvokerInterface.php @@ -0,0 +1,29 @@ + + */ +interface InvokerInterface +{ + /** + * Call the given function using the given parameters. + * + * @param callable $callable Function to call. + * @param array $parameters Parameters to use. + * + * @return mixed Result of the function. + * + * @throws InvocationException Base exception class for all the sub-exceptions below. + * @throws NotCallableException + * @throws NotEnoughParametersException + */ + public function call($callable, array $parameters = array()); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/AssociativeArrayResolver.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/AssociativeArrayResolver.php new file mode 100644 index 0000000000..d51f2c7d05 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/AssociativeArrayResolver.php @@ -0,0 +1,39 @@ +call($callable, ['foo' => 'bar'])` will inject the string `'bar'` + * in the parameter named `$foo`. + * + * Parameters that are not indexed by a string are ignored. + * + * @author Matthieu Napoli + */ +class AssociativeArrayResolver implements ParameterResolver +{ + public function getParameters( + ReflectionFunctionAbstract $reflection, + array $providedParameters, + array $resolvedParameters + ) { + $parameters = $reflection->getParameters(); + + // Skip parameters already resolved + if (! empty($resolvedParameters)) { + $parameters = array_diff_key($parameters, $resolvedParameters); + } + + foreach ($parameters as $index => $parameter) { + if (array_key_exists($parameter->name, $providedParameters)) { + $resolvedParameters[$index] = $providedParameters[$parameter->name]; + } + } + + return $resolvedParameters; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/Container/ParameterNameContainerResolver.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/Container/ParameterNameContainerResolver.php new file mode 100644 index 0000000000..983dea414f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/Container/ParameterNameContainerResolver.php @@ -0,0 +1,51 @@ + + */ +class ParameterNameContainerResolver implements ParameterResolver +{ + /** + * @var ContainerInterface + */ + private $container; + + /** + * @param ContainerInterface $container The container to get entries from. + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + public function getParameters( + ReflectionFunctionAbstract $reflection, + array $providedParameters, + array $resolvedParameters + ) { + $parameters = $reflection->getParameters(); + + // Skip parameters already resolved + if (! empty($resolvedParameters)) { + $parameters = array_diff_key($parameters, $resolvedParameters); + } + + foreach ($parameters as $index => $parameter) { + $name = $parameter->name; + + if ($name && $this->container->has($name)) { + $resolvedParameters[$index] = $this->container->get($name); + } + } + + return $resolvedParameters; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/Container/TypeHintContainerResolver.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/Container/TypeHintContainerResolver.php new file mode 100644 index 0000000000..65a4079497 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/Container/TypeHintContainerResolver.php @@ -0,0 +1,51 @@ + + */ +class TypeHintContainerResolver implements ParameterResolver +{ + /** + * @var ContainerInterface + */ + private $container; + + /** + * @param ContainerInterface $container The container to get entries from. + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + public function getParameters( + ReflectionFunctionAbstract $reflection, + array $providedParameters, + array $resolvedParameters + ) { + $parameters = $reflection->getParameters(); + + // Skip parameters already resolved + if (! empty($resolvedParameters)) { + $parameters = array_diff_key($parameters, $resolvedParameters); + } + + foreach ($parameters as $index => $parameter) { + $parameterClass = $parameter->getClass(); + + if ($parameterClass && $this->container->has($parameterClass->name)) { + $resolvedParameters[$index] = $this->container->get($parameterClass->name); + } + } + + return $resolvedParameters; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/DefaultValueResolver.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/DefaultValueResolver.php new file mode 100644 index 0000000000..6fa8f8c366 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/DefaultValueResolver.php @@ -0,0 +1,40 @@ + + */ +class DefaultValueResolver implements ParameterResolver +{ + public function getParameters( + ReflectionFunctionAbstract $reflection, + array $providedParameters, + array $resolvedParameters + ) { + $parameters = $reflection->getParameters(); + + // Skip parameters already resolved + if (! empty($resolvedParameters)) { + $parameters = array_diff_key($parameters, $resolvedParameters); + } + + foreach ($parameters as $index => $parameter) { + /** @var \ReflectionParameter $parameter */ + if ($parameter->isOptional()) { + try { + $resolvedParameters[$index] = $parameter->getDefaultValue(); + } catch (ReflectionException $e) { + // Can't get default values from PHP internal classes and functions + } + } + } + + return $resolvedParameters; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/NumericArrayResolver.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/NumericArrayResolver.php new file mode 100644 index 0000000000..dc5f02c46d --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/NumericArrayResolver.php @@ -0,0 +1,39 @@ +call($callable, ['foo', 'bar'])` will simply resolve the parameters + * to `['foo', 'bar']`. + * + * Parameters that are not indexed by a number (i.e. parameter position) + * will be ignored. + * + * @author Matthieu Napoli + */ +class NumericArrayResolver implements ParameterResolver +{ + public function getParameters( + ReflectionFunctionAbstract $reflection, + array $providedParameters, + array $resolvedParameters + ) { + // Skip parameters already resolved + if (! empty($resolvedParameters)) { + $providedParameters = array_diff_key($providedParameters, $resolvedParameters); + } + + foreach ($providedParameters as $key => $value) { + if (is_int($key)) { + $resolvedParameters[$key] = $value; + } + } + + return $resolvedParameters; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/ParameterResolver.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/ParameterResolver.php new file mode 100644 index 0000000000..39c7abcdfd --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/ParameterResolver.php @@ -0,0 +1,33 @@ + + */ +interface ParameterResolver +{ + /** + * Resolves the parameters to use to call the callable. + * + * `$resolvedParameters` contains parameters that have already been resolved. + * + * Each ParameterResolver must resolve parameters that are not already + * in `$resolvedParameters`. That allows to chain multiple ParameterResolver. + * + * @param ReflectionFunctionAbstract $reflection Reflection object for the callable. + * @param array $providedParameters Parameters provided by the caller. + * @param array $resolvedParameters Parameters resolved (indexed by parameter position). + * + * @return array + */ + public function getParameters( + ReflectionFunctionAbstract $reflection, + array $providedParameters, + array $resolvedParameters + ); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/ResolverChain.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/ResolverChain.php new file mode 100644 index 0000000000..d5247462bf --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/ParameterResolver/ResolverChain.php @@ -0,0 +1,69 @@ + + */ +class ResolverChain implements ParameterResolver +{ + /** + * @var ParameterResolver[] + */ + private $resolvers = array(); + + public function __construct(array $resolvers = array()) + { + $this->resolvers = $resolvers; + } + + public function getParameters( + ReflectionFunctionAbstract $reflection, + array $providedParameters, + array $resolvedParameters + ) { + $reflectionParameters = $reflection->getParameters(); + + foreach ($this->resolvers as $resolver) { + $resolvedParameters = $resolver->getParameters( + $reflection, + $providedParameters, + $resolvedParameters + ); + + $diff = array_diff_key($reflectionParameters, $resolvedParameters); + if (empty($diff)) { + // Stop traversing: all parameters are resolved + return $resolvedParameters; + } + } + + return $resolvedParameters; + } + + /** + * Push a parameter resolver after the ones already registered. + * + * @param ParameterResolver $resolver + */ + public function appendResolver(ParameterResolver $resolver) + { + $this->resolvers[] = $resolver; + } + + /** + * Insert a parameter resolver before the ones already registered. + * + * @param ParameterResolver $resolver + */ + public function prependResolver(ParameterResolver $resolver) + { + array_unshift($this->resolvers, $resolver); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Reflection/CallableReflection.php b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Reflection/CallableReflection.php new file mode 100644 index 0000000000..7835f44855 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/invoker/src/Reflection/CallableReflection.php @@ -0,0 +1,57 @@ + + */ +class CallableReflection +{ + /** + * @param callable $callable + * + * @return \ReflectionFunctionAbstract + * + * @throws NotCallableException + * + * TODO Use the `callable` type-hint once support for PHP 5.4 and up. + */ + public static function create($callable) + { + // Closure + if ($callable instanceof \Closure) { + return new \ReflectionFunction($callable); + } + + // Array callable + if (is_array($callable)) { + list($class, $method) = $callable; + + return new \ReflectionMethod($class, $method); + } + + // Callable object (i.e. implementing __invoke()) + if (is_object($callable) && method_exists($callable, '__invoke')) { + return new \ReflectionMethod($callable, '__invoke'); + } + + // Callable class (i.e. implementing __invoke()) + if (is_string($callable) && class_exists($callable) && method_exists($callable, '__invoke')) { + return new \ReflectionMethod($callable, '__invoke'); + } + + // Standard function + if (is_string($callable) && function_exists($callable)) { + return new \ReflectionFunction($callable); + } + + throw new NotCallableException(sprintf( + '%s is not a callable', + is_string($callable) ? $callable : 'Instance of ' . get_class($callable) + )); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/.coveralls.yml b/wcfsetup/install/files/lib/system/api/php-di/php-di/.coveralls.yml new file mode 100644 index 0000000000..bc71b62f87 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/.coveralls.yml @@ -0,0 +1,2 @@ +coverage_clover: clover.xml +json_path: coveralls-upload.json diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/.gitattributes b/wcfsetup/install/files/lib/system/api/php-di/php-di/.gitattributes new file mode 100644 index 0000000000..362238c15d --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/.gitattributes @@ -0,0 +1,8 @@ +# .gitattributes +tests/ export-ignore +website/ export-ignore +doc/ export-ignore +news/ export-ignore + +# Auto detect text files and perform LF normalization +* text=auto diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/.gitignore b/wcfsetup/install/files/lib/system/api/php-di/php-di/.gitignore new file mode 100644 index 0000000000..b39e0cdff5 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/.gitignore @@ -0,0 +1,9 @@ +/.idea/ +/vendor/ +/composer.phar +/composer.lock +/theme/ +/.couscous/ +/website/bower_components/ +/website/css/all.min.css +/logo/ diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/.travis.yml b/wcfsetup/install/files/lib/system/api/php-di/php-di/.travis.yml new file mode 100644 index 0000000000..1fed6a382d --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/.travis.yml @@ -0,0 +1,26 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + +matrix: + include: + - php: 5.4 + env: dependencies=lowest + +before_script: + - composer self-update + - if [[ $(phpenv version-name) == '5.6' ]]; then composer require satooshi/php-coveralls:dev-master -n ; fi + - if [[ $(phpenv version-name) != '5.6' ]]; then composer install -n ; fi + - if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --prefer-stable -n; fi; + +script: + - if [[ $(phpenv version-name) == '5.6' ]]; then phpunit --coverage-clover clover.xml ; fi + - if [[ $(phpenv version-name) != '5.6' ]]; then phpunit ; fi + +after_script: + - if [[ $(phpenv version-name) == '5.6' ]]; then php vendor/bin/coveralls -v ; fi diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/404.md b/wcfsetup/install/files/lib/system/api/php-di/php-di/404.md new file mode 100644 index 0000000000..c4e870fdc7 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/404.md @@ -0,0 +1,3 @@ +--- +layout: 404 +--- diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/CONTRIBUTING.md b/wcfsetup/install/files/lib/system/api/php-di/php-di/CONTRIBUTING.md new file mode 100644 index 0000000000..6e3bd2407b --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing + +[![Build Status](https://travis-ci.org/PHP-DI/PHP-DI.png?branch=master)](https://travis-ci.org/PHP-DI/PHP-DI) [![Coverage Status](https://coveralls.io/repos/PHP-DI/PHP-DI/badge.png?branch=master)](https://coveralls.io/r/PHP-DI/PHP-DI?branch=master) + +PHP-DI is licensed under the MIT License. + + +## Set up + +* Check out the sources using git or download them + +```bash +$ git clone https://github.com/PHP-DI/PHP-DI.git +``` + +* Install the libraries using composer: + +```bash +$ curl -s http://getcomposer.org/installer | php +$ php composer.phar install +``` + +If you are running Windows or are having trouble, read [the official documentation](http://getcomposer.org/doc/00-intro.md#installation). + + +## Run the tests + +The tests are run with [PHPUnit](http://www.phpunit.de/manual/current/en/installation.html): + +```bash +$ phpunit +``` + + +## Learning the internals + +Read the [How it works](doc/how-it-works.md) documentation. + + +## What to do? + +- Add tests: pick up uncovered situations in the [code coverage report](https://coveralls.io/r/PHP-DI/PHP-DI) +- Resolve issues: [issue list](https://github.com/PHP-DI/PHP-DI/issues) +- Improve the documentation +- … + + +## Coding style + +The code follows PSR0, PSR1 and [PSR2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md). + +Also, do not hesitate to add your name to the author list of a class in the docblock if you improve it. diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/LICENSE b/wcfsetup/install/files/lib/system/api/php-di/php-di/LICENSE new file mode 100644 index 0000000000..f24e183de7 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/LICENSE @@ -0,0 +1,18 @@ +PHP-DI - PHP Dependency Injection + +Copyright (C) Matthieu Napoli + +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/wcfsetup/install/files/lib/system/api/php-di/php-di/README.md b/wcfsetup/install/files/lib/system/api/php-di/php-di/README.md new file mode 100644 index 0000000000..7e40802540 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/README.md @@ -0,0 +1,21 @@ +--- +layout: home +--- + +PHP-DI is a Dependency Injection Container made for humans. + +[![Build Status](https://img.shields.io/travis/PHP-DI/PHP-DI.svg?style=flat-square)](https://travis-ci.org/PHP-DI/PHP-DI) +[![HHVM Status](https://img.shields.io/hhvm/php-di/php-di.svg?style=flat-square)](http://hhvm.h4cc.de/package/php-di/php-di) +[![Coverage Status](https://img.shields.io/coveralls/PHP-DI/PHP-DI/master.svg?style=flat-square)](https://coveralls.io/r/PHP-DI/PHP-DI?branch=master) +[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/PHP-DI/PHP-DI.svg?style=flat-square)](https://scrutinizer-ci.com/g/PHP-DI/PHP-DI/?branch=master) +[![Latest Version](https://img.shields.io/github/release/PHP-DI/PHP-DI.svg?style=flat-square)](https://packagist.org/packages/php-di/php-di) +[![Total Downloads](https://img.shields.io/packagist/dt/mnapoli/PHP-DI.svg?style=flat-square)](https://packagist.org/packages/mnapoli/php-di) + +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/PHP-DI/PHP-DI.svg)](http://isitmaintained.com/project/PHP-DI/PHP-DI "Average time to resolve an issue") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/PHP-DI/PHP-DI.svg)](http://isitmaintained.com/project/PHP-DI/PHP-DI "Percentage of issues still open") + +It is meant to be practical, powerful, and framework-agnostic. + +Read more on the website: **[php-di.org](http://php-di.org)** + +Join us in the Gitter chat room: [![Gitter chat](https://badges.gitter.im/PHP-DI/PHP-DI.png)](https://gitter.im/PHP-DI/PHP-DI) diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/change-log.md b/wcfsetup/install/files/lib/system/api/php-di/php-di/change-log.md new file mode 100644 index 0000000000..3a7ba86873 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/change-log.md @@ -0,0 +1,303 @@ +# Change log + +## 5.1 + +Read the [news entry](news/16-php-di-5-1-released.md). + +Improvements: + +- [Zend Framework 2 integration](https://github.com/PHP-DI/ZF2-Bridge) (by @Rastusik) +- [#308](https://github.com/PHP-DI/PHP-DI/pull/308): Instantiate factories using the container (`DI\factory(['FooFactory', 'create'])`) +- Many performances improvements - some benchmarks show up to 35% performance improvements, real results may vary of course +- Many documentation improvements (@jdreesen, @mindplay-dk, @mnapoli, @holtkamp, @Rastusik) +- [#296](https://github.com/PHP-DI/PHP-DI/issues/296): Provide a faster `ArrayCache` implementation, mostly useful in micro-benchmarks + +Bugfixes: + +- [#257](https://github.com/PHP-DI/PHP-DI/issues/257) & [#274](https://github.com/PHP-DI/PHP-DI/issues/274): Private properties of parent classes are not injected when using annotations +- [#300](https://github.com/PHP-DI/PHP-DI/pull/300): Exception if object definition extends an incompatible definition +- [#306](https://github.com/PHP-DI/PHP-DI/issues/306): Errors when using parameters passed by reference (fixed by @bradynpoulsen) +- [#318](https://github.com/PHP-DI/PHP-DI/issues/318): `Container::call()` ignores parameter's default value + +Internal changes: + +- [#276](https://github.com/PHP-DI/PHP-DI/pull/276): Tests now pass on Windows (@bgaillard) + +## 5.0 + +This is the complete change log. You can also read the [migration guide](doc/migration/5.0.md) for upgrading, or [the news article](news/15-php-di-5-0-released.md) for a nicer introduction to this new version. + +Improvements: + +- Moved to an organization on GitHub: [github.com/PHP-DI/PHP-DI](https://github.com/PHP-DI/PHP-DI) +- The package has been renamed to: from `mnapoli/php-di` to [`php-di/php-di`](https://packagist.org/packages/php-di/php-di) +- New [Silex integration](doc/frameworks/silex.md) +- Lighter package: from 10 to 3 Composer dependencies! +- [#235](https://github.com/PHP-DI/PHP-DI/issues/235): `DI\link()` is now deprecated in favor of `DI\get()`. There is no BC break as `DI\link()` still works. +- [#207](https://github.com/PHP-DI/PHP-DI/issues/207): Support for `DI\link()` in arrays +- [#203](https://github.com/PHP-DI/PHP-DI/issues/203): New `DI\string()` helper ([documentation](doc/php-definitions.md)) +- [#208](https://github.com/PHP-DI/PHP-DI/issues/208): Support for nested definitions +- [#226](https://github.com/PHP-DI/PHP-DI/pull/226): `DI\factory()` can now be omitted with closures: + + ```php + // before + 'My\Class' => DI\factory(function () { ... }) + // now (optional shortcut) + 'My\Class' => function () { ... } + ``` +- [#193](https://github.com/PHP-DI/PHP-DI/issues/193): `DI\object()->method()` now supports calling the same method twice (or more). +- [#248](https://github.com/PHP-DI/PHP-DI/issues/248): New `DI\decorate()` helper to decorate a previously defined entry ([documentation](doc/definition-overriding.md)) +- [#215](https://github.com/PHP-DI/PHP-DI/pull/215): New `DI\add()` helper to add entries to an existing array ([documentation](doc/definition-overriding.md)) +- [#218](https://github.com/PHP-DI/PHP-DI/issues/218): `ContainerBuilder::addDefinitions()` can now take an array of definitions +- [#211](https://github.com/PHP-DI/PHP-DI/pull/211): `ContainerBuilder::addDefinitions()` is now fluent (return `$this`) +- [#250](https://github.com/PHP-DI/PHP-DI/issues/250): `Container::call()` now also accepts parameters not indexed by name as well as embedded definitions ([documentation](doc/container.md)) +- Various performance improvements, e.g. lower the number of files loaded, simpler architecture, … + +BC breaks: + +- PHP-DI now requires a version of PHP >= 5.4.0 +- The package is lighter by default: + - [#251](https://github.com/PHP-DI/PHP-DI/issues/251): Annotations are disabled by default, if you use annotations enable them with `$containerBuilder->useAnnotations(true)`. Additionally the `doctrine/annotations` package isn't required by default anymore, so you also need to run `composer require doctrine/annotations`. + - `doctrine/cache` is not installed by default anymore, you need to require it in `composer.json` (`~1.0`) if you want to configure a cache for PHP-DI + - [#198](https://github.com/PHP-DI/PHP-DI/issues/198): `ocramius/proxy-manager` is not installed by default anymore, you need to require it in `composer.json` (`~1.0`) if you want to use **lazy injection** +- Closures are now converted into factory definitions automatically. If you ever defined a closure as a value (e.g. to have the closure injected in a class), you need to wrap the closure with the new `DI\value()` helper. +- [#223](https://github.com/PHP-DI/PHP-DI/issues/223): `DI\ContainerInterface` was deprecated since v4.1 and has been removed + +Internal changes in case you were replacing/extending some parts: + +- the definition sources architecture has been refactored, if you defined custom definition sources you will need to update your code (it should be much easier now) +- [#252](https://github.com/PHP-DI/PHP-DI/pull/252): `DI\Scope` internal implementation has changed. You are encouraged to use the constants (`DI\Scope::SINGLETON` and `DI\Scope::PROTOTYPE`) instead of the static methods, but backward compatibility is kept (static methods still work). +- [#241](https://github.com/PHP-DI/PHP-DI/issues/241): `Container::call()` now uses the *Invoker* external library + +## 4.4 + +Read the [news entry](news/13-php-di-4-4-released.md). + +- [#185](https://github.com/PHP-DI/PHP-DI/issues/185) Support for invokable objects in `Container::call()` +- [#192](https://github.com/PHP-DI/PHP-DI/pull/192) Support for invokable classes in `Container::call()` (will instantiate the class) +- [#184](https://github.com/PHP-DI/PHP-DI/pull/184) Option to ignore phpdoc errors + +## 4.3 + +Read the [news entry](news/11-php-di-4-3-released.md). + +- [#176](https://github.com/PHP-DI/PHP-DI/pull/176) New definition type for reading environment variables: `DI\env()` +- [#181](https://github.com/PHP-DI/PHP-DI/pull/181) `DI\FactoryInterface` and `DI\InvokerInterface` are now auto-registered inside the container so that you can inject them without any configuration needed +- [#173](https://github.com/PHP-DI/PHP-DI/pull/173) `$container->call(['MyClass', 'method]);` will get `MyClass` from the container if `method()` is not a static method + +## 4.2.2 + +- Fixed [#180](https://github.com/PHP-DI/PHP-DI/pull/180): `Container::call()` with object methods (`[$object, 'method']`) is now supported + +## 4.2.1 + +- Support for PHP 5.3.3, which was previously incomplete because of a bug in the reflection (there is now a workaround for this bug) + +But if you can, seriously avoid this (really old) PHP version and upgrade. + +## 4.2 + +Read the [news entry](news/10-php-di-4-2-released.md). + +**Minor BC-break**: Optional parameters (that were not configured) were injected, they are now ignored, which is what naturally makes sense since they are optional. +Example: + +```php + public function __construct(Bar $bar = null) + { + $this->bar = $bar ?: $this->createDefaultBar(); + } +``` + +Before 4.2, PHP-DI would try to inject a `Bar` instance. From 4.2 and onwards, it will inject `null`. + +Of course, you can still explicitly define an injection for the optional parameters and that will work. + +All changes: + +* [#162](https://github.com/PHP-DI/PHP-DI/pull/162) Added `Container::call()` to call functions with dependency injection +* [#156](https://github.com/PHP-DI/PHP-DI/issues/156) Wildcards (`*`) in definitions +* [#164](https://github.com/PHP-DI/PHP-DI/issues/164) Prototype scope is now available for `factory()` definitions too +* FIXED [#168](https://github.com/PHP-DI/PHP-DI/pull/168) `Container::has()` now returns false for interfaces and abstract classes that are not mapped in the definitions +* FIXED [#171](https://github.com/PHP-DI/PHP-DI/issues/171) Optional parameters are now ignored (not injected) if not set in the definitions (see the BC-break warning above) + +## 4.1 + +Read the [news entry](news/09-php-di-4-1-released.md). + +BC-breaks: None. + +* [#138](https://github.com/PHP-DI/PHP-DI/issues/138) [Container-interop](https://github.com/container-interop/container-interop) compliance +* [#143](https://github.com/PHP-DI/PHP-DI/issues/143) Much more explicit exception messages +* [#157](https://github.com/PHP-DI/PHP-DI/issues/157) HHVM support +* [#158](https://github.com/PHP-DI/PHP-DI/issues/158) Improved the documentation for [Symfony 2 integration](http://php-di.org/doc/frameworks/symfony2.html) + +## 4.0 + +Major changes: + +* The configuration format has changed ([read more here to understand why](news/06-php-di-4-0-new-definitions.md)) + +Read the migration guide if you are using 3.x: [Migration guide from 3.x to 4.0](doc/migration/4.0.md). + +BC-breaks: + +* YAML, XML and JSON definitions have been removed, and the PHP definition format has changed (see above) +* `ContainerSingleton` has been removed +* You cannot configure an injection as lazy anymore, you can only configure a container entry as lazy +* The Container constructor now takes mandatory parameters. Use the ContainerBuilder to create a Container. +* Removed `ContainerBuilder::setDefinitionsValidation()` (no definition validation anymore) +* `ContainerBuilder::useReflection()` is now named: `ContainerBuilder::useAutowiring()` +* `ContainerBuilder::addDefinitionsFromFile()` is now named: `ContainerBuilder::addDefinitions()` +* The `$proxy` parameter in `Container::get($name, $proxy = true)` hase been removed. To get a proxy, you now need to define an entry as "lazy". + +Other changes: + +* Added `ContainerInterface` and `FactoryInterface`, both implemented by the container. +* [#115](https://github.com/PHP-DI/PHP-DI/issues/115) Added `Container::has()` +* [#142](https://github.com/PHP-DI/PHP-DI/issues/142) Added `Container::make()` to resolve an entry +* [#127](https://github.com/PHP-DI/PHP-DI/issues/127) Added support for cases where PHP-DI is wrapped by another container (like Acclimate): PHP-DI can now use the wrapping container to perform injections +* [#128](https://github.com/PHP-DI/PHP-DI/issues/128) Configure entry aliases +* [#110](https://github.com/PHP-DI/PHP-DI/issues/110) XML definitions are not supported anymore +* [#122](https://github.com/PHP-DI/PHP-DI/issues/122) JSON definitions are not supported anymore +* `ContainerSingleton` has finally been removed +* Added `ContainerBuilder::buildDevContainer()` to get started with a default container very easily. +* [#99](https://github.com/PHP-DI/PHP-DI/issues/99) Fixed "`@param` with PHP internal type throws exception" + +## 3.5.1 + +* FIXED [#126](https://github.com/PHP-DI/PHP-DI/issues/126): `Container::set` without effect if a value has already been set and retrieved + +## 3.5 + +Read the [news entry](news/05-php-di-3-5.md). + +* Importing `@Inject` and `@Injectable` annotations is now optional! It means that you don't have to write `use DI\Annotation\Inject` anymore +* FIXED [#124](https://github.com/PHP-DI/PHP-DI/issues/124): `@Injects` annotation conflicts with other annotations + +## 3.4 + +Read the [news entry](news/04-php-di-3-4.md). + +* [#106](https://github.com/PHP-DI/PHP-DI/pull/106) You can now define arrays of values (in YAML, PHP, …) thanks to [@unkind](https://github.com/unkind) +* [#98](https://github.com/PHP-DI/PHP-DI/issues/98) `ContainerBuilder` is now fluent thanks to [@drdamour](https://github.com/drdamour) +* [#101](https://github.com/PHP-DI/PHP-DI/pull/101) Optional parameters are now supported: if you don't define a value to inject, their default value will be used +* XML definitions have been deprecated, there weren't even documented and were not maintained. They will be removed in 4.0. +* FIXED [#100](https://github.com/PHP-DI/PHP-DI/issues/100): bug for lazy injection in constructors + +## 3.3 + +Read the [news entry](news/03-php-di-3-3.md). + +* Inject dependencies on an existing instance with `Container::injectOn` (work from [Jeff Flitton](https://github.com/jflitton): [#89](https://github.com/PHP-DI/PHP-DI/pull/89)). +* [#86](https://github.com/PHP-DI/PHP-DI/issues/86): Optimized definition lookup (faster) +* FIXED [#87](https://github.com/PHP-DI/PHP-DI/issues/87): Rare bug in the `PhpDocParser`, fixed by [drdamour](https://github.com/drdamour) + +## 3.2 + +Read the [news entry](news/02-php-di-3-2.md). + +Small BC-break: PHP-DI 3.0 and 3.1 injected properties before calling the constructor. This was confusing and [not supported for internal classes](https://github.com/PHP-DI/PHP-DI/issues/74). +From 3.2 and on, properties are injected after calling the constructor. + +* **[Lazy injection](doc/lazy-injection.md)**: it is now possible to use lazy injection on properties and methods (setters and constructors). +* Lazy dependencies are now proxies that extend the class they proxy, so type-hinting works. +* Addition of the **`ContainerBuilder`** object, that helps to [create and configure a `Container`](doc/container-configuration.md). +* Some methods for configuring the Container have gone **deprecated** in favor of the `ContainerBuilder`. Fear not, these deprecated methods will remain until next major version (4.0). + * `Container::useReflection`, use ContainerBuilder::useReflection instead + * `Container::useAnnotations`, use ContainerBuilder::useAnnotations instead + * `Container::setDefinitionCache`, use ContainerBuilder::setDefinitionCache instead + * `Container::setDefinitionsValidation`, use ContainerBuilder::setDefinitionsValidation instead +* The container is now auto-registered (as 'DI\Container'). You can now inject the container without registering it. + +## 3.1.1 + +* Value definitions (`$container->set('foo', 80)`) are not cached anymore +* FIXED [#82](https://github.com/PHP-DI/PHP-DI/issues/82): Serialization error when using a cache + +## 3.1 + +Read the [news entry](news/01-php-di-3-1.md). + +* Zend Framework 1 integration through the [PHP-DI-ZF1 project](https://github.com/PHP-DI/PHP-DI-ZF1) +* Fixed the order of priorities when you mix different definition sources (reflection, annotations, files, …). See [Definition overriding](doc/definition-overriding.md) +* Now possible to define null values with `$container->set('foo', null)` (see [#79](https://github.com/PHP-DI/PHP-DI/issues/79)). +* Deprecated usage of `ContainerSingleton`, will be removed in next major version (4.0) + +## 3.0.6 + +* FIXED [#76](https://github.com/PHP-DI/PHP-DI/issues/76): Definition conflict when setting a closure for a class name + +## 3.0.5 + +* FIXED [#70](https://github.com/PHP-DI/PHP-DI/issues/70): Definition conflict when setting a value for a class name + +## 3.0.4 + +* FIXED [#69](https://github.com/PHP-DI/PHP-DI/issues/69): YamlDefinitionFileLoader crashes if YAML file is empty + +## 3.0.3 + +* Fixed over-restrictive dependencies in composer.json + +## 3.0.2 + +* [#64](https://github.com/PHP-DI/PHP-DI/issues/64): Non PHP-DI exceptions are not captured-rethrown anymore when injecting dependencies (cleaner stack trace) + +## 3.0.1 + +* [#62](https://github.com/PHP-DI/PHP-DI/issues/62): When using aliases, definitions are now merged + +## 3.0 + +Major compatibility breaks with 2.x. + +* The container is no longer a Singleton (but `ContainerSingleton::getInstance()` is available for fools who like it) +* Setter injection +* Constructor injection +* Scopes: singleton (share the same instance of the class) or prototype (create a new instance each time it is fetched). Defined at class level. +* Configuration is reworked from scratch. Now every configuration backend can do 100% of the job. +* Provided configuration backends: + * Reflection + * Annotations: @Inject, @Injectable + * PHP code (`Container::set()`) + * PHP array + * YAML file +* As a consequence, annotations are not mandatory anymore, all functionalities can be used with or without annotations. +* Renamed `DI\Annotations\` to `DI\Annotation\` +* `Container` no longer implements ArrayAccess, use only `$container->get($key)` now +* ZF1 integration broken and removed (work in progress for next releases) +* Code now follows PSR1 and PSR2 coding styles +* FIXED: [#58](https://github.com/PHP-DI/PHP-DI/issues/58) Getting a proxy of an alias didn't work + +## 2.1 + +* `use` statements to import classes from other namespaces are now taken into account with the `@var` annotation +* Updated and lightened the dependencies : `doctrine/common` has been replaced with more specific `doctrine/annotations` and `doctrine/cache` + +## 2.0 + +Major compatibility breaks with 1.x. + +* `Container::resolveDependencies()` has been renamed to `Container::injectAll()` +* Dependencies are now injected **before** the constructor is called, and thus are available in the constructor +* Merged `@Value` annotation with `@Inject`: no difference between value and bean injection anymore +* Container implements ArrayAccess for get() and set() (`$container['db.host'] = 'localhost';`) +* Ini configuration files removed: configuration is done in PHP +* Allow to define beans within closures for lazy-loading +* Switched to MIT License + +Warning: + +* If you use PHP 5.3 and __wakeup() methods, they will be called when PHP-DI creates new instances of those classes. + +## 1.1 + +* Caching of annotations based on Doctrine caches + +## 1.0 + +* DependencyManager renamed to Container +* Refactored basic Container usage with `get` and `set` +* Allow named injection `@Inject(name="")` +* Zend Framework integration diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/composer.json b/wcfsetup/install/files/lib/system/api/php-di/php-di/composer.json new file mode 100644 index 0000000000..3111f93603 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/composer.json @@ -0,0 +1,43 @@ +{ + "name": "php-di/php-di", + "type": "library", + "description": "The dependency injection container for humans", + "keywords": ["di", "dependency injection", "container"], + "homepage": "http://php-di.org/", + "license": "MIT", + "autoload": { + "psr-4": { + "DI\\": "src/DI/" + }, + "files": [ + "src/DI/functions.php" + ] + }, + "autoload-dev": { + "psr-4": { + "DI\\Test\\IntegrationTest\\": "tests/IntegrationTest/", + "DI\\Test\\UnitTest\\": "tests/UnitTest/" + } + }, + "require": { + "php": ">=5.4.0", + "container-interop/container-interop": "~1.0", + "php-di/invoker": "^1.0.1", + "php-di/phpdoc-reader": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.5", + "mnapoli/phpunit-easymock": "~0.1.4", + "doctrine/cache": "~1.4", + "doctrine/annotations": "~1.2", + "ocramius/proxy-manager": "~1.0" + }, + "replace": { + "mnapoli/php-di": "*" + }, + "suggest": { + "doctrine/cache": "Install it if you want to use the cache (version ~1.4)", + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~1.0)" + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/couscous.yml b/wcfsetup/install/files/lib/system/api/php-di/php-di/couscous.yml new file mode 100644 index 0000000000..c18e3cf688 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/couscous.yml @@ -0,0 +1,110 @@ +baseUrl: http://php-di.org + +scripts: + before: + - lessc --clean-css website/less/main.less website/css/all.min.css + +menu: + items: + introduction: + section: Introduction + items: + getting-started: + text: Getting started + url: doc/getting-started.html + understanding-di: + text: Understanding dependency injection + url: doc/understanding-di.html + best-practices: + text: "\"Best practices\" guide" + url: doc/best-practices.html + usage: + section: Usage + items: + container-configuration: + text: Configuring the container + url: doc/container-configuration.html + container: + text: Using the container + url: doc/container.html + definition: + section: Definitions + items: + introduction: + text: Introduction + url: doc/definition.html + autowiring: + text: Autowiring + url: doc/autowiring.html + php: + text: PHP definitions + url: doc/php-definitions.html + annotations: + text: Annotations + url: doc/annotations.html + definition-overriding: + text: Definition extensions and overriding + url: doc/definition-overriding.html + frameworks: + section: Frameworks + items: + symfony2: + text: Symfony 2 + url: doc/frameworks/symfony2.html + silex: + text: Silex + url: doc/frameworks/silex.html + zf2: + text: Zend Framework 2 + url: doc/frameworks/zf2.html + zf1: + text: Zend Framework 1 + url: doc/frameworks/zf1.html + silly: + text: Silly + url: doc/frameworks/silly.html + advanced: + section: Advanced topics + items: + performances: + text: Performances + url: doc/performances.html + scopes: + text: Scopes + url: doc/scopes.html + lazy-injection: + text: Lazy injection + url: doc/lazy-injection.html + inject-on-instance: + text: Inject on an existing instance + url: doc/inject-on-instance.html + environments: + text: Injections depending on the environment + url: doc/environments.html + migration: + section: Migration guides + items: + 4: + text: From PHP-DI 3.x to 4.0 + url: doc/migration/4.0.html + 5: + text: From PHP-DI 4.x to 5.0 + url: doc/migration/5.0.html + internals: + section: Internals + items: + contributing: + text: Contributing + url: contributing.html + how-it-works: + text: How PHP-DI works + url: doc/how-it-works.html + versions: + section: Old documentation + items: + v3: + text: PHP-DI 3.x + absoluteUrl: https://github.com/PHP-DI/PHP-DI/tree/3.x/doc + v4: + text: PHP-DI 4.x + absoluteUrl: https://github.com/PHP-DI/PHP-DI/tree/4.x/doc diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/phpunit.xml.dist b/wcfsetup/install/files/lib/system/api/php-di/php-di/phpunit.xml.dist new file mode 100644 index 0000000000..81c7f73867 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + ./tests/UnitTest/ + + + ./tests/IntegrationTest/ + + + + + + src + + + + diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Annotation/Inject.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Annotation/Inject.php new file mode 100644 index 0000000000..d0776a62d5 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Annotation/Inject.php @@ -0,0 +1,95 @@ + + */ +final class Inject +{ + /** + * Entry name + * @var string + */ + private $name; + + /** + * Parameters, indexed by the parameter number (index) or name + * + * Used if the annotation is set on a method + * @var array + */ + private $parameters = []; + + /** + * @param array $values + */ + public function __construct(array $values) + { + // Process the parameters as a list AND as a parameter array (we don't know on what the annotation is) + + // @Inject(name="foo") + if (isset($values['name']) && is_string($values['name'])) { + $this->name = $values['name']; + return; + } + + // @Inject + if (! isset($values['value'])) { + return; + } + + $values = $values['value']; + + // @Inject("foo") + if (is_string($values)) { + $this->name = $values; + } + + // @Inject({...}) on a method + if (is_array($values)) { + foreach ($values as $key => $value) { + if (! is_string($value)) { + throw new AnnotationException(sprintf( + '@Inject({"param" = "value"}) expects "value" to be a string, %s given.', + json_encode($value) + )); + } + + $this->parameters[$key] = $value; + } + } + } + + /** + * @return string Name of the entry to inject + */ + public function getName() + { + return $this->name; + } + + /** + * @return array Parameters, indexed by the parameter number (index) or name + */ + public function getParameters() + { + return $this->parameters; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Annotation/Injectable.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Annotation/Injectable.php new file mode 100644 index 0000000000..2176b6782e --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Annotation/Injectable.php @@ -0,0 +1,74 @@ + + * @author Matthieu Napoli + */ +final class Injectable +{ + /** + * The scope of an class: prototype, singleton + * @var string|null + */ + private $scope; + + /** + * Should the object be lazy-loaded + * @var boolean|null + */ + private $lazy; + + /** + * @param array $values + */ + public function __construct(array $values) + { + if (isset($values['scope'])) { + if ($values['scope'] === 'prototype') { + $this->scope = Scope::PROTOTYPE; + } elseif ($values['scope'] === 'singleton') { + $this->scope = Scope::SINGLETON; + } else { + throw new UnexpectedValueException(sprintf("Value '%s' is not a valid scope", $values['scope'])); + } + } + if (isset($values['lazy'])) { + $this->lazy = (boolean) $values['lazy']; + } + } + + /** + * @return string|null + */ + public function getScope() + { + return $this->scope; + } + + /** + * @return boolean|null + */ + public function isLazy() + { + return $this->lazy; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Cache/ArrayCache.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Cache/ArrayCache.php new file mode 100644 index 0000000000..dcd6c0ffcd --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Cache/ArrayCache.php @@ -0,0 +1,79 @@ + + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class ArrayCache implements Cache, FlushableCache, ClearableCache +{ + /** + * @var array $data + */ + private $data = []; + + public function fetch($id) + { + return $this->contains($id) ? $this->data[$id] : false; + } + + public function contains($id) + { + // isset() is required for performance optimizations, to avoid unnecessary function calls to array_key_exists. + return isset($this->data[$id]) || array_key_exists($id, $this->data); + } + + public function save($id, $data, $lifeTime = 0) + { + $this->data[$id] = $data; + + return true; + } + + public function delete($id) + { + unset($this->data[$id]); + + return true; + } + + public function getStats() + { + return null; + } + + public function flushAll() + { + $this->data = array(); + + return true; + } + + public function deleteAll() + { + return $this->flushAll(); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Container.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Container.php new file mode 100644 index 0000000000..642e56b84f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Container.php @@ -0,0 +1,341 @@ + + */ +class Container implements ContainerInterface, FactoryInterface, \DI\InvokerInterface +{ + /** + * Map of entries with Singleton scope that are already resolved. + * @var array + */ + private $singletonEntries = []; + + /** + * @var DefinitionSource + */ + private $definitionSource; + + /** + * @var DefinitionResolver + */ + private $definitionResolver; + + /** + * Array of entries being resolved. Used to avoid circular dependencies and infinite loops. + * @var array + */ + private $entriesBeingResolved = []; + + /** + * @var \Invoker\InvokerInterface|null + */ + private $invoker; + + /** + * Container that wraps this container. If none, points to $this. + * + * @var ContainerInterface + */ + private $wrapperContainer; + + /** + * Use the ContainerBuilder to ease constructing the Container. + * + * @see ContainerBuilder + * + * @param DefinitionSource $definitionSource + * @param ProxyFactory $proxyFactory + * @param ContainerInterface $wrapperContainer If the container is wrapped by another container. + */ + public function __construct( + DefinitionSource $definitionSource, + ProxyFactory $proxyFactory, + ContainerInterface $wrapperContainer = null + ) { + $this->wrapperContainer = $wrapperContainer ?: $this; + + $this->definitionSource = $definitionSource; + $this->definitionResolver = new ResolverDispatcher($this->wrapperContainer, $proxyFactory); + + // Auto-register the container + $this->singletonEntries['DI\Container'] = $this; + $this->singletonEntries['DI\FactoryInterface'] = $this; + $this->singletonEntries['DI\InvokerInterface'] = $this; + } + + /** + * Returns an entry of the container by its name. + * + * @param string $name Entry name or a class name. + * + * @throws InvalidArgumentException The name parameter must be of type string. + * @throws DependencyException Error while resolving the entry. + * @throws NotFoundException No entry found for the given name. + * @return mixed + */ + public function get($name) + { + if (! is_string($name)) { + throw new InvalidArgumentException(sprintf( + 'The name parameter must be of type string, %s given', + is_object($name) ? get_class($name) : gettype($name) + )); + } + + // Try to find the entry in the singleton map + if (array_key_exists($name, $this->singletonEntries)) { + return $this->singletonEntries[$name]; + } + + $definition = $this->definitionSource->getDefinition($name); + if (! $definition) { + throw new NotFoundException("No entry or class found for '$name'"); + } + + $value = $this->resolveDefinition($definition); + + // If the entry is singleton, we store it to always return it without recomputing it + if ($definition->getScope() === Scope::SINGLETON) { + $this->singletonEntries[$name] = $value; + } + + return $value; + } + + /** + * Build an entry of the container by its name. + * + * This method behave like get() except it forces the scope to "prototype", + * which means the definition of the entry will be re-evaluated each time. + * For example, if the entry is a class, then a new instance will be created each time. + * + * This method makes the container behave like a factory. + * + * @param string $name Entry name or a class name. + * @param array $parameters Optional parameters to use to build the entry. Use this to force specific parameters + * to specific values. Parameters not defined in this array will be resolved using + * the container. + * + * @throws InvalidArgumentException The name parameter must be of type string. + * @throws DependencyException Error while resolving the entry. + * @throws NotFoundException No entry found for the given name. + * @return mixed + */ + public function make($name, array $parameters = []) + { + if (! is_string($name)) { + throw new InvalidArgumentException(sprintf( + 'The name parameter must be of type string, %s given', + is_object($name) ? get_class($name) : gettype($name) + )); + } + + $definition = $this->definitionSource->getDefinition($name); + if (! $definition) { + // Try to find the entry in the singleton map + if (array_key_exists($name, $this->singletonEntries)) { + return $this->singletonEntries[$name]; + } + + throw new NotFoundException("No entry or class found for '$name'"); + } + + return $this->resolveDefinition($definition, $parameters); + } + + /** + * Test if the container can provide something for the given name. + * + * @param string $name Entry name or a class name. + * + * @throws InvalidArgumentException The name parameter must be of type string. + * @return bool + */ + public function has($name) + { + if (! is_string($name)) { + throw new InvalidArgumentException(sprintf( + 'The name parameter must be of type string, %s given', + is_object($name) ? get_class($name) : gettype($name) + )); + } + + if (array_key_exists($name, $this->singletonEntries)) { + return true; + } + + $definition = $this->definitionSource->getDefinition($name); + if ($definition === null) { + return false; + } + + return $this->definitionResolver->isResolvable($definition); + } + + /** + * Inject all dependencies on an existing instance + * + * @param object $instance Object to perform injection upon + * @throws InvalidArgumentException + * @throws DependencyException Error while injecting dependencies + * @return object $instance Returns the same instance + */ + public function injectOn($instance) + { + $objectDefinition = $this->definitionSource->getDefinition(get_class($instance)); + if (! $objectDefinition instanceof ObjectDefinition) { + return $instance; + } + + $definition = new InstanceDefinition($instance, $objectDefinition); + + $this->definitionResolver->resolve($definition); + + return $instance; + } + + /** + * Call the given function using the given parameters. + * + * Missing parameters will be resolved from the container. + * + * @param callable $callable Function to call. + * @param array $parameters Parameters to use. Can be indexed by the parameter names + * or not indexed (same order as the parameters). + * The array can also contain DI definitions, e.g. DI\get(). + * + * @return mixed Result of the function. + */ + public function call($callable, array $parameters = []) + { + return $this->getInvoker()->call($callable, $parameters); + } + + /** + * Define an object or a value in the container. + * + * @param string $name Entry name + * @param mixed|DefinitionHelper $value Value, use definition helpers to define objects + */ + public function set($name, $value) + { + if ($value instanceof DefinitionHelper) { + $value = $value->getDefinition($name); + } elseif ($value instanceof \Closure) { + $value = new FactoryDefinition($name, $value); + } + + if ($value instanceof Definition) { + $this->setDefinition($name, $value); + } else { + $this->singletonEntries[$name] = $value; + } + } + + /** + * Resolves a definition. + * + * Checks for circular dependencies while resolving the definition. + * + * @param Definition $definition + * @param array $parameters + * + * @throws DependencyException Error while resolving the entry. + * @return mixed + */ + private function resolveDefinition(Definition $definition, array $parameters = []) + { + $entryName = $definition->getName(); + + // Check if we are already getting this entry -> circular dependency + if (isset($this->entriesBeingResolved[$entryName])) { + throw new DependencyException("Circular dependency detected while trying to resolve entry '$entryName'"); + } + $this->entriesBeingResolved[$entryName] = true; + + // Resolve the definition + try { + $value = $this->definitionResolver->resolve($definition, $parameters); + } catch (Exception $exception) { + unset($this->entriesBeingResolved[$entryName]); + throw $exception; + } + + unset($this->entriesBeingResolved[$entryName]); + + return $value; + } + + private function setDefinition($name, Definition $definition) + { + if ($this->definitionSource instanceof CachedDefinitionSource) { + throw new \LogicException('You cannot set a definition at runtime on a container that has a cache configured. Doing so would risk caching the definition for the next execution, where it might be different. You can either put your definitions in a file, remove the cache or ->set() a raw value directly (PHP object, string, int, ...) instead of a PHP-DI definition.'); + } + + if (! $this->definitionSource instanceof MutableDefinitionSource) { + // This can happen if you instantiate the container yourself + throw new \LogicException('The container has not been initialized correctly'); + } + + // Clear existing entry if it exists + if (array_key_exists($name, $this->singletonEntries)) { + unset($this->singletonEntries[$name]); + } + + $this->definitionSource->addDefinition($definition); + } + + /** + * @return \Invoker\InvokerInterface + */ + private function getInvoker() + { + if (! $this->invoker) { + $parameterResolver = new ResolverChain([ + new DefinitionParameterResolver($this->definitionResolver), + new NumericArrayResolver, + new AssociativeArrayResolver, + new DefaultValueResolver, + new TypeHintContainerResolver($this->wrapperContainer), + ]); + + $this->invoker = new Invoker($parameterResolver, $this); + } + + return $this->invoker; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/ContainerBuilder.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/ContainerBuilder.php new file mode 100644 index 0000000000..53871e2bd0 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/ContainerBuilder.php @@ -0,0 +1,258 @@ +build(); + * + * @since 3.2 + * @author Matthieu Napoli + */ +class ContainerBuilder +{ + /** + * Name of the container class, used to create the container. + * @var string + */ + private $containerClass; + + /** + * @var boolean + */ + private $useAutowiring = true; + + /** + * @var boolean + */ + private $useAnnotations = false; + + /** + * @var boolean + */ + private $ignorePhpDocErrors = false; + + /** + * @var Cache + */ + private $cache; + + /** + * If true, write the proxies to disk to improve performances. + * @var boolean + */ + private $writeProxiesToFile = false; + + /** + * Directory where to write the proxies (if $writeProxiesToFile is enabled). + * @var string + */ + private $proxyDirectory; + + /** + * If PHP-DI is wrapped in another container, this references the wrapper. + * @var ContainerInterface + */ + private $wrapperContainer; + + /** + * @var DefinitionSource[] + */ + private $definitionSources = []; + + /** + * Build a container configured for the dev environment. + * + * @return Container + */ + public static function buildDevContainer() + { + $builder = new self(); + return $builder->build(); + } + + /** + * @param string $containerClass Name of the container class, used to create the container. + */ + public function __construct($containerClass = 'DI\Container') + { + $this->containerClass = $containerClass; + } + + /** + * Build and return a container. + * + * @return Container + */ + public function build() + { + $sources = array_reverse($this->definitionSources); + if ($this->useAnnotations) { + $sources[] = new AnnotationReader($this->ignorePhpDocErrors); + } elseif ($this->useAutowiring) { + $sources[] = new Autowiring(); + } + + $chain = new SourceChain($sources); + + if ($this->cache) { + $source = new CachedDefinitionSource($chain, $this->cache); + $chain->setRootDefinitionSource($source); + } else { + $source = $chain; + // Mutable definition source + $source->setMutableDefinitionSource(new DefinitionArray()); + } + + $proxyFactory = new ProxyFactory($this->writeProxiesToFile, $this->proxyDirectory); + + $containerClass = $this->containerClass; + + return new $containerClass($source, $proxyFactory, $this->wrapperContainer); + } + + /** + * Enable or disable the use of autowiring to guess injections. + * + * Enabled by default. + * + * @param boolean $bool + * @return ContainerBuilder + */ + public function useAutowiring($bool) + { + $this->useAutowiring = $bool; + return $this; + } + + /** + * Enable or disable the use of annotations to guess injections. + * + * Disabled by default. + * + * @param boolean $bool + * @return ContainerBuilder + */ + public function useAnnotations($bool) + { + $this->useAnnotations = $bool; + return $this; + } + + /** + * Enable or disable ignoring phpdoc errors (non-existent classes in `@param` or `@var`) + * + * @param boolean $bool + * @return ContainerBuilder + */ + public function ignorePhpDocErrors($bool) + { + $this->ignorePhpDocErrors = $bool; + return $this; + } + + /** + * Enables the use of a cache for the definitions. + * + * @param Cache $cache Cache backend to use + * @return ContainerBuilder + */ + public function setDefinitionCache(Cache $cache) + { + $this->cache = $cache; + return $this; + } + + /** + * Configure the proxy generation + * + * For dev environment, use writeProxiesToFile(false) (default configuration) + * For production environment, use writeProxiesToFile(true, 'tmp/proxies') + * + * @param boolean $writeToFile If true, write the proxies to disk to improve performances + * @param string|null $proxyDirectory Directory where to write the proxies + * @return ContainerBuilder + * + * @throws InvalidArgumentException when writeToFile is set to true and the proxy directory is null + */ + public function writeProxiesToFile($writeToFile, $proxyDirectory = null) + { + $this->writeProxiesToFile = $writeToFile; + + if ($writeToFile && $proxyDirectory === null) { + throw new InvalidArgumentException( + "The proxy directory must be specified if you want to write proxies on disk" + ); + } + $this->proxyDirectory = $proxyDirectory; + + return $this; + } + + /** + * If PHP-DI's container is wrapped by another container, we can + * set this so that PHP-DI will use the wrapper rather than itself for building objects. + * + * @param ContainerInterface $otherContainer + * @return $this + */ + public function wrapContainer(ContainerInterface $otherContainer) + { + $this->wrapperContainer = $otherContainer; + + return $this; + } + + /** + * Add definitions to the container. + * + * @param string|array|DefinitionSource $definitions Can be an array of definitions, the + * name of a file containing definitions + * or a DefinitionSource object. + * @return $this + */ + public function addDefinitions($definitions) + { + if (is_string($definitions)) { + // File + $definitions = new DefinitionFile($definitions); + } elseif (is_array($definitions)) { + $definitions = new DefinitionArray($definitions); + } elseif (! $definitions instanceof DefinitionSource) { + throw new InvalidArgumentException(sprintf( + '%s parameter must be a string, an array or a DefinitionSource object, %s given', + 'ContainerBuilder::addDefinitions()', + is_object($definitions) ? get_class($definitions) : gettype($definitions) + )); + } + + $this->definitionSources[] = $definitions; + + return $this; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Debug.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Debug.php new file mode 100644 index 0000000000..a8b3ae2101 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Debug.php @@ -0,0 +1,39 @@ + + */ +class Debug +{ + /** + * Dump the definition to a string. + * + * @param Definition $definition + * + * @return string + */ + public static function dumpDefinition(Definition $definition) + { + static $dumper; + + if (! $dumper) { + $dumper = new DefinitionDumperDispatcher(); + } + + return $dumper->dump($definition); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/AliasDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/AliasDefinition.php new file mode 100644 index 0000000000..ea26506b12 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/AliasDefinition.php @@ -0,0 +1,66 @@ + + */ +class AliasDefinition implements CacheableDefinition +{ + /** + * Entry name + * @var string + */ + private $name; + + /** + * Name of the target entry + * @var string + */ + private $targetEntryName; + + /** + * @param string $name Entry name + * @param string $targetEntryName Name of the target entry + */ + public function __construct($name, $targetEntryName) + { + $this->name = $name; + $this->targetEntryName = $targetEntryName; + } + + /** + * @return string Entry name + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getScope() + { + return Scope::PROTOTYPE; + } + + /** + * @return string + */ + public function getTargetEntryName() + { + return $this->targetEntryName; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ArrayDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ArrayDefinition.php new file mode 100644 index 0000000000..f225f89a5d --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ArrayDefinition.php @@ -0,0 +1,66 @@ + + */ +class ArrayDefinition implements Definition +{ + /** + * Entry name + * @var string + */ + private $name; + + /** + * @var array + */ + private $values; + + /** + * @param string $name Entry name + * @param array $values + */ + public function __construct($name, array $values) + { + $this->name = $name; + $this->values = $values; + } + + /** + * @return string Entry name + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getScope() + { + return Scope::SINGLETON; + } + + /** + * @return array + */ + public function getValues() + { + return $this->values; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ArrayDefinitionExtension.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ArrayDefinitionExtension.php new file mode 100644 index 0000000000..e2f2b76f75 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ArrayDefinitionExtension.php @@ -0,0 +1,61 @@ + + */ +class ArrayDefinitionExtension extends ArrayDefinition implements HasSubDefinition +{ + /** + * @var ArrayDefinition + */ + private $subDefinition; + + /** + * {@inheritdoc} + */ + public function getValues() + { + if (! $this->subDefinition) { + return parent::getValues(); + } + + return array_merge($this->subDefinition->getValues(), parent::getValues()); + } + + /** + * @return string + */ + public function getSubDefinitionName() + { + return $this->getName(); + } + + /** + * {@inheritdoc} + */ + public function setSubDefinition(Definition $definition) + { + if (! $definition instanceof ArrayDefinition) { + throw new DefinitionException(sprintf( + 'Definition %s tries to add array entries but the previous definition is not an array', + $this->getName() + )); + } + + $this->subDefinition = $definition; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/CacheableDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/CacheableDefinition.php new file mode 100644 index 0000000000..eca8e3a86b --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/CacheableDefinition.php @@ -0,0 +1,19 @@ + + */ +interface CacheableDefinition extends Definition +{ +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/DecoratorDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/DecoratorDefinition.php new file mode 100644 index 0000000000..68b4b60d65 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/DecoratorDefinition.php @@ -0,0 +1,48 @@ + + */ +class DecoratorDefinition extends FactoryDefinition implements Definition, HasSubDefinition +{ + /** + * @var Definition + */ + private $decorated; + + /** + * @return string + */ + public function getSubDefinitionName() + { + return $this->getName(); + } + + /** + * @param Definition $definition + */ + public function setSubDefinition(Definition $definition) + { + $this->decorated = $definition; + } + + /** + * @return Definition + */ + public function getDecoratedDefinition() + { + return $this->decorated; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Definition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Definition.php new file mode 100644 index 0000000000..849c382c78 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Definition.php @@ -0,0 +1,32 @@ + + */ +interface Definition +{ + /** + * Returns the name of the entry in the container + * + * @return string + */ + public function getName(); + + /** + * Returns the scope of the entry + * + * @return string + */ + public function getScope(); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/AliasDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/AliasDefinitionDumper.php new file mode 100644 index 0000000000..c50c6a7f0d --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/AliasDefinitionDumper.php @@ -0,0 +1,48 @@ + + */ +class AliasDefinitionDumper implements DefinitionDumper +{ + /** + * {@inheritdoc} + */ + public function dump(Definition $definition) + { + if (! $definition instanceof AliasDefinition) { + throw new \InvalidArgumentException(sprintf( + 'This definition dumper is only compatible with AliasDefinition objects, %s given', + get_class($definition) + )); + } + + if ($definition->getName()) { + return sprintf( + "get(%s => %s)", + $definition->getName(), + $definition->getTargetEntryName() + ); + } + + return sprintf( + "get(%s)", + $definition->getTargetEntryName() + ); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ArrayDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ArrayDefinitionDumper.php new file mode 100644 index 0000000000..c31b7b1f38 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ArrayDefinitionDumper.php @@ -0,0 +1,65 @@ + + */ +class ArrayDefinitionDumper implements DefinitionDumper +{ + /** + * {@inheritdoc} + */ + public function dump(Definition $definition) + { + if (! $definition instanceof ArrayDefinition) { + throw new \InvalidArgumentException(sprintf( + 'This definition dumper is only compatible with ArrayDefinition objects, %s given', + get_class($definition) + )); + } + + $str = '[' . PHP_EOL; + + foreach ($definition->getValues() as $key => $value) { + if (is_string($key)) { + $key = "'" . $key . "'"; + } + + $str .= ' ' . $key . ' => '; + + if ($value instanceof DefinitionHelper) { + $nestedDefinition = Debug::dumpDefinition($value->getDefinition('')); + $str .= $this->indent($nestedDefinition); + } else { + $str .= var_export($value, true); + } + + $str .= ',' . PHP_EOL; + } + + $str .= ']'; + + return $str; + } + + private function indent($str) + { + return str_replace(PHP_EOL, PHP_EOL . " ", $str); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DecoratorDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DecoratorDefinitionDumper.php new file mode 100644 index 0000000000..9aaa55fcea --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DecoratorDefinitionDumper.php @@ -0,0 +1,37 @@ + + */ +class DecoratorDefinitionDumper implements DefinitionDumper +{ + /** + * {@inheritdoc} + */ + public function dump(Definition $definition) + { + if (! $definition instanceof DecoratorDefinition) { + throw new \InvalidArgumentException(sprintf( + 'This definition dumper is only compatible with DecoratorDefinition objects, %s given', + get_class($definition) + )); + } + + return 'Decorate(' . $definition->getSubDefinitionName() . ')'; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumper.php new file mode 100644 index 0000000000..7083c97a5c --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumper.php @@ -0,0 +1,30 @@ + + */ +interface DefinitionDumper +{ + /** + * Returns the given definition as string representation. + * + * @param Definition $definition + * + * @return string + */ + public function dump(Definition $definition); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumperDispatcher.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumperDispatcher.php new file mode 100644 index 0000000000..89add31d07 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumperDispatcher.php @@ -0,0 +1,68 @@ + + */ +class DefinitionDumperDispatcher implements DefinitionDumper +{ + /** + * Definition dumpers, indexed by the class of the definition they can dump. + * + * @var DefinitionDumper[]|null + */ + private $dumpers = []; + + public function __construct($dumpers = null) + { + $this->dumpers = $dumpers; + } + + /** + * {@inheritdoc} + */ + public function dump(Definition $definition) + { + $this->initialize(); + + $class = get_class($definition); + + if (! array_key_exists($class, $this->dumpers)) { + throw new \RuntimeException(sprintf( + 'There is no DefinitionDumper capable of dumping this definition of type %s', + $class + )); + } + + $dumper = $this->dumpers[$class]; + + return $dumper->dump($definition); + } + + private function initialize() + { + if ($this->dumpers === null) { + $this->dumpers = [ + 'DI\Definition\ValueDefinition' => new ValueDefinitionDumper(), + 'DI\Definition\FactoryDefinition' => new FactoryDefinitionDumper(), + 'DI\Definition\DecoratorDefinition' => new DecoratorDefinitionDumper(), + 'DI\Definition\AliasDefinition' => new AliasDefinitionDumper(), + 'DI\Definition\ObjectDefinition' => new ObjectDefinitionDumper(), + 'DI\Definition\EnvironmentVariableDefinition' => new EnvironmentVariableDefinitionDumper(), + ]; + } + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/EnvironmentVariableDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/EnvironmentVariableDefinitionDumper.php new file mode 100644 index 0000000000..9886731761 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/EnvironmentVariableDefinitionDumper.php @@ -0,0 +1,63 @@ + + */ +class EnvironmentVariableDefinitionDumper implements DefinitionDumper +{ + /** + * {@inheritdoc} + */ + public function dump(Definition $definition) + { + if (! $definition instanceof EnvironmentVariableDefinition) { + throw new \InvalidArgumentException(sprintf( + 'This definition dumper is only compatible with EnvironmentVariableDefinition objects, %s given', + get_class($definition) + )); + } + + $str = " variable = " . $definition->getVariableName(); + $str .= PHP_EOL . " optional = " . ($definition->isOptional() ? 'yes' : 'no'); + + if ($definition->isOptional()) { + $defaultValue = $definition->getDefaultValue(); + + if ($defaultValue instanceof DefinitionHelper) { + $nestedDefinition = Debug::dumpDefinition($defaultValue->getDefinition('')); + $defaultValueStr = $this->indent($nestedDefinition); + } else { + $defaultValueStr = var_export($defaultValue, true); + } + + $str .= PHP_EOL . " default = " . $defaultValueStr; + } + + return sprintf( + "Environment variable (" . PHP_EOL . "%s" . PHP_EOL . ")", + $str + ); + } + + private function indent($str) + { + return str_replace(PHP_EOL, PHP_EOL . " ", $str); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/FactoryDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/FactoryDefinitionDumper.php new file mode 100644 index 0000000000..8477f5dab8 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/FactoryDefinitionDumper.php @@ -0,0 +1,37 @@ + + */ +class FactoryDefinitionDumper implements DefinitionDumper +{ + /** + * {@inheritdoc} + */ + public function dump(Definition $definition) + { + if (! $definition instanceof FactoryDefinition) { + throw new \InvalidArgumentException(sprintf( + 'This definition dumper is only compatible with FactoryDefinition objects, %s given', + get_class($definition) + )); + } + + return 'Factory'; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ObjectDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ObjectDefinitionDumper.php new file mode 100644 index 0000000000..66d87d778a --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ObjectDefinitionDumper.php @@ -0,0 +1,158 @@ + + */ +class ObjectDefinitionDumper implements DefinitionDumper +{ + /** + * {@inheritdoc} + */ + public function dump(Definition $definition) + { + if (! $definition instanceof ObjectDefinition) { + throw new \InvalidArgumentException(sprintf( + 'This definition dumper is only compatible with ObjectDefinition objects, %s given', + get_class($definition) + )); + } + + $className = $definition->getClassName(); + $classExist = class_exists($className) || interface_exists($className); + + // Class + if (! $classExist) { + $warning = '#UNKNOWN# '; + } else { + $class = new \ReflectionClass($className); + $warning = $class->isInstantiable() ? '' : '#NOT INSTANTIABLE# '; + } + $str = sprintf(' class = %s%s', $warning, $className); + + // Scope + $str .= PHP_EOL . " scope = " . $definition->getScope(); + + // Lazy + $str .= PHP_EOL . " lazy = " . var_export($definition->isLazy(), true); + + if ($classExist) { + // Constructor + $str .= $this->dumpConstructor($className, $definition); + + // Properties + $str .= $this->dumpProperties($definition); + + // Methods + $str .= $this->dumpMethods($className, $definition); + } + + return sprintf("Object (" . PHP_EOL . "%s" . PHP_EOL . ")", $str); + } + + private function dumpConstructor($className, ObjectDefinition $definition) + { + $str = ''; + + $constructorInjection = $definition->getConstructorInjection(); + + if ($constructorInjection !== null) { + $parameters = $this->dumpMethodParameters($className, $constructorInjection); + + $str .= sprintf(PHP_EOL . " __construct(" . PHP_EOL . " %s" . PHP_EOL . " )", $parameters); + } + + return $str; + } + + private function dumpProperties(ObjectDefinition $definition) + { + $str = ''; + + foreach ($definition->getPropertyInjections() as $propertyInjection) { + $value = $propertyInjection->getValue(); + if ($value instanceof EntryReference) { + $valueStr = sprintf('get(%s)', $value->getName()); + } else { + $valueStr = var_export($value, true); + } + + $str .= sprintf(PHP_EOL . " $%s = %s", $propertyInjection->getPropertyName(), $valueStr); + } + + return $str; + } + + private function dumpMethods($className, ObjectDefinition $definition) + { + $str = ''; + + foreach ($definition->getMethodInjections() as $methodInjection) { + $parameters = $this->dumpMethodParameters($className, $methodInjection); + + $str .= sprintf(PHP_EOL . " %s(" . PHP_EOL . " %s" . PHP_EOL . " )", $methodInjection->getMethodName(), $parameters); + } + + return $str; + } + + private function dumpMethodParameters($className, MethodInjection $methodInjection) + { + $methodReflection = new \ReflectionMethod($className, $methodInjection->getMethodName()); + + $args = []; + + $definitionParameters = $methodInjection->getParameters(); + + foreach ($methodReflection->getParameters() as $index => $parameter) { + if (array_key_exists($index, $definitionParameters)) { + $value = $definitionParameters[$index]; + + if ($value instanceof EntryReference) { + $args[] = sprintf('$%s = get(%s)', $parameter->getName(), $value->getName()); + } else { + $args[] = sprintf('$%s = %s', $parameter->getName(), var_export($value, true)); + } + continue; + } + + // If the parameter is optional and wasn't specified, we take its default value + if ($parameter->isOptional()) { + try { + $value = $parameter->getDefaultValue(); + + $args[] = sprintf( + '$%s = (default value) %s', + $parameter->getName(), + var_export($value, true) + ); + continue; + } catch (ReflectionException $e) { + // The default value can't be read through Reflection because it is a PHP internal class + } + } + + $args[] = sprintf('$%s = #UNDEFINED#', $parameter->getName()); + } + + return implode(PHP_EOL . ' ', $args); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/StringDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/StringDefinitionDumper.php new file mode 100644 index 0000000000..b7267d7cf4 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/StringDefinitionDumper.php @@ -0,0 +1,37 @@ + + */ +class StringDefinitionDumper implements DefinitionDumper +{ + /** + * {@inheritdoc} + */ + public function dump(Definition $definition) + { + if (! $definition instanceof StringDefinition) { + throw new \InvalidArgumentException(sprintf( + 'This definition dumper is only compatible with StringDefinition objects, %s given', + get_class($definition) + )); + } + + return $definition->getExpression(); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ValueDefinitionDumper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ValueDefinitionDumper.php new file mode 100644 index 0000000000..c6decdc277 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Dumper/ValueDefinitionDumper.php @@ -0,0 +1,44 @@ + + */ +class ValueDefinitionDumper implements DefinitionDumper +{ + /** + * {@inheritdoc} + */ + public function dump(Definition $definition) + { + if (! $definition instanceof ValueDefinition) { + throw new \InvalidArgumentException(sprintf( + 'This definition dumper is only compatible with ValueDefinition objects, %s given', + get_class($definition) + )); + } + + ob_start(); + + var_dump($definition->getValue()); + + return sprintf( + "Value (" . PHP_EOL . " %s" . PHP_EOL . ")", + trim(ob_get_clean()) + ); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/EntryReference.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/EntryReference.php new file mode 100644 index 0000000000..3a136045d3 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/EntryReference.php @@ -0,0 +1,52 @@ + + */ +class EntryReference implements DefinitionHelper +{ + /** + * Entry name + * @var string + */ + private $name; + + /** + * @param string $entryName Entry name + */ + public function __construct($entryName) + { + $this->name = $entryName; + } + + /** + * @return string Entry name + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getDefinition($entryName) + { + return new AliasDefinition($entryName, $this->name); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/EnvironmentVariableDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/EnvironmentVariableDefinition.php new file mode 100644 index 0000000000..1cc0997765 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/EnvironmentVariableDefinition.php @@ -0,0 +1,116 @@ + + */ +class EnvironmentVariableDefinition implements CacheableDefinition +{ + /** + * Entry name + * @var string + */ + private $name; + + /** + * The name of the environment variable + * @var string + */ + private $variableName; + + /** + * Whether or not the environment variable definition is optional + * + * If true and the environment variable given by $variableName has not been + * defined, $defaultValue is used. + * + * @var boolean + */ + private $isOptional; + + /** + * The default value to use if the environment variable is optional and not provided + * @var mixed + */ + private $defaultValue; + + /** + * @var string|null + */ + private $scope; + + /** + * @param string $name Entry name + * @param string $variableName The name of the environment variable + * @param boolean $isOptional Whether or not the environment variable definition is optional + * @param mixed $defaultValue The default value to use if the environment variable is optional and not provided + */ + public function __construct($name, $variableName, $isOptional = false, $defaultValue = null) + { + $this->name = $name; + $this->variableName = $variableName; + $this->isOptional = $isOptional; + $this->defaultValue = $defaultValue; + } + + /** + * @return string Entry name + */ + public function getName() + { + return $this->name; + } + + /** + * @return string The name of the environment variable + */ + public function getVariableName() + { + return $this->variableName; + } + + /** + * @return boolean Whether or not the environment variable definition is optional + */ + public function isOptional() + { + return $this->isOptional; + } + + /** + * @return mixed The default value to use if the environment variable is optional and not provided + */ + public function getDefaultValue() + { + return $this->defaultValue; + } + + /** + * @param string $scope + */ + public function setScope($scope) + { + $this->scope = $scope; + } + + /** + * {@inheritdoc} + */ + public function getScope() + { + return $this->scope ?: Scope::SINGLETON; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Exception/AnnotationException.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Exception/AnnotationException.php new file mode 100644 index 0000000000..3657c20c9f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Exception/AnnotationException.php @@ -0,0 +1,19 @@ + + */ +class AnnotationException extends DefinitionException +{ +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Exception/DefinitionException.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Exception/DefinitionException.php new file mode 100644 index 0000000000..cfa806aa20 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Exception/DefinitionException.php @@ -0,0 +1,30 @@ + + */ +class DefinitionException extends \Exception +{ + public static function create(Definition $definition, $message) + { + return new self(sprintf( + "%s" . PHP_EOL . "Full definition:" . PHP_EOL . "%s", + $message, + Debug::dumpDefinition($definition) + )); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/FactoryDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/FactoryDefinition.php new file mode 100644 index 0000000000..6c007a08d5 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/FactoryDefinition.php @@ -0,0 +1,75 @@ + + */ +class FactoryDefinition implements Definition +{ + /** + * Entry name. + * @var string + */ + private $name; + + /** + * @var string + */ + private $scope; + + /** + * Callable that returns the value. + * @var callable + */ + private $factory; + + /** + * @param string $name Entry name + * @param callable $factory Callable that returns the value associated to the entry name. + * @param string|null $scope + */ + public function __construct($name, $factory, $scope = null) + { + $this->name = $name; + $this->factory = $factory; + $this->scope = $scope; + } + + /** + * @return string Entry name. + */ + public function getName() + { + return $this->name; + } + + /** + * Default scope is singleton: the callable is called once and the result is shared. + * + * {@inheritdoc} + */ + public function getScope() + { + return $this->scope ?: Scope::SINGLETON; + } + + /** + * @return callable Callable that returns the value associated to the entry name. + */ + public function getCallable() + { + return $this->factory; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/HasSubDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/HasSubDefinition.php new file mode 100644 index 0000000000..76391200a8 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/HasSubDefinition.php @@ -0,0 +1,28 @@ + + */ +interface HasSubDefinition extends Definition +{ + /** + * @return string + */ + public function getSubDefinitionName(); + + /** + * @param Definition $definition + */ + public function setSubDefinition(Definition $definition); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ArrayDefinitionExtensionHelper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ArrayDefinitionExtensionHelper.php new file mode 100644 index 0000000000..6961b4c1df --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ArrayDefinitionExtensionHelper.php @@ -0,0 +1,46 @@ + + */ +class ArrayDefinitionExtensionHelper implements DefinitionHelper +{ + /** + * @var array + */ + private $values = []; + + /** + * @param array $values Values to add to the array. + */ + public function __construct(array $values) + { + $this->values = $values; + } + + /** + * @param string $entryName Container entry name + * + * @return ArrayDefinitionExtension + */ + public function getDefinition($entryName) + { + return new ArrayDefinitionExtension($entryName, $this->values); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/DefinitionHelper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/DefinitionHelper.php new file mode 100644 index 0000000000..2493b2fc73 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/DefinitionHelper.php @@ -0,0 +1,24 @@ + + */ +interface DefinitionHelper +{ + /** + * @param string $entryName Container entry name + * @return \DI\Definition\Definition + */ + public function getDefinition($entryName); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/EnvironmentVariableDefinitionHelper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/EnvironmentVariableDefinitionHelper.php new file mode 100644 index 0000000000..8e525bd0ab --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/EnvironmentVariableDefinitionHelper.php @@ -0,0 +1,64 @@ + + */ +class EnvironmentVariableDefinitionHelper implements DefinitionHelper +{ + /** + * The name of the environment variable + * @var string + */ + private $variableName; + + /** + * Whether or not the environment variable definition is optional + * + * If true and the environment variable given by $variableName has not been + * defined, $defaultValue is used. + * + * @var boolean + */ + private $isOptional; + + /** + * The default value to use if the environment variable is optional and not provided + * @var mixed + */ + private $defaultValue; + + /** + * @param string $variableName The name of the environment variable + * @param boolean $isOptional Whether or not the environment variable definition is optional + * @param mixed $defaultValue The default value to use if the environment variable is optional and not provided + */ + public function __construct($variableName, $isOptional, $defaultValue = null) + { + $this->variableName = $variableName; + $this->isOptional = $isOptional; + $this->defaultValue = $defaultValue; + } + + /** + * @param string $entryName Container entry name + * + * @return EnvironmentVariableDefinition + */ + public function getDefinition($entryName) + { + return new EnvironmentVariableDefinition($entryName, $this->variableName, $this->isOptional, $this->defaultValue); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/FactoryDefinitionHelper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/FactoryDefinitionHelper.php new file mode 100644 index 0000000000..0278023cf1 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/FactoryDefinitionHelper.php @@ -0,0 +1,72 @@ + + */ +class FactoryDefinitionHelper implements DefinitionHelper +{ + /** + * @var callable + */ + private $factory; + + /** + * @var string|null + */ + private $scope; + + /** + * @var bool + */ + private $decorate; + + /** + * @param callable $factory + * @param bool $decorate Is the factory decorating a previous definition? + */ + public function __construct($factory, $decorate = false) + { + $this->factory = $factory; + $this->decorate = $decorate; + } + + /** + * Defines the scope of the entry. + * + * @param string $scope + * + * @return FactoryDefinitionHelper + */ + public function scope($scope) + { + $this->scope = $scope; + return $this; + } + + /** + * @param string $entryName Container entry name + * @return FactoryDefinition + */ + public function getDefinition($entryName) + { + if ($this->decorate) { + return new DecoratorDefinition($entryName, $this->factory, $this->scope); + } + + return new FactoryDefinition($entryName, $this->factory, $this->scope); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ObjectDefinitionHelper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ObjectDefinitionHelper.php new file mode 100644 index 0000000000..a05725b440 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ObjectDefinitionHelper.php @@ -0,0 +1,279 @@ + + */ +class ObjectDefinitionHelper implements DefinitionHelper +{ + /** + * @var string|null + */ + private $className; + + /** + * @var boolean|null + */ + private $lazy; + + /** + * @var string|null + */ + private $scope; + + /** + * Array of constructor parameters. + * @var array + */ + private $constructor = []; + + /** + * Array of properties and their value. + * @var array + */ + private $properties = []; + + /** + * Array of methods and their parameters. + * @var array + */ + private $methods = []; + + /** + * Helper for defining an object. + * + * @param string|null $className Class name of the object. + * If null, the name of the entry (in the container) will be used as class name. + */ + public function __construct($className = null) + { + $this->className = $className; + } + + /** + * Define the entry as lazy. + * + * A lazy entry is created only when it is used, a proxy is injected instead. + * + * @return ObjectDefinitionHelper + */ + public function lazy() + { + $this->lazy = true; + return $this; + } + + /** + * Defines the scope of the entry. + * + * @param string $scope + * + * @return ObjectDefinitionHelper + */ + public function scope($scope) + { + $this->scope = $scope; + return $this; + } + + /** + * Defines the arguments to use to call the constructor. + * + * This method takes a variable number of arguments, example: + * ->constructor($param1, $param2, $param3) + * + * @param mixed ... Parameters to use for calling the constructor of the class. + * + * @return ObjectDefinitionHelper + */ + public function constructor() + { + $this->constructor = func_get_args(); + return $this; + } + + /** + * Defines a value for a specific argument of the constructor. + * + * This method is usually used together with annotations or autowiring, when a parameter + * is not (or cannot be) type-hinted. Using this method instead of constructor() allows to + * avoid defining all the parameters (letting them being resolved using annotations or autowiring) + * and only define one. + * + * @param string $parameter Parameter for which the value will be given. + * @param mixed $value Value to give to this parameter. + * + * @return ObjectDefinitionHelper + */ + public function constructorParameter($parameter, $value) + { + $this->constructor[$parameter] = $value; + return $this; + } + + /** + * Defines a value to inject in a property of the object. + * + * @param string $property Entry in which to inject the value. + * @param mixed $value Value to inject in the property. + * + * @return ObjectDefinitionHelper + */ + public function property($property, $value) + { + $this->properties[$property] = $value; + return $this; + } + + /** + * Defines a method to call and the arguments to use. + * + * This method takes a variable number of arguments after the method name, example: + * + * ->method('myMethod', $param1, $param2) + * + * Can be used multiple times to declare multiple calls. + * + * @param string $method Name of the method to call. + * @param mixed ... Parameters to use for calling the method. + * + * @return ObjectDefinitionHelper + */ + public function method($method) + { + $args = func_get_args(); + array_shift($args); + + if (! isset($this->methods[$method])) { + $this->methods[$method] = []; + } + + $this->methods[$method][] = $args; + + return $this; + } + + /** + * Defines a method to call and a value for a specific argument. + * + * This method is usually used together with annotations or autowiring, when a parameter + * is not (or cannot be) type-hinted. Using this method instead of method() allows to + * avoid defining all the parameters (letting them being resolved using annotations or + * autowiring) and only define one. + * + * If multiple calls to the method have been configured already (e.g. in a previous definition) + * then this method only overrides the parameter for the *first* call. + * + * @param string $method Name of the method to call. + * @param string $parameter Name or index of the parameter for which the value will be given. + * @param mixed $value Value to give to this parameter. + * + * @return ObjectDefinitionHelper + */ + public function methodParameter($method, $parameter, $value) + { + // Special case for the constructor + if ($method === '__construct') { + $this->constructor[$parameter] = $value; + return $this; + } + + if (! isset($this->methods[$method])) { + $this->methods[$method] = [0 => []]; + } + + $this->methods[$method][0][$parameter] = $value; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getDefinition($entryName) + { + $definition = new ObjectDefinition($entryName, $this->className); + + if ($this->lazy !== null) { + $definition->setLazy($this->lazy); + } + if ($this->scope !== null) { + $definition->setScope($this->scope); + } + + if (! empty($this->constructor)) { + $parameters = $this->fixParameters($definition, '__construct', $this->constructor); + $constructorInjection = MethodInjection::constructor($parameters); + $definition->setConstructorInjection($constructorInjection); + } + + if (! empty($this->properties)) { + foreach ($this->properties as $property => $value) { + $definition->addPropertyInjection( + new PropertyInjection($property, $value) + ); + } + } + + if (! empty($this->methods)) { + foreach ($this->methods as $method => $calls) { + foreach ($calls as $parameters) { + $parameters = $this->fixParameters($definition, $method, $parameters); + $methodInjection = new MethodInjection($method, $parameters); + $definition->addMethodInjection($methodInjection); + } + } + } + + return $definition; + } + + /** + * Fixes parameters indexed by the parameter name -> reindex by position. + * + * This is necessary so that merging definitions between sources is possible. + * + * @param ObjectDefinition $definition + * @param string $method + * @param array $parameters + * @throws DefinitionException + * @return array + */ + private function fixParameters(ObjectDefinition $definition, $method, $parameters) + { + $fixedParameters = []; + + foreach ($parameters as $index => $parameter) { + // Parameter indexed by the parameter name, we reindex it with its position + if (is_string($index)) { + $callable = [$definition->getClassName(), $method]; + try { + $reflectionParameter = new \ReflectionParameter($callable, $index); + } catch (\ReflectionException $e) { + throw DefinitionException::create($definition, "Parameter with name '$index' could not be found"); + } + + $index = $reflectionParameter->getPosition(); + } + + $fixedParameters[$index] = $parameter; + } + + return $fixedParameters; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/StringDefinitionHelper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/StringDefinitionHelper.php new file mode 100644 index 0000000000..b6949527c8 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/StringDefinitionHelper.php @@ -0,0 +1,39 @@ + + */ +class StringDefinitionHelper implements DefinitionHelper +{ + /** + * @var string + */ + private $expression; + + public function __construct($expression) + { + $this->expression = $expression; + } + + /** + * @param string $entryName Container entry name + * + * @return StringDefinition + */ + public function getDefinition($entryName) + { + return new StringDefinition($entryName, $this->expression); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ValueDefinitionHelper.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ValueDefinitionHelper.php new file mode 100644 index 0000000000..436ebe31d9 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Helper/ValueDefinitionHelper.php @@ -0,0 +1,42 @@ + + */ +class ValueDefinitionHelper implements DefinitionHelper +{ + /** + * @var mixed + */ + private $value; + + /** + * @param mixed $value + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * @param string $entryName Container entry name + * @return ValueDefinition + */ + public function getDefinition($entryName) + { + return new ValueDefinition($entryName, $this->value); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/InstanceDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/InstanceDefinition.php new file mode 100644 index 0000000000..7be8e6571f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/InstanceDefinition.php @@ -0,0 +1,76 @@ + + */ +class InstanceDefinition implements Definition +{ + /** + * Instance on which to inject dependencies. + * + * @var object + */ + private $instance; + + /** + * @var ObjectDefinition + */ + private $objectDefinition; + + /** + * @param object $instance + * @param ObjectDefinition $objectDefinition + */ + public function __construct($instance, ObjectDefinition $objectDefinition) + { + $this->instance = $instance; + $this->objectDefinition = $objectDefinition; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + // Name are superfluous for instance definitions + return ''; + } + + /** + * {@inheritdoc} + */ + public function getScope() + { + return Scope::PROTOTYPE; + } + + /** + * @return object + */ + public function getInstance() + { + return $this->instance; + } + + /** + * @return ObjectDefinition + */ + public function getObjectDefinition() + { + return $this->objectDefinition; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition.php new file mode 100644 index 0000000000..03cbf99be9 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition.php @@ -0,0 +1,337 @@ + + */ +class ObjectDefinition implements Definition, CacheableDefinition, HasSubDefinition +{ + /** + * Entry name (most of the time, same as $classname) + * @var string + */ + private $name; + + /** + * Class name (if null, then the class name is $name) + * @var string|null + */ + private $className; + + /** + * Constructor parameter injection + * @var MethodInjection|null + */ + private $constructorInjection; + + /** + * Property injections + * @var PropertyInjection[] + */ + private $propertyInjections = []; + + /** + * Method calls + * @var MethodInjection[][] + */ + private $methodInjections = []; + + /** + * @var string|null + */ + private $scope; + + /** + * @var boolean|null + */ + private $lazy; + + /** + * Store if the class exists. Storing it (in cache) avoids recomputing this. + * + * @var bool + */ + private $classExists; + + /** + * Store if the class is instantiable. Storing it (in cache) avoids recomputing this. + * + * @var bool + */ + private $isInstantiable; + + /** + * @param string $name Class name + * @param string $className + */ + public function __construct($name, $className = null) + { + $this->name = (string) $name; + $this->setClassName($className); + } + + /** + * @return string Entry name + */ + public function getName() + { + return $this->name; + } + + /** + * @param string|null $className + */ + public function setClassName($className) + { + $this->className = $className; + + $this->updateCache(); + } + + /** + * @return string Class name + */ + public function getClassName() + { + if ($this->className !== null) { + return $this->className; + } + return $this->name; + } + + /** + * @return MethodInjection|null + */ + public function getConstructorInjection() + { + return $this->constructorInjection; + } + + /** + * @param MethodInjection $constructorInjection + */ + public function setConstructorInjection(MethodInjection $constructorInjection) + { + $this->constructorInjection = $constructorInjection; + } + + /** + * @return PropertyInjection[] Property injections + */ + public function getPropertyInjections() + { + return $this->propertyInjections; + } + + /** + * @param string $propertyName + * @return PropertyInjection + */ + public function getPropertyInjection($propertyName) + { + return isset($this->propertyInjections[$propertyName]) ? $this->propertyInjections[$propertyName] : null; + } + + /** + * @param PropertyInjection $propertyInjection + */ + public function addPropertyInjection(PropertyInjection $propertyInjection) + { + $this->propertyInjections[$propertyInjection->getPropertyName()] = $propertyInjection; + } + + /** + * @return MethodInjection[] Method injections + */ + public function getMethodInjections() + { + // Return array leafs + $injections = []; + array_walk_recursive($this->methodInjections, function ($injection) use (&$injections) { + $injections[] = $injection; + }); + return $injections; + } + + /** + * @param MethodInjection $methodInjection + */ + public function addMethodInjection(MethodInjection $methodInjection) + { + $method = $methodInjection->getMethodName(); + if (! isset($this->methodInjections[$method])) { + $this->methodInjections[$method] = []; + } + $this->methodInjections[$method][] = $methodInjection; + } + + /** + * @param string $scope + */ + public function setScope($scope) + { + $this->scope = $scope; + } + + /** + * {@inheritdoc} + */ + public function getScope() + { + return $this->scope ?: Scope::SINGLETON; + } + + /** + * @param boolean|null $lazy + */ + public function setLazy($lazy) + { + $this->lazy = $lazy; + } + + /** + * @return bool + */ + public function isLazy() + { + if ($this->lazy !== null) { + return $this->lazy; + } else { + // Default value + return false; + } + } + + /** + * @return bool + */ + public function classExists() + { + return $this->classExists; + } + + /** + * @return bool + */ + public function isInstantiable() + { + return $this->isInstantiable; + } + + /** + * {@inheritdoc} + */ + public function getSubDefinitionName() + { + return $this->getClassName(); + } + + /** + * {@inheritdoc} + */ + public function setSubDefinition(Definition $definition) + { + if (! $definition instanceof ObjectDefinition) { + return; + } + + // The current prevails + if ($this->className === null) { + $this->setClassName($definition->className); + } + if ($this->scope === null) { + $this->scope = $definition->scope; + } + if ($this->lazy === null) { + $this->lazy = $definition->lazy; + } + + // Merge constructor injection + $this->mergeConstructorInjection($definition); + + // Merge property injections + $this->mergePropertyInjections($definition); + + // Merge method injections + $this->mergeMethodInjections($definition); + } + + private function mergeConstructorInjection(ObjectDefinition $definition) + { + if ($definition->getConstructorInjection() !== null) { + if ($this->constructorInjection !== null) { + // Merge + $this->constructorInjection->merge($definition->getConstructorInjection()); + } else { + // Set + $this->constructorInjection = $definition->getConstructorInjection(); + } + } + } + + private function mergePropertyInjections(ObjectDefinition $definition) + { + foreach ($definition->getPropertyInjections() as $propertyName => $propertyInjection) { + if (! array_key_exists($propertyName, $this->propertyInjections)) { + // Add + $this->propertyInjections[$propertyName] = $propertyInjection; + } + } + } + + private function mergeMethodInjections(ObjectDefinition $definition) + { + foreach ($definition->methodInjections as $methodName => $calls) { + if (array_key_exists($methodName, $this->methodInjections)) { + $this->mergeMethodCalls($calls, $methodName); + } else { + // Add + $this->methodInjections[$methodName] = $calls; + } + } + } + + private function mergeMethodCalls(array $calls, $methodName) + { + foreach ($calls as $index => $methodInjection) { + // Merge + if (array_key_exists($index, $this->methodInjections[$methodName])) { + // Merge + $this->methodInjections[$methodName][$index]->merge($methodInjection); + } else { + // Add + $this->methodInjections[$methodName][$index] = $methodInjection; + } + } + } + + private function updateCache() + { + $className = $this->getClassName(); + + $this->classExists = class_exists($className) || interface_exists($className); + + if (! $this->classExists) { + $this->isInstantiable = false; + return; + } + + $class = new ReflectionClass($className); + $this->isInstantiable = $class->isInstantiable(); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition/MethodInjection.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition/MethodInjection.php new file mode 100644 index 0000000000..5861641fdb --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition/MethodInjection.php @@ -0,0 +1,94 @@ + + */ +class MethodInjection implements Definition +{ + /** + * @var string + */ + private $methodName; + + /** + * @var array + */ + private $parameters = []; + + /** + * @param string $methodName + * @param array $parameters + */ + public function __construct($methodName, array $parameters = []) + { + $this->methodName = (string) $methodName; + $this->parameters = $parameters; + } + + public static function constructor(array $parameters = []) + { + return new self('__construct', $parameters); + } + + /** + * @return string Method name + */ + public function getMethodName() + { + return $this->methodName; + } + + /** + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Replace the parameters of the definition by a new array of parameters. + * + * @param array $parameters + */ + public function replaceParameters(array $parameters) + { + $this->parameters = $parameters; + } + + public function merge(MethodInjection $definition) + { + // In case of conflicts, the current definition prevails. + $this->parameters = $this->parameters + $definition->parameters; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return null; + } + + /** + * {@inheritdoc} + */ + public function getScope() + { + return Scope::PROTOTYPE; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition/PropertyInjection.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition/PropertyInjection.php new file mode 100644 index 0000000000..07ecf9e322 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ObjectDefinition/PropertyInjection.php @@ -0,0 +1,74 @@ + + */ +class PropertyInjection +{ + /** + * Property name + * @var string + */ + private $propertyName; + + /** + * Value that should be injected in the property + * @var mixed + */ + private $value; + + /** + * Use for injecting in properties of parent classes: the class name + * must be the name of the parent class because private properties + * can be attached to the parent classes, not the one we are resolving. + * @var string|null + */ + private $className; + + /** + * @param string $propertyName Property name + * @param mixed $value Value that should be injected in the property + * @param string|null $className + */ + public function __construct($propertyName, $value, $className = null) + { + $this->propertyName = (string) $propertyName; + $this->value = $value; + $this->className = $className; + } + + /** + * @return string Property name + */ + public function getPropertyName() + { + return $this->propertyName; + } + + /** + * @return string Value that should be injected in the property + */ + public function getValue() + { + return $this->value; + } + + /** + * @return string|null + */ + public function getClassName() + { + return $this->className; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/AliasResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/AliasResolver.php new file mode 100644 index 0000000000..b92772d381 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/AliasResolver.php @@ -0,0 +1,63 @@ + + */ +class AliasResolver implements DefinitionResolver +{ + /** + * @var ContainerInterface + */ + private $container; + + /** + * The resolver needs a container. + * This container will be used to get the entry to which the alias points to. + * + * @param ContainerInterface $container + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * Resolve an alias definition to a value. + * + * This will return the entry the alias points to. + * + * @param AliasDefinition $definition + * + * {@inheritdoc} + */ + public function resolve(Definition $definition, array $parameters = []) + { + return $this->container->get($definition->getTargetEntryName()); + } + + /** + * @param AliasDefinition $definition + * + * {@inheritdoc} + */ + public function isResolvable(Definition $definition, array $parameters = []) + { + return $this->container->has($definition->getTargetEntryName()); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ArrayResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ArrayResolver.php new file mode 100644 index 0000000000..dcc76822bc --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ArrayResolver.php @@ -0,0 +1,91 @@ + + */ +class ArrayResolver implements DefinitionResolver +{ + /** + * @var DefinitionResolver + */ + private $definitionResolver; + + /** + * @param DefinitionResolver $definitionResolver Used to resolve nested definitions. + */ + public function __construct(DefinitionResolver $definitionResolver) + { + $this->definitionResolver = $definitionResolver; + } + + /** + * Resolve an array definition to a value. + * + * An array definition can contain simple values or references to other entries. + * + * @param ArrayDefinition $definition + * + * {@inheritdoc} + */ + public function resolve(Definition $definition, array $parameters = []) + { + $values = $definition->getValues(); + + $values = $this->resolveNestedDefinitions($definition, $values); + + return $values; + } + + /** + * {@inheritdoc} + */ + public function isResolvable(Definition $definition, array $parameters = []) + { + return true; + } + + private function resolveNestedDefinitions(ArrayDefinition $definition, array $values) + { + foreach ($values as $key => $value) { + if ($value instanceof DefinitionHelper) { + $values[$key] = $this->resolveDefinition($value, $definition, $key); + } + } + + return $values; + } + + private function resolveDefinition(DefinitionHelper $value, ArrayDefinition $definition, $key) + { + try { + return $this->definitionResolver->resolve($value->getDefinition('')); + } catch (DependencyException $e) { + throw $e; + } catch (Exception $e) { + throw new DependencyException(sprintf( + "Error while resolving %s[%s]. %s", + $definition->getName(), + $key, + $e->getMessage() + ), 0, $e); + } + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/DecoratorResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/DecoratorResolver.php new file mode 100644 index 0000000000..db19a60cb6 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/DecoratorResolver.php @@ -0,0 +1,93 @@ + + */ +class DecoratorResolver implements DefinitionResolver +{ + /** + * @var ContainerInterface + */ + private $container; + + /** + * @var DefinitionResolver + */ + private $definitionResolver; + + /** + * The resolver needs a container. This container will be passed to the factory as a parameter + * so that the factory can access other entries of the container. + * + * @param ContainerInterface $container + * @param DefinitionResolver $definitionResolver Used to resolve nested definitions. + */ + public function __construct(ContainerInterface $container, DefinitionResolver $definitionResolver) + { + $this->container = $container; + $this->definitionResolver = $definitionResolver; + } + + /** + * Resolve a decorator definition to a value. + * + * This will call the callable of the definition and pass it the decorated entry. + * + * @param DecoratorDefinition $definition + * + * {@inheritdoc} + */ + public function resolve(Definition $definition, array $parameters = []) + { + $callable = $definition->getCallable(); + + if (! is_callable($callable)) { + throw new DefinitionException(sprintf( + 'The decorator "%s" is not callable', + $definition->getName() + )); + } + + $decoratedDefinition = $definition->getDecoratedDefinition(); + + if (! $decoratedDefinition instanceof Definition) { + if (! $definition->getSubDefinitionName()) { + throw new DefinitionException('Decorators cannot be nested in another definition'); + } + + throw new DefinitionException(sprintf( + 'Entry "%s" decorates nothing: no previous definition with the same name was found', + $definition->getName() + )); + } + + $decorated = $this->definitionResolver->resolve($decoratedDefinition); + + return call_user_func($callable, $decorated, $this->container); + } + + /** + * {@inheritdoc} + */ + public function isResolvable(Definition $definition, array $parameters = []) + { + return true; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/DefinitionResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/DefinitionResolver.php new file mode 100644 index 0000000000..a4358b15ba --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/DefinitionResolver.php @@ -0,0 +1,44 @@ + + */ +interface DefinitionResolver +{ + /** + * Resolve a definition to a value. + * + * @param Definition $definition Object that defines how the value should be obtained. + * @param array $parameters Optional parameters to use to build the entry. + * + * @throws DefinitionException If the definition cannot be resolved. + * + * @return mixed Value obtained from the definition. + */ + public function resolve(Definition $definition, array $parameters = []); + + /** + * Check if a definition can be resolved. + * + * @param Definition $definition Object that defines how the value should be obtained. + * @param array $parameters Optional parameters to use to build the entry. + * + * @return bool + */ + public function isResolvable(Definition $definition, array $parameters = []); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/EnvironmentVariableResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/EnvironmentVariableResolver.php new file mode 100644 index 0000000000..0a9849ca3f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/EnvironmentVariableResolver.php @@ -0,0 +1,81 @@ + + */ +class EnvironmentVariableResolver implements DefinitionResolver +{ + /** + * @var DefinitionResolver + */ + private $definitionResolver; + + /** + * @var callable + */ + private $variableReader; + + public function __construct(DefinitionResolver $definitionResolver, $variableReader = 'getenv') + { + $this->definitionResolver = $definitionResolver; + $this->variableReader = $variableReader; + } + + /** + * Resolve an environment variable definition to a value. + * + * @param EnvironmentVariableDefinition $definition + * + * {@inheritdoc} + */ + public function resolve(Definition $definition, array $parameters = []) + { + $value = call_user_func($this->variableReader, $definition->getVariableName()); + + if (false !== $value) { + return $value; + } + + if (!$definition->isOptional()) { + throw new DefinitionException(sprintf( + "The environment variable '%s' has not been defined", + $definition->getVariableName() + )); + } + + $value = $definition->getDefaultValue(); + + // Nested definition + if ($value instanceof DefinitionHelper) { + return $this->definitionResolver->resolve($value->getDefinition('')); + } + + return $value; + } + + /** + * @param EnvironmentVariableDefinition $definition + * + * {@inheritdoc} + */ + public function isResolvable(Definition $definition, array $parameters = []) + { + return true; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/FactoryResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/FactoryResolver.php new file mode 100644 index 0000000000..28c797b70f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/FactoryResolver.php @@ -0,0 +1,82 @@ + + */ +class FactoryResolver implements DefinitionResolver +{ + /** + * @var ContainerInterface + */ + private $container; + + /** + * @var Invoker|null + */ + private $invoker; + + /** + * The resolver needs a container. This container will be passed to the factory as a parameter + * so that the factory can access other entries of the container. + * + * @param ContainerInterface $container + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * Resolve a factory definition to a value. + * + * This will call the callable of the definition. + * + * @param FactoryDefinition $definition + * + * {@inheritdoc} + */ + public function resolve(Definition $definition, array $parameters = []) + { + $callable = $definition->getCallable(); + + if (! is_callable($callable)) { + throw new DefinitionException(sprintf( + 'The factory definition "%s" is not callable', + $definition->getName() + )); + } + + if (! $this->invoker) { + $this->invoker = new Invoker(new NumericArrayResolver, $this->container); + } + + return $this->invoker->call($callable, [$this->container]); + } + + /** + * {@inheritdoc} + */ + public function isResolvable(Definition $definition, array $parameters = []) + { + return true; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/InstanceInjector.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/InstanceInjector.php new file mode 100644 index 0000000000..eb825bae8b --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/InstanceInjector.php @@ -0,0 +1,53 @@ + + */ +class InstanceInjector extends ObjectCreator +{ + /** + * Injects dependencies on an existing instance. + * + * @param InstanceDefinition $definition + * + * {@inheritdoc} + */ + public function resolve(Definition $definition, array $parameters = []) + { + try { + $this->injectMethodsAndProperties($definition->getInstance(), $definition->getObjectDefinition()); + } catch (NotFoundException $e) { + $message = sprintf( + "Error while injecting dependencies into %s: %s", + get_class($definition->getInstance()), + $e->getMessage() + ); + throw new DependencyException($message, 0, $e); + } + } + + /** + * {@inheritdoc} + */ + public function isResolvable(Definition $definition, array $parameters = []) + { + return true; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ObjectCreator.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ObjectCreator.php new file mode 100644 index 0000000000..cc2169a818 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ObjectCreator.php @@ -0,0 +1,258 @@ + + */ +class ObjectCreator implements DefinitionResolver +{ + /** + * @var ProxyFactory + */ + private $proxyFactory; + + /** + * @var ParameterResolver + */ + private $parameterResolver; + + /** + * @var DefinitionResolver + */ + private $definitionResolver; + + /** + * @param DefinitionResolver $definitionResolver Used to resolve nested definitions. + * @param ProxyFactory $proxyFactory Used to create proxies for lazy injections. + */ + public function __construct( + DefinitionResolver $definitionResolver, + ProxyFactory $proxyFactory + ) { + $this->definitionResolver = $definitionResolver; + $this->proxyFactory = $proxyFactory; + $this->parameterResolver = new ParameterResolver($definitionResolver); + } + + /** + * Resolve a class definition to a value. + * + * This will create a new instance of the class using the injections points defined. + * + * @param ObjectDefinition $definition + * + * {@inheritdoc} + */ + public function resolve(Definition $definition, array $parameters = []) + { + // Lazy? + if ($definition->isLazy()) { + return $this->createProxy($definition, $parameters); + } + + return $this->createInstance($definition, $parameters); + } + + /** + * The definition is not resolvable if the class is not instantiable (interface or abstract) + * or if the class doesn't exist. + * + * @param ObjectDefinition $definition + * + * {@inheritdoc} + */ + public function isResolvable(Definition $definition, array $parameters = []) + { + return $definition->isInstantiable(); + } + + /** + * Returns a proxy instance + * + * @param ObjectDefinition $definition + * @param array $parameters + * + * @return LazyLoadingInterface Proxy instance + */ + private function createProxy(ObjectDefinition $definition, array $parameters) + { + /** @noinspection PhpUnusedParameterInspection */ + $proxy = $this->proxyFactory->createProxy( + $definition->getClassName(), + function (& $wrappedObject, $proxy, $method, $params, & $initializer) use ($definition, $parameters) { + $wrappedObject = $this->createInstance($definition, $parameters); + $initializer = null; // turning off further lazy initialization + return true; + } + ); + + return $proxy; + } + + /** + * Creates an instance of the class and injects dependencies.. + * + * @param ObjectDefinition $definition + * @param array $parameters Optional parameters to use to create the instance. + * + * @throws DefinitionException + * @throws DependencyException + * @return object + */ + private function createInstance(ObjectDefinition $definition, array $parameters) + { + $this->assertClassExists($definition); + + $classname = $definition->getClassName(); + $classReflection = new ReflectionClass($classname); + + $this->assertClassIsInstantiable($definition); + + $constructorInjection = $definition->getConstructorInjection(); + + try { + $args = $this->parameterResolver->resolveParameters( + $constructorInjection, + $classReflection->getConstructor(), + $parameters + ); + + if (count($args) > 0) { + $object = $classReflection->newInstanceArgs($args); + } else { + $object = new $classname; + } + + $this->injectMethodsAndProperties($object, $definition); + } catch (NotFoundException $e) { + throw new DependencyException(sprintf( + "Error while injecting dependencies into %s: %s", + $classReflection->getName(), + $e->getMessage() + ), 0, $e); + } catch (DefinitionException $e) { + throw DefinitionException::create($definition, sprintf( + "Entry %s cannot be resolved: %s", + $definition->getName(), + $e->getMessage() + )); + } + + if (! $object) { + throw new DependencyException(sprintf( + "Entry %s cannot be resolved: %s could not be constructed", + $definition->getName(), + $classReflection->getName() + )); + } + + return $object; + } + + protected function injectMethodsAndProperties($object, ObjectDefinition $objectDefinition) + { + // Property injections + foreach ($objectDefinition->getPropertyInjections() as $propertyInjection) { + $this->injectProperty($object, $propertyInjection); + } + + // Method injections + foreach ($objectDefinition->getMethodInjections() as $methodInjection) { + $methodReflection = new \ReflectionMethod($object, $methodInjection->getMethodName()); + $args = $this->parameterResolver->resolveParameters($methodInjection, $methodReflection); + + $methodReflection->invokeArgs($object, $args); + } + } + + /** + * Inject dependencies into properties. + * + * @param object $object Object to inject dependencies into + * @param PropertyInjection $propertyInjection Property injection definition + * + * @throws DependencyException + * @throws DefinitionException + */ + private function injectProperty($object, PropertyInjection $propertyInjection) + { + $propertyName = $propertyInjection->getPropertyName(); + + $className = $propertyInjection->getClassName(); + $className = $className ?: get_class($object); + $property = new ReflectionProperty($className, $propertyName); + + $value = $propertyInjection->getValue(); + + if ($value instanceof DefinitionHelper) { + /** @var Definition $nestedDefinition */ + $nestedDefinition = $value->getDefinition(''); + + try { + $value = $this->definitionResolver->resolve($nestedDefinition); + } catch (DependencyException $e) { + throw $e; + } catch (Exception $e) { + throw new DependencyException(sprintf( + "Error while injecting in %s::%s. %s", + get_class($object), + $propertyName, + $e->getMessage() + ), 0, $e); + } + } + + if (! $property->isPublic()) { + $property->setAccessible(true); + } + $property->setValue($object, $value); + } + + private function assertClassExists(ObjectDefinition $definition) + { + if (! $definition->classExists()) { + throw DefinitionException::create($definition, + sprintf( + "Entry %s cannot be resolved: class %s doesn't exist", + $definition->getName(), + $definition->getClassName() + )); + } + } + + private function assertClassIsInstantiable(ObjectDefinition $definition) + { + if (! $definition->isInstantiable()) { + throw DefinitionException::create($definition, + sprintf( + "Entry %s cannot be resolved: class %s is not instantiable", + $definition->getName(), + $definition->getClassName() + )); + } + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ParameterResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ParameterResolver.php new file mode 100644 index 0000000000..63b0ba975a --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ParameterResolver.php @@ -0,0 +1,140 @@ + + */ +class ParameterResolver +{ + /** + * @var DefinitionResolver + */ + private $definitionResolver; + + /** + * @param DefinitionResolver $definitionResolver Will be used to resolve nested definitions. + */ + public function __construct(DefinitionResolver $definitionResolver) + { + $this->definitionResolver = $definitionResolver; + } + + /** + * @param MethodInjection $definition + * @param \ReflectionFunctionAbstract $functionReflection + * @param array $parameters + * + * @throws DefinitionException A parameter has no value defined or guessable. + * @return array Parameters to use to call the function. + */ + public function resolveParameters( + MethodInjection $definition = null, + \ReflectionFunctionAbstract $functionReflection = null, + array $parameters = [] + ) { + $args = []; + + if (! $functionReflection) { + return $args; + } + + $definitionParameters = $definition ? $definition->getParameters() : array(); + + foreach ($functionReflection->getParameters() as $index => $parameter) { + if (array_key_exists($parameter->getName(), $parameters)) { + // Look in the $parameters array + $value = &$parameters[$parameter->getName()]; + } elseif (array_key_exists($index, $definitionParameters)) { + // Look in the definition + $value = &$definitionParameters[$index]; + } else { + // If the parameter is optional and wasn't specified, we take its default value + if ($parameter->isOptional()) { + $args[] = $this->getParameterDefaultValue($parameter, $functionReflection); + continue; + } + + throw new DefinitionException(sprintf( + "The parameter '%s' of %s has no value defined or guessable", + $parameter->getName(), + $this->getFunctionName($functionReflection) + )); + } + + if ($value instanceof DefinitionHelper) { + $nestedDefinition = $value->getDefinition(''); + + // If the container cannot produce the entry, we can use the default parameter value + if ($parameter->isOptional() && !$this->definitionResolver->isResolvable($nestedDefinition)) { + $value = $this->getParameterDefaultValue($parameter, $functionReflection); + } else { + $value = $this->definitionResolver->resolve($nestedDefinition); + } + } + + $args[] = &$value; + } + + return $args; + } + + /** + * Returns the default value of a function parameter. + * + * @param \ReflectionParameter $parameter + * @param \ReflectionFunctionAbstract $function + * + * @throws DefinitionException Can't get default values from PHP internal classes and functions + * @return mixed + */ + private function getParameterDefaultValue( + \ReflectionParameter $parameter, + \ReflectionFunctionAbstract $function + ) { + try { + return $parameter->getDefaultValue(); + } catch (\ReflectionException $e) { + throw new DefinitionException(sprintf( + "The parameter '%s' of %s has no type defined or guessable. It has a default value, " + . "but the default value can't be read through Reflection because it is a PHP internal class.", + $parameter->getName(), + $this->getFunctionName($function) + )); + } + } + + private function getFunctionName(\ReflectionFunctionAbstract $reflectionFunction) + { + if ($reflectionFunction instanceof \ReflectionMethod) { + return sprintf( + '%s::%s', + $reflectionFunction->getDeclaringClass()->getName(), + $reflectionFunction->getName() + ); + } elseif ($reflectionFunction->isClosure()) { + return sprintf( + 'closure defined in %s at line %d', + $reflectionFunction->getFileName(), + $reflectionFunction->getStartLine() + ); + } + + return $reflectionFunction->getName(); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ResolverDispatcher.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ResolverDispatcher.php new file mode 100644 index 0000000000..d93d57c74a --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ResolverDispatcher.php @@ -0,0 +1,138 @@ + + */ +class ResolverDispatcher implements DefinitionResolver +{ + /** + * @var ContainerInterface + */ + private $container; + + /** + * @var ProxyFactory + */ + private $proxyFactory; + + private $valueResolver; + private $arrayResolver; + private $factoryResolver; + private $decoratorResolver; + private $aliasResolver; + private $objectResolver; + private $instanceResolver; + private $envVariableResolver; + private $stringResolver; + + public function __construct(ContainerInterface $container, ProxyFactory $proxyFactory) + { + $this->container = $container; + $this->proxyFactory = $proxyFactory; + } + + /** + * Resolve a definition to a value. + * + * @param Definition $definition Object that defines how the value should be obtained. + * @param array $parameters Optional parameters to use to build the entry. + * + * @throws DefinitionException If the definition cannot be resolved. + * + * @return mixed Value obtained from the definition. + */ + public function resolve(Definition $definition, array $parameters = []) + { + $definitionResolver = $this->getDefinitionResolver($definition); + + return $definitionResolver->resolve($definition, $parameters); + } + + /** + * Check if a definition can be resolved. + * + * @param Definition $definition Object that defines how the value should be obtained. + * @param array $parameters Optional parameters to use to build the entry. + * + * @return bool + */ + public function isResolvable(Definition $definition, array $parameters = []) + { + $definitionResolver = $this->getDefinitionResolver($definition); + + return $definitionResolver->isResolvable($definition, $parameters); + } + + /** + * Returns a resolver capable of handling the given definition. + * + * @param Definition $definition + * + * @throws \RuntimeException No definition resolver was found for this type of definition. + * @return DefinitionResolver + */ + private function getDefinitionResolver(Definition $definition) + { + switch (true) { + case ($definition instanceof \DI\Definition\ObjectDefinition): + if (! $this->objectResolver) { + $this->objectResolver = new ObjectCreator($this, $this->proxyFactory); + } + return $this->objectResolver; + case ($definition instanceof \DI\Definition\ValueDefinition): + if (! $this->valueResolver) { + $this->valueResolver = new ValueResolver(); + } + return $this->valueResolver; + case ($definition instanceof \DI\Definition\AliasDefinition): + if (! $this->aliasResolver) { + $this->aliasResolver = new AliasResolver($this->container); + } + return $this->aliasResolver; + case ($definition instanceof \DI\Definition\DecoratorDefinition): + if (! $this->decoratorResolver) { + $this->decoratorResolver = new DecoratorResolver($this->container, $this); + } + return $this->decoratorResolver; + case ($definition instanceof \DI\Definition\FactoryDefinition): + if (! $this->factoryResolver) { + $this->factoryResolver = new FactoryResolver($this->container); + } + return $this->factoryResolver; + case ($definition instanceof \DI\Definition\ArrayDefinition): + if (! $this->arrayResolver) { + $this->arrayResolver = new ArrayResolver($this); + } + return $this->arrayResolver; + case ($definition instanceof \DI\Definition\EnvironmentVariableDefinition): + if (! $this->envVariableResolver) { + $this->envVariableResolver = new EnvironmentVariableResolver($this); + } + return $this->envVariableResolver; + case ($definition instanceof \DI\Definition\StringDefinition): + if (! $this->stringResolver) { + $this->stringResolver = new StringResolver($this->container); + } + return $this->stringResolver; + case ($definition instanceof \DI\Definition\InstanceDefinition): + if (! $this->instanceResolver) { + $this->instanceResolver = new InstanceInjector($this, $this->proxyFactory); + } + return $this->instanceResolver; + default: + throw new \RuntimeException('No definition resolver was configured for definition of type ' . get_class($definition)); + } + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/StringResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/StringResolver.php new file mode 100644 index 0000000000..96854d40cf --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/StringResolver.php @@ -0,0 +1,81 @@ + + */ +class StringResolver implements DefinitionResolver +{ + /** + * @var ContainerInterface + */ + private $container; + + /** + * The resolver needs a container. + * This container will be used to get the entry to which the alias points to. + * + * @param ContainerInterface $container + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * Resolve a value definition to a value. + * + * A value definition is simple, so this will just return the value of the ValueDefinition. + * + * @param StringDefinition $definition + * + * {@inheritdoc} + */ + public function resolve(Definition $definition, array $parameters = []) + { + $expression = $definition->getExpression(); + + $result = preg_replace_callback('#\{([^\{\}]+)\}#', function (array $matches) use ($definition) { + try { + return $this->container->get($matches[1]); + } catch (NotFoundException $e) { + throw new DependencyException(sprintf( + "Error while parsing string expression for entry '%s': %s", + $definition->getName(), + $e->getMessage() + ), 0, $e); + } + }, $expression); + + if ($result === null) { + throw new \RuntimeException(sprintf('An unknown error occurred while parsing the string definition: \'%s\'', $expression)); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function isResolvable(Definition $definition, array $parameters = []) + { + return true; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ValueResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ValueResolver.php new file mode 100644 index 0000000000..04d98fcf49 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Resolver/ValueResolver.php @@ -0,0 +1,44 @@ + + */ +class ValueResolver implements DefinitionResolver +{ + /** + * Resolve a value definition to a value. + * + * A value definition is simple, so this will just return the value of the ValueDefinition. + * + * @param ValueDefinition $definition + * + * {@inheritdoc} + */ + public function resolve(Definition $definition, array $parameters = []) + { + return $definition->getValue(); + } + + /** + * {@inheritdoc} + */ + public function isResolvable(Definition $definition, array $parameters = []) + { + return true; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/AnnotationReader.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/AnnotationReader.php new file mode 100644 index 0000000000..e12ab86e05 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/AnnotationReader.php @@ -0,0 +1,280 @@ + + */ +class AnnotationReader implements DefinitionSource +{ + /** + * @var Reader + */ + private $annotationReader; + + /** + * @var PhpDocReader + */ + private $phpDocReader; + + /** + * @var bool + */ + private $ignorePhpDocErrors; + + public function __construct($ignorePhpDocErrors = false) + { + $this->ignorePhpDocErrors = (bool) $ignorePhpDocErrors; + } + + /** + * {@inheritdoc} + * @throws AnnotationException + * @throws InvalidArgumentException The class doesn't exist + */ + public function getDefinition($name) + { + if (!class_exists($name) && !interface_exists($name)) { + return null; + } + + $class = new ReflectionClass($name); + $definition = new ObjectDefinition($name); + + $this->readInjectableAnnotation($class, $definition); + + // Browse the class properties looking for annotated properties + $this->readProperties($class, $definition); + + // Browse the object's methods looking for annotated methods + $this->readMethods($class, $definition); + + return $definition; + } + + /** + * Browse the class properties looking for annotated properties. + */ + private function readProperties(ReflectionClass $class, ObjectDefinition $definition) + { + foreach ($class->getProperties() as $property) { + if ($property->isStatic()) { + continue; + } + $this->readProperty($property, $definition); + } + + // Read also the *private* properties of the parent classes + /** @noinspection PhpAssignmentInConditionInspection */ + while ($class = $class->getParentClass()) { + foreach ($class->getProperties(ReflectionProperty::IS_PRIVATE) as $property) { + if ($property->isStatic()) { + continue; + } + $this->readProperty($property, $definition, $class->getName()); + } + } + } + + private function readProperty(ReflectionProperty $property, ObjectDefinition $definition, $classname = null) + { + // Look for @Inject annotation + /** @var $annotation Inject */ + $annotation = $this->getAnnotationReader()->getPropertyAnnotation($property, 'DI\Annotation\Inject'); + if ($annotation === null) { + return null; + } + + // @Inject("name") or look for @var content + $entryName = $annotation->getName() ?: $this->getPhpDocReader()->getPropertyClass($property); + + if ($entryName === null) { + throw new AnnotationException(sprintf( + '@Inject found on property %s::%s but unable to guess what to inject, use a @var annotation', + $property->getDeclaringClass()->getName(), + $property->getName() + )); + } + + $definition->addPropertyInjection( + new PropertyInjection($property->getName(), new EntryReference($entryName), $classname) + ); + } + + /** + * Browse the object's methods looking for annotated methods. + */ + private function readMethods(ReflectionClass $class, ObjectDefinition $objectDefinition) + { + // This will look in all the methods, including those of the parent classes + foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + if ($method->isStatic()) { + continue; + } + + $methodInjection = $this->getMethodInjection($method); + + if (! $methodInjection) { + continue; + } + + if ($method->isConstructor()) { + $objectDefinition->setConstructorInjection($methodInjection); + } else { + $objectDefinition->addMethodInjection($methodInjection); + } + } + } + + private function getMethodInjection(ReflectionMethod $method) + { + // Look for @Inject annotation + /** @var $annotation Inject|null */ + try { + $annotation = $this->getAnnotationReader()->getMethodAnnotation($method, 'DI\Annotation\Inject'); + } catch (AnnotationException $e) { + throw new AnnotationException(sprintf( + '@Inject annotation on %s::%s is malformed. %s', + $method->getDeclaringClass()->getName(), + $method->getName(), + $e->getMessage() + ), 0, $e); + } + $annotationParameters = $annotation ? $annotation->getParameters() : []; + + // @Inject on constructor is implicit + if (! ($annotation || $method->isConstructor())) { + return null; + } + + $parameters = []; + foreach ($method->getParameters() as $index => $parameter) { + $entryName = $this->getMethodParameter($index, $parameter, $annotationParameters); + + if ($entryName !== null) { + $parameters[$index] = new EntryReference($entryName); + } + } + + if ($method->isConstructor()) { + return MethodInjection::constructor($parameters); + } else { + return new MethodInjection($method->getName(), $parameters); + } + } + + /** + * @param int $parameterIndex + * @param ReflectionParameter $parameter + * @param array $annotationParameters + * + * @return string|null Entry name or null if not found. + */ + private function getMethodParameter($parameterIndex, ReflectionParameter $parameter, array $annotationParameters) + { + // @Inject has definition for this parameter (by index, or by name) + if (isset($annotationParameters[$parameterIndex])) { + return $annotationParameters[$parameterIndex]; + } + if (isset($annotationParameters[$parameter->getName()])) { + return $annotationParameters[$parameter->getName()]; + } + + // Skip optional parameters if not explicitly defined + if ($parameter->isOptional()) { + return null; + } + + // Try to use the type-hinting + $parameterClass = $parameter->getClass(); + if ($parameterClass) { + return $parameterClass->getName(); + } + + // Last resort, look for @param tag + return $this->getPhpDocReader()->getParameterClass($parameter); + } + + /** + * @return Reader The annotation reader + */ + public function getAnnotationReader() + { + if ($this->annotationReader === null) { + AnnotationRegistry::registerAutoloadNamespace('DI\Annotation', __DIR__ . '/../../../'); + $this->annotationReader = new SimpleAnnotationReader(); + $this->annotationReader->addNamespace('DI\Annotation'); + } + + return $this->annotationReader; + } + + /** + * @return PhpDocReader + */ + private function getPhpDocReader() + { + if ($this->phpDocReader === null) { + $this->phpDocReader = new PhpDocReader($this->ignorePhpDocErrors); + } + + return $this->phpDocReader; + } + + private function readInjectableAnnotation(ReflectionClass $class, ObjectDefinition $definition) + { + try { + /** @var $annotation Injectable|null */ + $annotation = $this->getAnnotationReader() + ->getClassAnnotation($class, 'DI\Annotation\Injectable'); + } catch (UnexpectedValueException $e) { + throw new DefinitionException(sprintf( + 'Error while reading @Injectable on %s: %s', + $class->getName(), + $e->getMessage() + ), 0, $e); + } + + if (! $annotation) { + return; + } + + if ($annotation->getScope()) { + $definition->setScope($annotation->getScope()); + } + if ($annotation->isLazy() !== null) { + $definition->setLazy($annotation->isLazy()); + } + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/Autowiring.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/Autowiring.php new file mode 100644 index 0000000000..730799bc8b --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/Autowiring.php @@ -0,0 +1,68 @@ + + */ +class Autowiring implements DefinitionSource +{ + /** + * {@inheritdoc} + */ + public function getDefinition($name) + { + if (!class_exists($name) && !interface_exists($name)) { + return null; + } + + $definition = new ObjectDefinition($name); + + // Constructor + $class = new \ReflectionClass($name); + $constructor = $class->getConstructor(); + if ($constructor && $constructor->isPublic()) { + $definition->setConstructorInjection( + MethodInjection::constructor($this->getParametersDefinition($constructor)) + ); + } + + return $definition; + } + + /** + * Read the type-hinting from the parameters of the function. + */ + private function getParametersDefinition(\ReflectionFunctionAbstract $constructor) + { + $parameters = []; + + foreach ($constructor->getParameters() as $index => $parameter) { + // Skip optional parameters + if ($parameter->isOptional()) { + continue; + } + + $parameterClass = $parameter->getClass(); + + if ($parameterClass) { + $parameters[$index] = new EntryReference($parameterClass->getName()); + } + } + + return $parameters; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/CachedDefinitionSource.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/CachedDefinitionSource.php new file mode 100644 index 0000000000..19bc7c0e62 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/CachedDefinitionSource.php @@ -0,0 +1,104 @@ + + */ +class CachedDefinitionSource implements DefinitionSource +{ + /** + * Prefix for cache key, to avoid conflicts with other systems using the same cache + * @var string + */ + const CACHE_PREFIX = 'DI\\Definition\\'; + + /** + * @var DefinitionSource + */ + private $source; + + /** + * @var Cache + */ + private $cache; + + public function __construct(DefinitionSource $source, Cache $cache) + { + $this->source = $source; + $this->cache = $cache; + } + + /** + * {@inheritdoc} + */ + public function getDefinition($name) + { + // Look in cache + $definition = $this->fetchFromCache($name); + + if ($definition === false) { + $definition = $this->source->getDefinition($name); + + // Save to cache + if ($definition === null || ($definition instanceof CacheableDefinition)) { + $this->saveToCache($name, $definition); + } + } + + return $definition; + } + + /** + * @return Cache + */ + public function getCache() + { + return $this->cache; + } + + /** + * Fetches a definition from the cache + * + * @param string $name Entry name + * @return Definition|null|boolean The cached definition, null or false if the value is not already cached + */ + private function fetchFromCache($name) + { + $cacheKey = self::CACHE_PREFIX . $name; + + $data = $this->cache->fetch($cacheKey); + + if ($data !== false) { + return $data; + } + + return false; + } + + /** + * Saves a definition to the cache + * + * @param string $name Entry name + * @param Definition|null $definition + */ + private function saveToCache($name, Definition $definition = null) + { + $cacheKey = self::CACHE_PREFIX . $name; + + $this->cache->save($cacheKey, $definition); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionArray.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionArray.php new file mode 100644 index 0000000000..21b6eeb9ce --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionArray.php @@ -0,0 +1,142 @@ + + */ +class DefinitionArray implements DefinitionSource, MutableDefinitionSource +{ + const WILDCARD = '*'; + /** + * Matches anything except "\" + */ + const WILDCARD_PATTERN = '([^\\\\]+)'; + + /** + * DI definitions in a PHP array + * @var array + */ + private $definitions = []; + + /** + * @param array $definitions + */ + public function __construct(array $definitions = []) + { + $this->definitions = $definitions; + } + + /** + * @param array $definitions DI definitions in a PHP array indexed by the definition name. + */ + public function addDefinitions(array $definitions) + { + // The newly added data prevails + // "for keys that exist in both arrays, the elements from the left-hand array will be used" + $this->definitions = $definitions + $this->definitions; + } + + /** + * {@inheritdoc} + */ + public function addDefinition(Definition $definition) + { + $this->definitions[$definition->getName()] = $definition; + } + + /** + * {@inheritdoc} + */ + public function getDefinition($name) + { + // Look for the definition by name + if (array_key_exists($name, $this->definitions)) { + return $this->castDefinition($this->definitions[$name], $name); + } + + // Look if there are wildcards definitions + foreach ($this->definitions as $key => $definition) { + if (strpos($key, self::WILDCARD) === false) { + continue; + } + + // Turn the pattern into a regex + $key = addslashes($key); + $key = '#' . str_replace(self::WILDCARD, self::WILDCARD_PATTERN, $key) . '#'; + if (preg_match($key, $name, $matches) === 1) { + $definition = $this->castDefinition($definition, $name); + + // For a class definition, we replace * in the class name with the matches + // *Interface -> *Impl => FooInterface -> FooImpl + if ($definition instanceof ObjectDefinition) { + array_shift($matches); + $definition->setClassName( + $this->replaceWildcards($definition->getClassName(), $matches) + ); + } + + return $definition; + } + } + + return null; + } + + /** + * @param mixed $definition + * @param string $name + * @return Definition + */ + private function castDefinition($definition, $name) + { + if ($definition instanceof DefinitionHelper) { + $definition = $definition->getDefinition($name); + } + if (! $definition instanceof Definition && is_array($definition)) { + $definition = new ArrayDefinition($name, $definition); + } + if ($definition instanceof \Closure) { + $definition = new FactoryDefinition($name, $definition); + } + if (! $definition instanceof Definition) { + $definition = new ValueDefinition($name, $definition); + } + + return $definition; + } + + /** + * Replaces all the wildcards in the string with the given replacements. + * @param string $string + * @param string[] $replacements + * @return string + */ + private function replaceWildcards($string, array $replacements) + { + foreach ($replacements as $replacement) { + $pos = strpos($string, self::WILDCARD); + if ($pos !== false) { + $string = substr_replace($string, $replacement, $pos, 1); + } + } + + return $string; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionFile.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionFile.php new file mode 100644 index 0000000000..a79ccf8a24 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionFile.php @@ -0,0 +1,74 @@ + + */ +class DefinitionFile extends DefinitionArray +{ + /** + * @var bool + */ + private $initialized = false; + + /** + * File containing definitions, or null if the definitions are given as a PHP array. + * @var string|null + */ + private $file; + + /** + * @param string $file File in which the definitions are returned as an array. + */ + public function __construct($file) + { + // Lazy-loading to improve performances + $this->file = $file; + + parent::__construct([]); + } + + /** + * {@inheritdoc} + */ + public function getDefinition($name) + { + $this->initialize(); + + return parent::getDefinition($name); + } + + /** + * Lazy-loading of the definitions. + * @throws DefinitionException + */ + private function initialize() + { + if ($this->initialized === true) { + return; + } + + $definitions = require $this->file; + + if (! is_array($definitions)) { + throw new DefinitionException("File {$this->file} should return an array of definitions"); + } + + $this->addDefinitions($definitions); + + $this->initialized = true; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionSource.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionSource.php new file mode 100644 index 0000000000..7aa5836c41 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/DefinitionSource.php @@ -0,0 +1,31 @@ + + */ +interface DefinitionSource +{ + /** + * Returns the DI definition for the entry name. + * + * @param string $name + * + * @throws DefinitionException An invalid definition was found. + * @return Definition|null + */ + public function getDefinition($name); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/MutableDefinitionSource.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/MutableDefinitionSource.php new file mode 100644 index 0000000000..287cc32304 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/MutableDefinitionSource.php @@ -0,0 +1,15 @@ + + */ +interface MutableDefinitionSource extends DefinitionSource +{ + public function addDefinition(Definition $definition); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/SourceChain.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/SourceChain.php new file mode 100644 index 0000000000..962d6f4e65 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/Source/SourceChain.php @@ -0,0 +1,108 @@ + + */ +class SourceChain implements DefinitionSource, MutableDefinitionSource +{ + /** + * @var DefinitionSource[] + */ + private $sources; + + /** + * @var DefinitionSource + */ + private $rootSource; + + /** + * @var MutableDefinitionSource|null + */ + private $mutableSource; + + /** + * @param DefinitionSource[] $sources + */ + public function __construct(array $sources) + { + // We want a numerically indexed array to ease the traversal later + $this->sources = array_values($sources); + $this->rootSource = $this; + } + + /** + * {@inheritdoc} + * @param int $startIndex Use this parameter to start looking from a specific + * point in the source chain. + */ + public function getDefinition($name, $startIndex = 0) + { + $count = count($this->sources); + for ($i = $startIndex; $i < $count; $i++) { + $source = $this->sources[$i]; + + $definition = $source->getDefinition($name); + + if ($definition) { + if ($definition instanceof HasSubDefinition) { + $this->resolveSubDefinition($definition, $i); + } + return $definition; + } + } + + return null; + } + + public function addDefinition(Definition $definition) + { + if (! $this->mutableSource) { + throw new \LogicException("The container's definition source has not been initialized correctly"); + } + + $this->mutableSource->addDefinition($definition); + } + + public function setRootDefinitionSource(DefinitionSource $rootSource) + { + $this->rootSource = $rootSource; + } + + private function resolveSubDefinition(HasSubDefinition $definition, $currentIndex) + { + $subDefinitionName = $definition->getSubDefinitionName(); + + if ($subDefinitionName === $definition->getName()) { + // Extending itself: look in the next sources only (else infinite recursion) + $subDefinition = $this->getDefinition($subDefinitionName, $currentIndex + 1); + } else { + // Extending another definition: look from the root + $subDefinition = $this->rootSource->getDefinition($subDefinitionName); + } + + if ($subDefinition) { + $definition->setSubDefinition($subDefinition); + } + } + + public function setMutableDefinitionSource(MutableDefinitionSource $mutableSource) + { + $this->mutableSource = $mutableSource; + + array_unshift($this->sources, $mutableSource); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/StringDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/StringDefinition.php new file mode 100644 index 0000000000..27d2811fbd --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/StringDefinition.php @@ -0,0 +1,66 @@ + + */ +class StringDefinition implements Definition +{ + /** + * Entry name + * @var string + */ + private $name; + + /** + * @var string + */ + private $expression; + + /** + * @param string $name Entry name + * @param string $expression + */ + public function __construct($name, $expression) + { + $this->name = $name; + $this->expression = $expression; + } + + /** + * @return string Entry name + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getScope() + { + return Scope::SINGLETON; + } + + /** + * @return string + */ + public function getExpression() + { + return $this->expression; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ValueDefinition.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ValueDefinition.php new file mode 100644 index 0000000000..b0123f916e --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Definition/ValueDefinition.php @@ -0,0 +1,67 @@ + + */ +class ValueDefinition implements Definition +{ + /** + * Entry name + * @var string + */ + private $name; + + /** + * @var mixed + */ + private $value; + + /** + * @param string $name Entry name + * @param mixed $value + */ + public function __construct($name, $value) + { + $this->name = $name; + $this->value = $value; + } + + /** + * @return string Entry name + */ + public function getName() + { + return $this->name; + } + + /** + * A value definition is like a constant, there is nothing to compute, the value is the same for everyone. + * + * {@inheritdoc} + */ + public function getScope() + { + return Scope::SINGLETON; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/DependencyException.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/DependencyException.php new file mode 100644 index 0000000000..f410f38a09 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/DependencyException.php @@ -0,0 +1,19 @@ + + */ +interface FactoryInterface +{ + /** + * Resolves an entry by its name. If given a class name, it will return a new instance of that class. + * + * @param string $name Entry name or a class name. + * @param array $parameters Optional parameters to use to build the entry. Use this to force specific + * parameters to specific values. Parameters not defined in this array will + * be automatically resolved. + * + * @throws \InvalidArgumentException The name parameter must be of type string. + * @throws DependencyException Error while resolving the entry. + * @throws NotFoundException No entry or class found for the given name. + * @return mixed + */ + public function make($name, array $parameters = []); +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Invoker/DefinitionParameterResolver.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Invoker/DefinitionParameterResolver.php new file mode 100644 index 0000000000..f9718f2164 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Invoker/DefinitionParameterResolver.php @@ -0,0 +1,66 @@ + + */ +class DefinitionParameterResolver implements ParameterResolver +{ + /** + * @var DefinitionResolver + */ + private $definitionResolver; + + public function __construct(DefinitionResolver $definitionResolver) + { + $this->definitionResolver = $definitionResolver; + } + + /** + * {@inheritdoc} + */ + public function getParameters( + ReflectionFunctionAbstract $reflection, + array $providedParameters, + array $resolvedParameters + ) { + // Skip parameters already resolved + if (! empty($resolvedParameters)) { + $providedParameters = array_diff_key($providedParameters, $resolvedParameters); + } + + foreach ($providedParameters as $key => $value) { + if (! $value instanceof DefinitionHelper) { + continue; + } + + $definition = $value->getDefinition(''); + $value = $this->definitionResolver->resolve($definition); + + if (is_int($key)) { + // Indexed by position + $resolvedParameters[$key] = $value; + } else { + // Indexed by parameter name + // TODO optimize? + $reflectionParameters = $reflection->getParameters(); + foreach ($reflectionParameters as $reflectionParameter) { + if ($key === $reflectionParameter->name) { + $resolvedParameters[$reflectionParameter->getPosition()] = $value; + } + } + } + } + + return $resolvedParameters; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/InvokerInterface.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/InvokerInterface.php new file mode 100644 index 0000000000..65d0746b82 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/InvokerInterface.php @@ -0,0 +1,19 @@ + + */ +interface InvokerInterface extends \Invoker\InvokerInterface +{ +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/NotFoundException.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/NotFoundException.php new file mode 100644 index 0000000000..0119578cd6 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/NotFoundException.php @@ -0,0 +1,19 @@ + + */ +class ProxyFactory +{ + /** + * If true, write the proxies to disk to improve performances. + * @var boolean + */ + private $writeProxiesToFile; + + /** + * Directory where to write the proxies (if $writeProxiesToFile is enabled). + * @var string + */ + private $proxyDirectory; + + /** + * @var LazyLoadingValueHolderFactory|null + */ + private $proxyManager; + + public function __construct($writeProxiesToFile, $proxyDirectory = null) + { + $this->writeProxiesToFile = $writeProxiesToFile; + $this->proxyDirectory = $proxyDirectory; + } + + /** + * Creates a new lazy proxy instance of the given class with + * the given initializer + * + * @param string $className name of the class to be proxied + * @param \Closure $initializer initializer to be passed to the proxy + * + * @return \ProxyManager\Proxy\LazyLoadingInterface + */ + public function createProxy($className, \Closure $initializer) + { + $this->createProxyManager(); + + return $this->proxyManager->createProxy($className, $initializer); + } + + private function createProxyManager() + { + if ($this->proxyManager !== null) { + return; + } + + if (! class_exists('ProxyManager\Configuration')) { + throw new \RuntimeException('The ocramius/proxy-manager library is not installed. Lazy injection requires that library to be installed with Composer in order to work. Run "composer require ocramius/proxy-manager:~0.3".'); + } + + $config = new Configuration(); + + if ($this->writeProxiesToFile) { + $config->setProxiesTargetDir($this->proxyDirectory); + spl_autoload_register($config->getProxyAutoloader()); + } else { + $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); + } + + $this->proxyManager = new LazyLoadingValueHolderFactory($config); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Reflection/CallableReflectionFactory.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Reflection/CallableReflectionFactory.php new file mode 100644 index 0000000000..f6062d3121 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Reflection/CallableReflectionFactory.php @@ -0,0 +1,51 @@ + + */ +class CallableReflectionFactory +{ + /** + * @param callable $callable + * + * @return \ReflectionFunctionAbstract + */ + public static function fromCallable($callable) + { + // Array callable + if (is_array($callable)) { + list($class, $method) = $callable; + + return new \ReflectionMethod($class, $method); + } + + // Closure + if ($callable instanceof \Closure) { + return new \ReflectionFunction($callable); + } + + // Callable object (i.e. implementing __invoke()) + if (is_object($callable) && method_exists($callable, '__invoke')) { + return new \ReflectionMethod($callable, '__invoke'); + } + + // Callable class (i.e. implementing __invoke()) + if (is_string($callable) && class_exists($callable) && method_exists($callable, '__invoke')) { + return new \ReflectionMethod($callable, '__invoke'); + } + + // Standard function + return new \ReflectionFunction($callable); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Scope.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Scope.php new file mode 100644 index 0000000000..cddd7d5c54 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/Scope.php @@ -0,0 +1,54 @@ + + */ +class Scope +{ + /** + * A singleton entry will be computed once and shared. + * + * For a class, only a single instance of the class will be created. + */ + const SINGLETON = 'singleton'; + + /** + * A prototype entry will be recomputed each time it is asked. + * + * For a class, this will create a new instance each time. + */ + const PROTOTYPE = 'prototype'; + + /** + * Method kept for backward compatibility, use the constant instead. + * + * @return string + */ + public static function SINGLETON() + { + return self::SINGLETON; + } + + /** + * Method kept for backward compatibility, use the constant instead. + * + * @return string + */ + public static function PROTOTYPE() + { + return self::PROTOTYPE; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/functions.php b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/functions.php new file mode 100644 index 0000000000..313c71f441 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/php-di/src/DI/functions.php @@ -0,0 +1,181 @@ + decorate(function ($foo, $container) { + * return new CachedFoo($foo, $container->get('cache')); + * }) + * + * @param callable $callable The callable takes the decorated object as first parameter and + * the container as second. + * + * @return FactoryDefinitionHelper + */ + function decorate($callable) + { + return new FactoryDefinitionHelper($callable, true); + } +} + +if (! function_exists('DI\get')) { + /** + * Helper for referencing another container entry in an object definition. + * + * @param string $entryName + * + * @return EntryReference + */ + function get($entryName) + { + return new EntryReference($entryName); + } +} + +if (! function_exists('DI\link')) { + /** + * Helper for referencing another container entry in an object definition. + * + * @deprecated \DI\link() has been replaced by \DI\get() + * + * @param string $entryName + * + * @return EntryReference + */ + function link($entryName) + { + return new EntryReference($entryName); + } +} + +if (! function_exists('DI\env')) { + /** + * Helper for referencing environment variables. + * + * @param string $variableName The name of the environment variable. + * @param mixed $defaultValue The default value to be used if the environment variable is not defined. + * + * @return EnvironmentVariableDefinitionHelper + */ + function env($variableName, $defaultValue = null) + { + // Only mark as optional if the default value was *explicitly* provided. + $isOptional = 2 === func_num_args(); + + return new EnvironmentVariableDefinitionHelper($variableName, $isOptional, $defaultValue); + } +} + +if (! function_exists('DI\add')) { + /** + * Helper for extending another definition. + * + * Example: + * + * 'log.backends' => DI\add(DI\get('My\Custom\LogBackend')) + * + * or: + * + * 'log.backends' => DI\add([ + * DI\get('My\Custom\LogBackend') + * ]) + * + * @param mixed|array $values A value or an array of values to add to the array. + * + * @return ArrayDefinitionExtensionHelper + * + * @since 5.0 + */ + function add($values) + { + if (! is_array($values)) { + $values = [$values]; + } + + return new ArrayDefinitionExtensionHelper($values); + } +} + +if (! function_exists('DI\string')) { + /** + * Helper for concatenating strings. + * + * Example: + * + * 'log.filename' => DI\string('{app.path}/app.log') + * + * @param string $expression A string expression. Use the `{}` placeholders to reference other container entries. + * + * @return StringDefinitionHelper + * + * @since 5.0 + */ + function string($expression) + { + return new StringDefinitionHelper((string) $expression); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/.gitattributes b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/.gitattributes new file mode 100644 index 0000000000..912292cf40 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/.gitattributes @@ -0,0 +1,7 @@ +# .gitattributes +tests/ export-ignore +phpunit.xml.dist export-ignore +.travis.yml export-ignore + +# Auto detect text files and perform LF normalization +* text=auto diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/.gitignore b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/.gitignore new file mode 100644 index 0000000000..f7be360d40 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +.idea/* +vendor/* +composer.phar +composer.lock diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/LICENSE b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/LICENSE new file mode 100644 index 0000000000..f859a317a2 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/LICENSE @@ -0,0 +1,16 @@ +Copyright (C) 2015 Matthieu Napoli + +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/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/README.md b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/README.md new file mode 100644 index 0000000000..9a59545479 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/README.md @@ -0,0 +1,59 @@ +# PhpDocReader + +[![Build Status](https://img.shields.io/travis/PHP-DI/PhpDocReader.svg)](https://travis-ci.org/mnapoli/PhpDocReader) +[![Coverage Status](https://img.shields.io/coveralls/PHP-DI/PhpDocReader.svg)](https://coveralls.io/r/mnapoli/PhpDocReader) +![](https://img.shields.io/packagist/dt/PHP-DI/phpdoc-reader.svg) + +This project is used by: + +- [PHP-DI](http://php-di.org/) +- [phockito-unit-php-di](https://github.com/balihoo/phockito-unit-php-di) + +Fork the README to add your project here. + +## Features + +PhpDocReader parses `@var` and `@param` values in PHP docblocks: + +```php + +use My\Cache\Backend; + +class Cache +{ + /** + * @var Backend + */ + protected $backend; + + /** + * @param Backend $backend + */ + public function __construct($backend) + { + } +} +``` + +It supports namespaced class names with the same resolution rules as PHP: + +- fully qualified name (starting with `\`) +- imported class name (eg. `use My\Cache\Backend;`) +- relative class name (from the current namespace, like `SubNamespace\MyClass`) +- aliased class name (eg. `use My\Cache\Backend as FooBar;`) + +Primitive types (`@var string`) are ignored (returns null), only valid class names are returned. + +## Usage + +```php +$reader = new PhpDocReader(); + +// Read a property type (@var phpdoc) +$property = new ReflectionProperty($className, $propertyName); +$propertyClass = $reader->getPropertyClass($property); + +// Read a parameter type (@param phpdoc) +$parameter = new ReflectionParameter(array($className, $methodName), $parameterName); +$parameterClass = $reader->getParameterClass($parameter); +``` diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/composer.json b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/composer.json new file mode 100644 index 0000000000..11da6a52e9 --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/composer.json @@ -0,0 +1,23 @@ +{ + "name": "php-di/phpdoc-reader", + "type": "library", + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": ["phpdoc", "reflection"], + "license": "MIT", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "autoload-dev": { + "psr-4": { + "UnitTest\\PhpDocReader\\": "tests/" + } + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.6" + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/AnnotationException.php b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/AnnotationException.php new file mode 100644 index 0000000000..577d73f28c --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/AnnotationException.php @@ -0,0 +1,10 @@ + + */ +class AnnotationException extends \Exception +{ +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpDocReader.php b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpDocReader.php new file mode 100644 index 0000000000..6251371b6f --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpDocReader.php @@ -0,0 +1,270 @@ + + */ +class PhpDocReader +{ + /** + * @var UseStatementParser + */ + private $parser; + + private $ignoredTypes = array( + 'bool', + 'boolean', + 'string', + 'int', + 'integer', + 'float', + 'double', + 'array', + 'object', + 'callable', + 'resource', + ); + + /** + * Enable or disable throwing errors when PhpDoc Errors occur (when parsing annotations) + * + * @var bool + */ + private $ignorePhpDocErrors; + + /** + * + * @param bool $ignorePhpDocErrors + */ + public function __construct($ignorePhpDocErrors = false) + { + $this->parser = new UseStatementParser(); + $this->ignorePhpDocErrors = $ignorePhpDocErrors; + } + + /** + * Parse the docblock of the property to get the class of the var annotation. + * + * @param ReflectionProperty $property + * + * @throws AnnotationException + * @return string|null Type of the property (content of var annotation) + * + * @deprecated Use getPropertyClass instead. + */ + public function getPropertyType(ReflectionProperty $property) + { + return $this->getPropertyClass($property); + } + + /** + * Parse the docblock of the property to get the class of the var annotation. + * + * @param ReflectionProperty $property + * + * @throws AnnotationException + * @return string|null Type of the property (content of var annotation) + */ + public function getPropertyClass(ReflectionProperty $property) + { + // Get the content of the @var annotation + if (preg_match('/@var\s+([^\s]+)/', $property->getDocComment(), $matches)) { + list(, $type) = $matches; + } else { + return null; + } + + // Ignore primitive types + if (in_array($type, $this->ignoredTypes)) { + return null; + } + + // Ignore types containing special characters ([], <> ...) + if (! preg_match('/^[a-zA-Z0-9\\\\_]+$/', $type)) { + return null; + } + + $class = $property->getDeclaringClass(); + + // If the class name is not fully qualified (i.e. doesn't start with a \) + if ($type[0] !== '\\') { + $alias = (false === $pos = strpos($type, '\\')) ? $type : substr($type, 0, $pos); + $loweredAlias = strtolower($alias); + + // Retrieve "use" statements + $uses = $this->parser->parseUseStatements($property->getDeclaringClass()); + + $found = false; + + if (isset($uses[$loweredAlias])) { + // Imported classes + if (false !== $pos) { + $type = $uses[$loweredAlias] . substr($type, $pos); + } else { + $type = $uses[$loweredAlias]; + } + $found = true; + } elseif ($this->classExists($class->getNamespaceName() . '\\' . $type)) { + $type = $class->getNamespaceName() . '\\' . $type; + $found = true; + } elseif (isset($uses['__NAMESPACE__']) && $this->classExists($uses['__NAMESPACE__'] . '\\' . $type)) { + // Class namespace + $type = $uses['__NAMESPACE__'] . '\\' . $type; + $found = true; + } elseif ($this->classExists($type)) { + // No namespace + $found = true; + } + + if (!$found && !$this->ignorePhpDocErrors) { + throw new AnnotationException(sprintf( + 'The @var annotation on %s::%s contains a non existent class "%s". ' + . 'Did you maybe forget to add a "use" statement for this annotation?', + $class->name, + $property->getName(), + $type + )); + } + } + + if (!$this->classExists($type) && !$this->ignorePhpDocErrors) { + throw new AnnotationException(sprintf( + 'The @var annotation on %s::%s contains a non existent class "%s"', + $class->name, + $property->getName(), + $type + )); + } + + // Remove the leading \ (FQN shouldn't contain it) + $type = ltrim($type, '\\'); + + return $type; + } + + /** + * Parse the docblock of the property to get the class of the param annotation. + * + * @param ReflectionParameter $parameter + * + * @throws AnnotationException + * @return string|null Type of the property (content of var annotation) + * + * @deprecated Use getParameterClass instead. + */ + public function getParameterType(ReflectionParameter $parameter) + { + return $this->getParameterClass($parameter); + } + + /** + * Parse the docblock of the property to get the class of the param annotation. + * + * @param ReflectionParameter $parameter + * + * @throws AnnotationException + * @return string|null Type of the property (content of var annotation) + */ + public function getParameterClass(ReflectionParameter $parameter) + { + // Use reflection + $parameterClass = $parameter->getClass(); + if ($parameterClass !== null) { + return $parameterClass->name; + } + + $parameterName = $parameter->name; + // Get the content of the @param annotation + $method = $parameter->getDeclaringFunction(); + if (preg_match('/@param\s+([^\s]+)\s+\$' . $parameterName . '/', $method->getDocComment(), $matches)) { + list(, $type) = $matches; + } else { + return null; + } + + // Ignore primitive types + if (in_array($type, $this->ignoredTypes)) { + return null; + } + + // Ignore types containing special characters ([], <> ...) + if (! preg_match('/^[a-zA-Z0-9\\\\_]+$/', $type)) { + return null; + } + + $class = $parameter->getDeclaringClass(); + + // If the class name is not fully qualified (i.e. doesn't start with a \) + if ($type[0] !== '\\') { + $alias = (false === $pos = strpos($type, '\\')) ? $type : substr($type, 0, $pos); + $loweredAlias = strtolower($alias); + + // Retrieve "use" statements + $uses = $this->parser->parseUseStatements($class); + + $found = false; + + if (isset($uses[$loweredAlias])) { + // Imported classes + if (false !== $pos) { + $type = $uses[$loweredAlias] . substr($type, $pos); + } else { + $type = $uses[$loweredAlias]; + } + $found = true; + } elseif ($this->classExists($class->getNamespaceName() . '\\' . $type)) { + $type = $class->getNamespaceName() . '\\' . $type; + $found = true; + } elseif (isset($uses['__NAMESPACE__']) && $this->classExists($uses['__NAMESPACE__'] . '\\' . $type)) { + // Class namespace + $type = $uses['__NAMESPACE__'] . '\\' . $type; + $found = true; + } elseif ($this->classExists($type)) { + // No namespace + $found = true; + } + + if (!$found && !$this->ignorePhpDocErrors) { + throw new AnnotationException(sprintf( + 'The @param annotation for parameter "%s" of %s::%s contains a non existent class "%s". ' + . 'Did you maybe forget to add a "use" statement for this annotation?', + $parameterName, + $class->name, + $method->name, + $type + )); + } + } + + if (!$this->classExists($type) && !$this->ignorePhpDocErrors) { + throw new AnnotationException(sprintf( + 'The @param annotation for parameter "%s" of %s::%s contains a non existent class "%s"', + $parameterName, + $class->name, + $method->name, + $type + )); + } + + // Remove the leading \ (FQN shouldn't contain it) + $type = ltrim($type, '\\'); + + return $type; + } + + /** + * @param string $class + * @return bool + */ + private function classExists($class) + { + return class_exists($class) || interface_exists($class); + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpParser/TokenParser.php b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpParser/TokenParser.php new file mode 100644 index 0000000000..d3965d753a --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpParser/TokenParser.php @@ -0,0 +1,159 @@ + + * @author Christian Kaps + */ +class TokenParser +{ + /** + * The token list. + * + * @var array + */ + private $tokens; + + /** + * The number of tokens. + * + * @var int + */ + private $numTokens; + + /** + * The current array pointer. + * + * @var int + */ + private $pointer = 0; + + /** + * @param string $contents + */ + public function __construct($contents) + { + $this->tokens = token_get_all($contents); + + // The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it + // saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored + // doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a + // docblock. If the first thing in the file is a class without a doc block this would cause calls to + // getDocBlock() on said class to return our long lost doc_comment. Argh. + // To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least + // it's harmless to us. + token_get_all("numTokens = count($this->tokens); + } + + /** + * Gets all use statements. + * + * @param string $namespaceName The namespace name of the reflected class. + * + * @return array A list with all found use statements. + */ + public function parseUseStatements($namespaceName) + { + $statements = array(); + while (($token = $this->next())) { + if ($token[0] === T_USE) { + $statements = array_merge($statements, $this->parseUseStatement()); + continue; + } + if ($token[0] !== T_NAMESPACE || $this->parseNamespace() != $namespaceName) { + continue; + } + + // Get fresh array for new namespace. This is to prevent the parser to collect the use statements + // for a previous namespace with the same name. This is the case if a namespace is defined twice + // or if a namespace with the same name is commented out. + $statements = array(); + } + + return $statements; + } + + /** + * Gets the next non whitespace and non comment token. + * + * @param boolean $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped. + * If FALSE then only whitespace and normal comments are skipped. + * + * @return array|null The token if exists, null otherwise. + */ + private function next($docCommentIsComment = true) + { + for ($i = $this->pointer; $i < $this->numTokens; $i++) { + $this->pointer++; + if ($this->tokens[$i][0] === T_WHITESPACE || + $this->tokens[$i][0] === T_COMMENT || + ($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT)) { + + continue; + } + + return $this->tokens[$i]; + } + + return null; + } + + /** + * Parses a single use statement. + * + * @return array A list with all found class names for a use statement. + */ + private function parseUseStatement() + { + $class = ''; + $alias = ''; + $statements = array(); + $explicitAlias = false; + while (($token = $this->next())) { + $isNameToken = $token[0] === T_STRING || $token[0] === T_NS_SEPARATOR; + if (!$explicitAlias && $isNameToken) { + $class .= $token[1]; + $alias = $token[1]; + } elseif ($explicitAlias && $isNameToken) { + $alias .= $token[1]; + } elseif ($token[0] === T_AS) { + $explicitAlias = true; + $alias = ''; + } elseif ($token === ',') { + $statements[strtolower($alias)] = $class; + $class = ''; + $alias = ''; + $explicitAlias = false; + } elseif ($token === ';') { + $statements[strtolower($alias)] = $class; + break; + } else { + break; + } + } + + return $statements; + } + + /** + * Gets the namespace. + * + * @return string The found namespace. + */ + private function parseNamespace() + { + $name = ''; + while (($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) { + $name .= $token[1]; + } + + return $name; + } +} diff --git a/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpParser/UseStatementParser.php b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpParser/UseStatementParser.php new file mode 100644 index 0000000000..1b0d63d20c --- /dev/null +++ b/wcfsetup/install/files/lib/system/api/php-di/phpdoc-reader/src/PhpDocReader/PhpParser/UseStatementParser.php @@ -0,0 +1,68 @@ + + * @author Christian Kaps + */ +class UseStatementParser +{ + /** + * @return array A list with use statements in the form (Alias => FQN). + */ + public function parseUseStatements(\ReflectionClass $class) + { + if (false === $filename = $class->getFilename()) { + return array(); + } + + $content = $this->getFileContent($filename, $class->getStartLine()); + + if (null === $content) { + return array(); + } + + $namespace = preg_quote($class->getNamespaceName()); + $content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content); + $tokenizer = new TokenParser('parseUseStatements($class->getNamespaceName()); + + return $statements; + } + + /** + * Gets the content of the file right up to the given line number. + * + * @param string $filename The name of the file to load. + * @param integer $lineNumber The number of lines to read from file. + * + * @return string The content of the file. + */ + private function getFileContent($filename, $lineNumber) + { + if ( ! is_file($filename)) { + return null; + } + + $content = ''; + $lineCnt = 0; + $file = new SplFileObject($filename); + while (!$file->eof()) { + if ($lineCnt++ == $lineNumber) { + break; + } + + $content .= $file->fgets(); + } + + return $content; + } +} -- 2.20.1