From 65d075016888e202cc20c9710800512caec77811 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 4 Jan 2010 15:26:20 +0100 Subject: [PATCH] added some Symfony 2 components --- LICENSE | 19 + .../DependencyInjection/Builder.php | 446 +++++ .../BuilderConfiguration.php | 238 +++ .../DependencyInjection/Container.php | 382 ++++ .../ContainerInterface.php | 41 + .../DependencyInjection/Definition.php | 245 +++ .../DependencyInjection/Dumper/Dumper.php | 49 + .../Dumper/DumperInterface.php | 25 + .../Dumper/GraphvizDumper.php | 238 +++ .../DependencyInjection/Dumper/PhpDumper.php | 460 +++++ .../DependencyInjection/Dumper/XmlDumper.php | 232 +++ .../DependencyInjection/Dumper/YamlDumper.php | 215 +++ .../DependencyInjection/Loader/FileLoader.php | 80 + .../Loader/IniFileLoader.php | 60 + .../DependencyInjection/Loader/Loader.php | 35 + .../Loader/LoaderExtension.php | 41 + .../Loader/LoaderExtensionInterface.php | 40 + .../Loader/LoaderInterface.php | 67 + .../Loader/XmlFileLoader.php | 369 ++++ .../Loader/YamlFileLoader.php | 271 +++ .../DependencyInjection/Loader/services.xsd | 113 ++ .../DependencyInjection/Parameter.php | 46 + .../DependencyInjection/Reference.php | 54 + .../DependencyInjection/SimpleXMLElement.php | 87 + .../Components/EventDispatcher/Event.php | 162 ++ .../EventDispatcher/EventDispatcher.php | 158 ++ .../OutputEscaper/ArrayDecorator.php | 168 ++ .../Components/OutputEscaper/Escaper.php | 272 +++ .../OutputEscaper/GetterDecorator.php | 58 + .../OutputEscaper/IteratorDecorator.php | 167 ++ .../OutputEscaper/ObjectDecorator.php | 104 + src/Symfony/Components/OutputEscaper/Safe.php | 81 + .../OutputEscaper/escaping_helpers.php | 112 ++ .../Templating/DebuggerInterface.php | 31 + src/Symfony/Components/Templating/Engine.php | 312 +++ .../Templating/Helper/AssetsHelper.php | 182 ++ .../Components/Templating/Helper/Helper.php | 46 + .../Templating/Helper/HelperInterface.php | 44 + .../Templating/Helper/HelperSet.php | 105 + .../Templating/Helper/JavascriptsHelper.php | 86 + .../Templating/Helper/StylesheetsHelper.php | 86 + .../Templating/Loader/CacheLoader.php | 93 + .../Templating/Loader/ChainLoader.php | 72 + .../Loader/CompilableLoaderInterface.php | 35 + .../Templating/Loader/FilesystemLoader.php | 102 + .../Components/Templating/Loader/Loader.php | 38 + .../Templating/Loader/LoaderInterface.php | 33 + .../Templating/Renderer/PhpRenderer.php | 57 + .../Templating/Renderer/Renderer.php | 63 + .../Templating/Renderer/RendererInterface.php | 43 + .../Templating/Storage/FileStorage.php | 24 + .../Components/Templating/Storage/Storage.php | 58 + .../Templating/Storage/StringStorage.php | 24 + src/Symfony/Components/YAML/Dumper.php | 60 + src/Symfony/Components/YAML/Inline.php | 411 ++++ src/Symfony/Components/YAML/Parser.php | 561 ++++++ src/Symfony/Components/YAML/YAML.php | 133 ++ src/Symfony/Foundation/ClassLoader.php | 123 ++ tests/bin/prove.php | 28 + .../containers/container10.php | 14 + .../containers/container8.php | 12 + .../containers/container9.php | 48 + .../graphviz/services1.dot | 7 + .../graphviz/services10-1.dot | 10 + .../graphviz/services10.dot | 10 + .../graphviz/services9.dot | 21 + .../includes/ProjectExtension.php | 23 + .../DependencyInjection/includes/classes.php | 32 + .../DependencyInjection/includes/foo.php | 34 + .../DependencyInjection/ini/nonvalid.ini | 2 + .../DependencyInjection/ini/parameters.ini | 3 + .../DependencyInjection/ini/parameters1.ini | 3 + .../DependencyInjection/php/services1-1.php | 16 + .../DependencyInjection/php/services1.php | 16 + .../DependencyInjection/php/services8.php | 47 + .../DependencyInjection/php/services9.php | 154 ++ .../DependencyInjection/xml/nonvalid.xml | 3 + .../DependencyInjection/xml/services1.xml | 4 + .../DependencyInjection/xml/services10.xml | 8 + .../DependencyInjection/xml/services11.xml | 8 + .../DependencyInjection/xml/services12.xml | 8 + .../DependencyInjection/xml/services2.xml | 26 + .../DependencyInjection/xml/services3.xml | 11 + .../DependencyInjection/xml/services4.xml | 9 + .../DependencyInjection/xml/services5.xml | 16 + .../DependencyInjection/xml/services6.xml | 45 + .../DependencyInjection/xml/services7.xml | 7 + .../DependencyInjection/xml/services8.xml | 18 + .../DependencyInjection/xml/services9.xml | 54 + .../DependencyInjection/yaml/nonvalid1.yml | 2 + .../DependencyInjection/yaml/nonvalid2.yml | 1 + .../DependencyInjection/yaml/services1.yml | 1 + .../DependencyInjection/yaml/services10.yml | 1 + .../DependencyInjection/yaml/services11.yml | 1 + .../DependencyInjection/yaml/services12.yml | 1 + .../DependencyInjection/yaml/services2.yml | 9 + .../DependencyInjection/yaml/services3.yml | 5 + .../DependencyInjection/yaml/services4.yml | 4 + .../DependencyInjection/yaml/services6.yml | 20 + .../DependencyInjection/yaml/services7.yml | 2 + .../DependencyInjection/yaml/services8.yml | 5 + .../DependencyInjection/yaml/services9.yml | 37 + .../Components/Templating/templates/foo.php | 1 + .../Components/YAML/YtsAnchorAlias.yml | 31 + .../Symfony/Components/YAML/YtsBasicTests.yml | 178 ++ .../Components/YAML/YtsBlockMapping.yml | 52 + .../Components/YAML/YtsDocumentSeparator.yml | 85 + .../Symfony/Components/YAML/YtsErrorTests.yml | 26 + .../Components/YAML/YtsFlowCollections.yml | 60 + .../Components/YAML/YtsFoldedScalars.yml | 176 ++ .../Components/YAML/YtsNullsAndEmpties.yml | 45 + .../YAML/YtsSpecificationExamples.yml | 1681 +++++++++++++++++ .../Components/YAML/YtsTypeTransfers.yml | 244 +++ .../Symfony/Components/YAML/index.yml | 15 + .../Symfony/Components/YAML/sfComments.yml | 41 + .../Symfony/Components/YAML/sfMergeKey.yml | 27 + .../Symfony/Components/YAML/sfObjects.yml | 11 + .../Symfony/Components/YAML/sfQuotes.yml | 33 + .../Symfony/Components/YAML/sfTests.yml | 139 ++ .../Templating/ProjectTemplateDebugger.php | 31 + .../Components/Templating/SimpleHelper.php | 23 + .../lib/vendor/lime/LimeAnnotationSupport.php | 196 ++ .../lime/LimeAssertionFailedException.php | 62 + tests/lib/vendor/lime/LimeAutoloader.php | 238 +++ tests/lib/vendor/lime/LimeColorizer.php | 196 ++ tests/lib/vendor/lime/LimeCoverage.php | 250 +++ tests/lib/vendor/lime/LimeError.php | 162 ++ .../vendor/lime/LimeExceptionExpectation.php | 41 + tests/lib/vendor/lime/LimePrinter.php | 127 ++ tests/lib/vendor/lime/LimeRegistration.php | 96 + tests/lib/vendor/lime/LimeTest.php | 435 +++++ tests/lib/vendor/lime/LimeTestAnalyzer.php | 97 + tests/lib/vendor/lime/LimeTestCase.php | 59 + tests/lib/vendor/lime/LimeTestRunner.php | 217 +++ tests/lib/vendor/lime/LimeTestSuite.php | 120 ++ tests/lib/vendor/lime/LimeTools.php | 43 + tests/lib/vendor/lime/LimeTrace.php | 43 + .../vendor/lime/constraint/LimeConstraint.php | 34 + .../constraint/LimeConstraintContains.php | 37 + .../constraint/LimeConstraintContainsNot.php | 37 + .../constraint/LimeConstraintException.php | 23 + .../constraint/LimeConstraintGreaterThan.php | 37 + .../LimeConstraintGreaterThanEqual.php | 37 + .../constraint/LimeConstraintInterface.php | 32 + .../lime/constraint/LimeConstraintIs.php | 37 + .../lime/constraint/LimeConstraintIsNot.php | 37 + .../constraint/LimeConstraintLessThan.php | 37 + .../LimeConstraintLessThanEqual.php | 37 + .../lime/constraint/LimeConstraintLike.php | 37 + .../lime/constraint/LimeConstraintNotSame.php | 37 + .../lime/constraint/LimeConstraintSame.php | 37 + .../lime/constraint/LimeConstraintUnlike.php | 37 + tests/lib/vendor/lime/lexer/LimeLexer.php | 365 ++++ .../lime/lexer/LimeLexerAnnotationAware.php | 213 +++ .../vendor/lime/lexer/LimeLexerCodeLines.php | 100 + .../lime/lexer/LimeLexerTestVariable.php | 89 + .../lexer/LimeLexerTransformAnnotations.php | 222 +++ .../vendor/lime/lexer/LimeLexerVariables.php | 65 + tests/lib/vendor/lime/lime.php | 262 +++ tests/lib/vendor/lime/mock/LimeMock.php | 345 ++++ .../vendor/lime/mock/LimeMockBehaviour.php | 140 ++ .../lime/mock/LimeMockBehaviourInterface.php | 84 + .../vendor/lime/mock/LimeMockException.php | 26 + .../vendor/lime/mock/LimeMockInterface.php | 73 + .../vendor/lime/mock/LimeMockInvocation.php | 134 ++ .../lime/mock/LimeMockInvocationException.php | 38 + .../mock/LimeMockInvocationExceptionStack.php | 67 + .../mock/LimeMockInvocationExpectation.php | 462 +++++ tests/lib/vendor/lime/mock/LimeMockMethod.php | 57 + .../lime/mock/LimeMockMethodInterface.php | 8 + .../lime/mock/LimeMockOrderedBehaviour.php | 51 + .../vendor/lime/mock/LimeMockRecordState.php | 99 + .../vendor/lime/mock/LimeMockReplayState.php | 75 + .../lime/mock/LimeMockStateInterface.php | 56 + .../lib/vendor/lime/mock/LimeMockTemplate.php | 57 + .../lime/mock/LimeMockUnorderedBehaviour.php | 45 + .../matcher/LimeMockInvocationMatcherAny.php | 59 + .../LimeMockInvocationMatcherAtLeastOnce.php | 63 + .../LimeMockInvocationMatcherBetween.php | 94 + .../LimeMockInvocationMatcherException.php | 27 + .../LimeMockInvocationMatcherInterface.php | 77 + .../LimeMockInvocationMatcherParameter.php | 288 +++ .../LimeMockInvocationMatcherTimes.php | 90 + .../lime/mock/template/mocked_class.tpl | 93 + tests/lib/vendor/lime/output/LimeOutput.php | 169 ++ .../vendor/lime/output/LimeOutputArray.php | 222 +++ .../lime/output/LimeOutputConsoleSummary.php | 325 ++++ .../vendor/lime/output/LimeOutputCoverage.php | 47 + .../vendor/lime/output/LimeOutputFactory.php | 47 + .../lime/output/LimeOutputInspectable.php | 140 ++ .../lime/output/LimeOutputInterface.php | 131 ++ .../lib/vendor/lime/output/LimeOutputNone.php | 41 + .../lib/vendor/lime/output/LimeOutputRaw.php | 96 + .../lib/vendor/lime/output/LimeOutputTap.php | 280 +++ .../lib/vendor/lime/output/LimeOutputXml.php | 139 ++ tests/lib/vendor/lime/parser/LimeParser.php | 43 + .../lime/parser/LimeParserInterface.php | 8 + .../lib/vendor/lime/parser/LimeParserRaw.php | 77 + .../lib/vendor/lime/parser/LimeParserTap.php | 81 + tests/lib/vendor/lime/shell/LimeShell.php | 123 ++ tests/lib/vendor/lime/shell/LimeShellCode.php | 12 + .../vendor/lime/shell/LimeShellCommand.php | 67 + .../vendor/lime/shell/LimeShellProcess.php | 35 + tests/lib/vendor/lime/tester/LimeTester.php | 111 ++ .../vendor/lime/tester/LimeTesterArray.php | 293 +++ .../vendor/lime/tester/LimeTesterDouble.php | 83 + .../lime/tester/LimeTesterException.php | 24 + .../vendor/lime/tester/LimeTesterFactory.php | 113 ++ .../vendor/lime/tester/LimeTesterInteger.php | 17 + .../lime/tester/LimeTesterInterface.php | 40 + .../vendor/lime/tester/LimeTesterObject.php | 121 ++ .../vendor/lime/tester/LimeTesterResource.php | 48 + .../vendor/lime/tester/LimeTesterScalar.php | 116 ++ .../vendor/lime/tester/LimeTesterString.php | 51 + .../DependencyInjection/BuilderTest.php | 279 +++ .../DependencyInjection/ContainerTest.php | 208 ++ .../DependencyInjection/CrossCheckTest.php | 78 + .../DependencyInjection/DefinitionTest.php | 71 + .../DependencyInjection/Dumper/DumperTest.php | 32 + .../Dumper/GraphvizDumperTest.php | 46 + .../Dumper/PhpDumperTest.php | 52 + .../Dumper/XmlDumperTest.php | 51 + .../Dumper/YamlDumperTest.php | 51 + .../Loader/FileLoaderTest.php | 52 + .../Loader/IniLoaderTest.php | 50 + .../Loader/LoaderExtensionTest.php | 32 + .../DependencyInjection/Loader/LoaderTest.php | 29 + .../Loader/XmlFileLoaderTest.php | 189 ++ .../Loader/YamlFileLoaderTest.php | 146 ++ .../DependencyInjection/ParameterTest.php | 21 + .../DependencyInjection/ReferenceTest.php | 21 + .../EventDispatcher/EventDispatcherTest.php | 134 ++ .../Components/EventDispatcher/EventTest.php | 63 + .../OutputEscaper/ArrayDecoratorTest.php | 78 + .../Components/OutputEscaper/EscaperTest.php | 154 ++ .../OutputEscaper/ObjectDecoratorTest.php | 47 + .../Components/OutputEscaper/SafeTest.php | 93 + .../Components/Templating/EngineTest.php | 239 +++ .../Templating/Helper/AssetsTest.php | 90 + .../Templating/Helper/HelperSetTest.php | 52 + .../Templating/Helper/HelperTest.php | 31 + .../Helper/JavascriptsHelperTest.php | 47 + .../Helper/StylesheetsHelperTest.php | 47 + .../Templating/Loader/CacheLoaderTest.php | 98 + .../Templating/Loader/ChainLoaderTest.php | 51 + .../Loader/FilesystemLoaderTest.php | 74 + .../Templating/Loader/LoaderTest.php | 36 + .../Templating/Renderer/PhpRendererTest.php | 30 + .../Templating/Renderer/RendererTest.php | 56 + .../Templating/Storage/FileStorageTest.php | 20 + .../Templating/Storage/StorageTest.php | 28 + .../Templating/Storage/StringStorageTest.php | 20 + .../Symfony/Components/YAML/DumperTest.php | 153 ++ .../Symfony/Components/YAML/InlineTest.php | 148 ++ .../Symfony/Components/YAML/ParserTest.php | 84 + tests/unit/bootstrap.php | 17 + 256 files changed, 24879 insertions(+) create mode 100644 LICENSE create mode 100644 src/Symfony/Components/DependencyInjection/Builder.php create mode 100644 src/Symfony/Components/DependencyInjection/BuilderConfiguration.php create mode 100644 src/Symfony/Components/DependencyInjection/Container.php create mode 100644 src/Symfony/Components/DependencyInjection/ContainerInterface.php create mode 100644 src/Symfony/Components/DependencyInjection/Definition.php create mode 100644 src/Symfony/Components/DependencyInjection/Dumper/Dumper.php create mode 100644 src/Symfony/Components/DependencyInjection/Dumper/DumperInterface.php create mode 100644 src/Symfony/Components/DependencyInjection/Dumper/GraphvizDumper.php create mode 100644 src/Symfony/Components/DependencyInjection/Dumper/PhpDumper.php create mode 100644 src/Symfony/Components/DependencyInjection/Dumper/XmlDumper.php create mode 100644 src/Symfony/Components/DependencyInjection/Dumper/YamlDumper.php create mode 100644 src/Symfony/Components/DependencyInjection/Loader/FileLoader.php create mode 100644 src/Symfony/Components/DependencyInjection/Loader/IniFileLoader.php create mode 100644 src/Symfony/Components/DependencyInjection/Loader/Loader.php create mode 100644 src/Symfony/Components/DependencyInjection/Loader/LoaderExtension.php create mode 100644 src/Symfony/Components/DependencyInjection/Loader/LoaderExtensionInterface.php create mode 100644 src/Symfony/Components/DependencyInjection/Loader/LoaderInterface.php create mode 100644 src/Symfony/Components/DependencyInjection/Loader/XmlFileLoader.php create mode 100644 src/Symfony/Components/DependencyInjection/Loader/YamlFileLoader.php create mode 100644 src/Symfony/Components/DependencyInjection/Loader/services.xsd create mode 100644 src/Symfony/Components/DependencyInjection/Parameter.php create mode 100644 src/Symfony/Components/DependencyInjection/Reference.php create mode 100644 src/Symfony/Components/DependencyInjection/SimpleXMLElement.php create mode 100644 src/Symfony/Components/EventDispatcher/Event.php create mode 100644 src/Symfony/Components/EventDispatcher/EventDispatcher.php create mode 100644 src/Symfony/Components/OutputEscaper/ArrayDecorator.php create mode 100644 src/Symfony/Components/OutputEscaper/Escaper.php create mode 100644 src/Symfony/Components/OutputEscaper/GetterDecorator.php create mode 100644 src/Symfony/Components/OutputEscaper/IteratorDecorator.php create mode 100644 src/Symfony/Components/OutputEscaper/ObjectDecorator.php create mode 100644 src/Symfony/Components/OutputEscaper/Safe.php create mode 100644 src/Symfony/Components/OutputEscaper/escaping_helpers.php create mode 100644 src/Symfony/Components/Templating/DebuggerInterface.php create mode 100644 src/Symfony/Components/Templating/Engine.php create mode 100644 src/Symfony/Components/Templating/Helper/AssetsHelper.php create mode 100644 src/Symfony/Components/Templating/Helper/Helper.php create mode 100644 src/Symfony/Components/Templating/Helper/HelperInterface.php create mode 100644 src/Symfony/Components/Templating/Helper/HelperSet.php create mode 100644 src/Symfony/Components/Templating/Helper/JavascriptsHelper.php create mode 100644 src/Symfony/Components/Templating/Helper/StylesheetsHelper.php create mode 100644 src/Symfony/Components/Templating/Loader/CacheLoader.php create mode 100644 src/Symfony/Components/Templating/Loader/ChainLoader.php create mode 100644 src/Symfony/Components/Templating/Loader/CompilableLoaderInterface.php create mode 100644 src/Symfony/Components/Templating/Loader/FilesystemLoader.php create mode 100644 src/Symfony/Components/Templating/Loader/Loader.php create mode 100644 src/Symfony/Components/Templating/Loader/LoaderInterface.php create mode 100644 src/Symfony/Components/Templating/Renderer/PhpRenderer.php create mode 100644 src/Symfony/Components/Templating/Renderer/Renderer.php create mode 100644 src/Symfony/Components/Templating/Renderer/RendererInterface.php create mode 100644 src/Symfony/Components/Templating/Storage/FileStorage.php create mode 100644 src/Symfony/Components/Templating/Storage/Storage.php create mode 100644 src/Symfony/Components/Templating/Storage/StringStorage.php create mode 100644 src/Symfony/Components/YAML/Dumper.php create mode 100644 src/Symfony/Components/YAML/Inline.php create mode 100644 src/Symfony/Components/YAML/Parser.php create mode 100644 src/Symfony/Components/YAML/YAML.php create mode 100644 src/Symfony/Foundation/ClassLoader.php create mode 100644 tests/bin/prove.php create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/containers/container10.php create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/containers/container8.php create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/containers/container9.php create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services1.dot create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services10-1.dot create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services10.dot create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services9.dot create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/includes/ProjectExtension.php create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/includes/classes.php create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/includes/foo.php create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/ini/nonvalid.ini create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/ini/parameters.ini create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/ini/parameters1.ini create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/php/services1-1.php create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/php/services1.php create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/php/services8.php create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/php/services9.php create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/xml/nonvalid.xml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/xml/services1.xml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/xml/services10.xml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/xml/services11.xml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/xml/services12.xml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/xml/services2.xml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/xml/services3.xml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/xml/services4.xml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/xml/services5.xml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/xml/services6.xml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/xml/services7.xml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/xml/services8.xml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/xml/services9.xml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/yaml/nonvalid1.yml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/yaml/nonvalid2.yml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/yaml/services1.yml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/yaml/services10.yml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/yaml/services11.yml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/yaml/services12.yml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/yaml/services2.yml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/yaml/services3.yml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/yaml/services4.yml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/yaml/services6.yml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/yaml/services7.yml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/yaml/services8.yml create mode 100644 tests/fixtures/Symfony/Components/DependencyInjection/yaml/services9.yml create mode 100644 tests/fixtures/Symfony/Components/Templating/templates/foo.php create mode 100644 tests/fixtures/Symfony/Components/YAML/YtsAnchorAlias.yml create mode 100644 tests/fixtures/Symfony/Components/YAML/YtsBasicTests.yml create mode 100644 tests/fixtures/Symfony/Components/YAML/YtsBlockMapping.yml create mode 100644 tests/fixtures/Symfony/Components/YAML/YtsDocumentSeparator.yml create mode 100644 tests/fixtures/Symfony/Components/YAML/YtsErrorTests.yml create mode 100644 tests/fixtures/Symfony/Components/YAML/YtsFlowCollections.yml create mode 100644 tests/fixtures/Symfony/Components/YAML/YtsFoldedScalars.yml create mode 100644 tests/fixtures/Symfony/Components/YAML/YtsNullsAndEmpties.yml create mode 100644 tests/fixtures/Symfony/Components/YAML/YtsSpecificationExamples.yml create mode 100644 tests/fixtures/Symfony/Components/YAML/YtsTypeTransfers.yml create mode 100644 tests/fixtures/Symfony/Components/YAML/index.yml create mode 100644 tests/fixtures/Symfony/Components/YAML/sfComments.yml create mode 100644 tests/fixtures/Symfony/Components/YAML/sfMergeKey.yml create mode 100644 tests/fixtures/Symfony/Components/YAML/sfObjects.yml create mode 100644 tests/fixtures/Symfony/Components/YAML/sfQuotes.yml create mode 100644 tests/fixtures/Symfony/Components/YAML/sfTests.yml create mode 100644 tests/lib/SymfonyTests/Components/Templating/ProjectTemplateDebugger.php create mode 100644 tests/lib/SymfonyTests/Components/Templating/SimpleHelper.php create mode 100644 tests/lib/vendor/lime/LimeAnnotationSupport.php create mode 100644 tests/lib/vendor/lime/LimeAssertionFailedException.php create mode 100644 tests/lib/vendor/lime/LimeAutoloader.php create mode 100644 tests/lib/vendor/lime/LimeColorizer.php create mode 100644 tests/lib/vendor/lime/LimeCoverage.php create mode 100644 tests/lib/vendor/lime/LimeError.php create mode 100644 tests/lib/vendor/lime/LimeExceptionExpectation.php create mode 100644 tests/lib/vendor/lime/LimePrinter.php create mode 100644 tests/lib/vendor/lime/LimeRegistration.php create mode 100644 tests/lib/vendor/lime/LimeTest.php create mode 100644 tests/lib/vendor/lime/LimeTestAnalyzer.php create mode 100644 tests/lib/vendor/lime/LimeTestCase.php create mode 100644 tests/lib/vendor/lime/LimeTestRunner.php create mode 100644 tests/lib/vendor/lime/LimeTestSuite.php create mode 100644 tests/lib/vendor/lime/LimeTools.php create mode 100644 tests/lib/vendor/lime/LimeTrace.php create mode 100644 tests/lib/vendor/lime/constraint/LimeConstraint.php create mode 100644 tests/lib/vendor/lime/constraint/LimeConstraintContains.php create mode 100644 tests/lib/vendor/lime/constraint/LimeConstraintContainsNot.php create mode 100644 tests/lib/vendor/lime/constraint/LimeConstraintException.php create mode 100644 tests/lib/vendor/lime/constraint/LimeConstraintGreaterThan.php create mode 100644 tests/lib/vendor/lime/constraint/LimeConstraintGreaterThanEqual.php create mode 100644 tests/lib/vendor/lime/constraint/LimeConstraintInterface.php create mode 100644 tests/lib/vendor/lime/constraint/LimeConstraintIs.php create mode 100644 tests/lib/vendor/lime/constraint/LimeConstraintIsNot.php create mode 100644 tests/lib/vendor/lime/constraint/LimeConstraintLessThan.php create mode 100644 tests/lib/vendor/lime/constraint/LimeConstraintLessThanEqual.php create mode 100644 tests/lib/vendor/lime/constraint/LimeConstraintLike.php create mode 100644 tests/lib/vendor/lime/constraint/LimeConstraintNotSame.php create mode 100644 tests/lib/vendor/lime/constraint/LimeConstraintSame.php create mode 100644 tests/lib/vendor/lime/constraint/LimeConstraintUnlike.php create mode 100644 tests/lib/vendor/lime/lexer/LimeLexer.php create mode 100644 tests/lib/vendor/lime/lexer/LimeLexerAnnotationAware.php create mode 100644 tests/lib/vendor/lime/lexer/LimeLexerCodeLines.php create mode 100644 tests/lib/vendor/lime/lexer/LimeLexerTestVariable.php create mode 100644 tests/lib/vendor/lime/lexer/LimeLexerTransformAnnotations.php create mode 100644 tests/lib/vendor/lime/lexer/LimeLexerVariables.php create mode 100644 tests/lib/vendor/lime/lime.php create mode 100644 tests/lib/vendor/lime/mock/LimeMock.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockBehaviour.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockBehaviourInterface.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockException.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockInterface.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockInvocation.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockInvocationException.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockInvocationExceptionStack.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockInvocationExpectation.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockMethod.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockMethodInterface.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockOrderedBehaviour.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockRecordState.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockReplayState.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockStateInterface.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockTemplate.php create mode 100644 tests/lib/vendor/lime/mock/LimeMockUnorderedBehaviour.php create mode 100644 tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherAny.php create mode 100644 tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherAtLeastOnce.php create mode 100644 tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherBetween.php create mode 100644 tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherException.php create mode 100644 tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherInterface.php create mode 100644 tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherParameter.php create mode 100644 tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherTimes.php create mode 100644 tests/lib/vendor/lime/mock/template/mocked_class.tpl create mode 100644 tests/lib/vendor/lime/output/LimeOutput.php create mode 100644 tests/lib/vendor/lime/output/LimeOutputArray.php create mode 100644 tests/lib/vendor/lime/output/LimeOutputConsoleSummary.php create mode 100644 tests/lib/vendor/lime/output/LimeOutputCoverage.php create mode 100644 tests/lib/vendor/lime/output/LimeOutputFactory.php create mode 100644 tests/lib/vendor/lime/output/LimeOutputInspectable.php create mode 100644 tests/lib/vendor/lime/output/LimeOutputInterface.php create mode 100644 tests/lib/vendor/lime/output/LimeOutputNone.php create mode 100644 tests/lib/vendor/lime/output/LimeOutputRaw.php create mode 100644 tests/lib/vendor/lime/output/LimeOutputTap.php create mode 100644 tests/lib/vendor/lime/output/LimeOutputXml.php create mode 100644 tests/lib/vendor/lime/parser/LimeParser.php create mode 100644 tests/lib/vendor/lime/parser/LimeParserInterface.php create mode 100644 tests/lib/vendor/lime/parser/LimeParserRaw.php create mode 100644 tests/lib/vendor/lime/parser/LimeParserTap.php create mode 100644 tests/lib/vendor/lime/shell/LimeShell.php create mode 100644 tests/lib/vendor/lime/shell/LimeShellCode.php create mode 100644 tests/lib/vendor/lime/shell/LimeShellCommand.php create mode 100644 tests/lib/vendor/lime/shell/LimeShellProcess.php create mode 100644 tests/lib/vendor/lime/tester/LimeTester.php create mode 100644 tests/lib/vendor/lime/tester/LimeTesterArray.php create mode 100644 tests/lib/vendor/lime/tester/LimeTesterDouble.php create mode 100644 tests/lib/vendor/lime/tester/LimeTesterException.php create mode 100644 tests/lib/vendor/lime/tester/LimeTesterFactory.php create mode 100644 tests/lib/vendor/lime/tester/LimeTesterInteger.php create mode 100644 tests/lib/vendor/lime/tester/LimeTesterInterface.php create mode 100644 tests/lib/vendor/lime/tester/LimeTesterObject.php create mode 100644 tests/lib/vendor/lime/tester/LimeTesterResource.php create mode 100644 tests/lib/vendor/lime/tester/LimeTesterScalar.php create mode 100644 tests/lib/vendor/lime/tester/LimeTesterString.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/BuilderTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/ContainerTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/CrossCheckTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/DefinitionTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/Dumper/DumperTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/Dumper/GraphvizDumperTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/Dumper/PhpDumperTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/Dumper/XmlDumperTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/Dumper/YamlDumperTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/Loader/FileLoaderTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/Loader/IniLoaderTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/Loader/LoaderExtensionTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/Loader/LoaderTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/Loader/XmlFileLoaderTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/Loader/YamlFileLoaderTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/ParameterTest.php create mode 100644 tests/unit/Symfony/Components/DependencyInjection/ReferenceTest.php create mode 100644 tests/unit/Symfony/Components/EventDispatcher/EventDispatcherTest.php create mode 100644 tests/unit/Symfony/Components/EventDispatcher/EventTest.php create mode 100644 tests/unit/Symfony/Components/OutputEscaper/ArrayDecoratorTest.php create mode 100644 tests/unit/Symfony/Components/OutputEscaper/EscaperTest.php create mode 100644 tests/unit/Symfony/Components/OutputEscaper/ObjectDecoratorTest.php create mode 100644 tests/unit/Symfony/Components/OutputEscaper/SafeTest.php create mode 100644 tests/unit/Symfony/Components/Templating/EngineTest.php create mode 100644 tests/unit/Symfony/Components/Templating/Helper/AssetsTest.php create mode 100644 tests/unit/Symfony/Components/Templating/Helper/HelperSetTest.php create mode 100644 tests/unit/Symfony/Components/Templating/Helper/HelperTest.php create mode 100644 tests/unit/Symfony/Components/Templating/Helper/JavascriptsHelperTest.php create mode 100644 tests/unit/Symfony/Components/Templating/Helper/StylesheetsHelperTest.php create mode 100644 tests/unit/Symfony/Components/Templating/Loader/CacheLoaderTest.php create mode 100644 tests/unit/Symfony/Components/Templating/Loader/ChainLoaderTest.php create mode 100644 tests/unit/Symfony/Components/Templating/Loader/FilesystemLoaderTest.php create mode 100644 tests/unit/Symfony/Components/Templating/Loader/LoaderTest.php create mode 100644 tests/unit/Symfony/Components/Templating/Renderer/PhpRendererTest.php create mode 100644 tests/unit/Symfony/Components/Templating/Renderer/RendererTest.php create mode 100644 tests/unit/Symfony/Components/Templating/Storage/FileStorageTest.php create mode 100644 tests/unit/Symfony/Components/Templating/Storage/StorageTest.php create mode 100644 tests/unit/Symfony/Components/Templating/Storage/StringStorageTest.php create mode 100644 tests/unit/Symfony/Components/YAML/DumperTest.php create mode 100644 tests/unit/Symfony/Components/YAML/InlineTest.php create mode 100644 tests/unit/Symfony/Components/YAML/ParserTest.php create mode 100644 tests/unit/bootstrap.php diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000000..89c135de5124 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2010 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Components/DependencyInjection/Builder.php b/src/Symfony/Components/DependencyInjection/Builder.php new file mode 100644 index 000000000000..e316a3ee3036 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Builder.php @@ -0,0 +1,446 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Builder is a DI container that provides an interface to build the services. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id: Builder.php 269 2009-03-26 20:39:16Z fabien $ + */ +class Builder extends Container +{ + protected + $definitions = array(), + $aliases = array(), + $loading = array(); + + /** + * Sets a service. + * + * @param string $id The service identifier + * @param object $service The service instance + */ + public function setService($id, $service) + { + unset($this->aliases[$id]); + + parent::setService($id, $service); + } + + /** + * Returns true if the given service is defined. + * + * @param string $id The service identifier + * + * @return Boolean true if the service is defined, false otherwise + */ + public function hasService($id) + { + return isset($this->definitions[$id]) || isset($this->aliases[$id]) || parent::hasService($id); + } + + /** + * Gets a service. + * + * @param string $id The service identifier + * @param int $invalidBehavior The behavior when the service does not exist + * + * @return object The associated service + * + * @throws \InvalidArgumentException if the service is not defined + * @throws \LogicException if the service has a circular reference to itself + * + * @see Reference + */ + public function getService($id, $invalidBehavior = Container::EXCEPTION_ON_INVALID_REFERENCE) + { + try + { + return parent::getService($id, Container::EXCEPTION_ON_INVALID_REFERENCE); + } + catch (\InvalidArgumentException $e) + { + if (isset($this->loading[$id])) + { + throw new \LogicException(sprintf('The service "%s" has a circular reference to itself.', $id)); + } + + if (!$this->hasDefinition($id) && isset($this->aliases[$id])) + { + return $this->getService($this->aliases[$id]); + } + + try + { + $definition = $this->getDefinition($id); + } + catch (\InvalidArgumentException $e) + { + if (Container::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) + { + return null; + } + + throw $e; + } + + $this->loading[$id] = true; + + if ($definition->isShared()) + { + $service = $this->services[$id] = $this->createService($definition); + } + else + { + $service = $this->createService($definition); + } + + unset($this->loading[$id]); + + return $service; + } + } + + /** + * Merges a BuilderConfiguration with the current Builder configuration. + * + * Service definitions overrides the current defined ones. + * + * But for parameters, they are overridden by the current ones. It allows + * the parameters passed to the container constructor to have precedence + * over the loaded ones. + * + * $container = new Builder(array('foo' => 'bar')); + * $loader = new LoaderXXX($container); + * $loader->load('resource_name'); + * $container->register('foo', new stdClass()); + * + * In the above example, even if the loaded resource defines a foo + * parameter, the value will still be 'bar' as defined in the builder + * constructor. + */ + public function merge(BuilderConfiguration $configuration = null) + { + if (null === $configuration) + { + return; + } + + $this->aliases = array_merge($this->aliases, $configuration->getAliases()); + $this->definitions = array_merge($this->definitions, $configuration->getDefinitions()); + + $currentParameters = $this->getParameters(); + foreach ($configuration->getParameters() as $key => $value) + { + $this->setParameter($key, $value); + } + $this->addParameters($currentParameters); + + foreach ($this->parameters as $key => $value) + { + $this->parameters[$key] = self::resolveValue($value, $this->parameters); + } + } + + /** + * Gets all service ids. + * + * @return array An array of all defined service ids + */ + public function getServiceIds() + { + return array_unique(array_merge(array_keys($this->getDefinitions()), array_keys($this->aliases), parent::getServiceIds())); + } + + /** + * Sets an alias for an existing service. + * + * @param string $alias The alias to create + * @param string $id The service to alias + */ + public function setAlias($alias, $id) + { + $this->aliases[$alias] = $id; + } + + /** + * Gets all defined aliases. + * + * @return array An array of aliases + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * Registers a service definition. + * + * This methods allows for simple registration of service definition + * with a fluid interface. + * + * @param string $id The service identifier + * @param string $class The service class + * + * @return Definition A Definition instance + */ + public function register($id, $class) + { + return $this->setDefinition($id, new Definition($class)); + } + + /** + * Adds the service definitions. + * + * @param array $definitions An array of service definitions + */ + public function addDefinitions(array $definitions) + { + foreach ($definitions as $id => $definition) + { + $this->setDefinition($id, $definition); + } + } + + /** + * Sets the service definitions. + * + * @param array $definitions An array of service definitions + */ + public function setDefinitions(array $definitions) + { + $this->definitions = array(); + $this->addDefinitions($definitions); + } + + /** + * Gets all service definitions. + * + * @return array An array of Definition instances + */ + public function getDefinitions() + { + return $this->definitions; + } + + /** + * Sets a service definition. + * + * @param string $id The service identifier + * @param Definition $definition A Definition instance + */ + public function setDefinition($id, Definition $definition) + { + unset($this->aliases[$id]); + + return $this->definitions[$id] = $definition; + } + + /** + * Returns true if a service definition exists under the given identifier. + * + * @param string $id The service identifier + * + * @return Boolean true if the service definition exists, false otherwise + */ + public function hasDefinition($id) + { + return array_key_exists($id, $this->definitions); + } + + /** + * Gets a service definition. + * + * @param string $id The service identifier + * + * @return Definition A Definition instance + * + * @throws \InvalidArgumentException if the service definition does not exist + */ + public function getDefinition($id) + { + if (!$this->hasDefinition($id)) + { + throw new \InvalidArgumentException(sprintf('The service definition "%s" does not exist.', $id)); + } + + return $this->definitions[$id]; + } + + /** + * Creates a service for a service definition. + * + * @param Definition $definition A service definition instance + * + * @return object The service described by the service definition + */ + protected function createService(Definition $definition) + { + if (null !== $definition->getFile()) + { + require_once self::resolveValue($definition->getFile(), $this->parameters); + } + + $r = new \ReflectionClass(self::resolveValue($definition->getClass(), $this->parameters)); + + $arguments = $this->resolveServices(self::resolveValue($definition->getArguments(), $this->parameters)); + + if (null !== $definition->getConstructor()) + { + $service = call_user_func_array(array(self::resolveValue($definition->getClass(), $this->parameters), $definition->getConstructor()), $arguments); + } + else + { + $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments); + } + + foreach ($definition->getMethodCalls() as $call) + { + $services = self::getServiceConditionals($call[1]); + + $ok = true; + foreach ($services as $s) + { + if (!$this->hasService($s)) + { + $ok = false; + break; + } + } + + if ($ok) + { + call_user_func_array(array($service, $call[0]), $this->resolveServices(self::resolveValue($call[1], $this->parameters))); + } + } + + if ($callable = $definition->getConfigurator()) + { + if (is_array($callable) && is_object($callable[0]) && $callable[0] instanceof Reference) + { + $callable[0] = $this->getService((string) $callable[0]); + } + elseif (is_array($callable)) + { + $callable[0] = self::resolveValue($callable[0], $this->parameters); + } + + if (!is_callable($callable)) + { + throw new \InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_class($service))); + } + + call_user_func($callable, $service); + } + + return $service; + } + + /** + * Replaces parameter placeholders (%name%) by their values. + * + * @param mixed $value A value + * + * @return mixed The same value with all placeholders replaced by their values + * + * @throws \RuntimeException if a placeholder references a parameter that does not exist + */ + static public function resolveValue($value, $parameters) + { + if (is_array($value)) + { + $args = array(); + foreach ($value as $k => $v) + { + $args[self::resolveValue($k, $parameters)] = self::resolveValue($v, $parameters); + } + + $value = $args; + } + else if (is_string($value)) + { + if (preg_match('/^%([^%]+)%$/', $value, $match)) + { + // we do this to deal with non string values (boolean, integer, ...) + // the preg_replace_callback converts them to strings + if (!array_key_exists($name = strtolower($match[1]), $parameters)) + { + throw new \RuntimeException(sprintf('The parameter "%s" must be defined.', $name)); + } + + $value = $parameters[$name]; + } + else + { + $replaceParameter = function ($match) use ($parameters) + { + if (!array_key_exists($name = strtolower($match[2]), $parameters)) + { + throw new \RuntimeException(sprintf('The parameter "%s" must be defined.', $name)); + } + + return $parameters[$name]; + }; + + $value = str_replace('%%', '%', preg_replace_callback('/(?resolveServices($v); + } + } + else if (is_object($value) && $value instanceof Reference) + { + $value = $this->getService((string) $value, $value->getInvalidBehavior()); + } + + return $value; + } + + static public function getServiceConditionals($value) + { + $services = array(); + + if (is_array($value)) + { + foreach ($value as $v) + { + $services = array_unique(array_merge($services, self::getServiceConditionals($v))); + } + } + elseif (is_object($value) && $value instanceof Reference && $value->getInvalidBehavior() === Container::IGNORE_ON_INVALID_REFERENCE) + { + $services[] = (string) $value; + } + + return $services; + } +} diff --git a/src/Symfony/Components/DependencyInjection/BuilderConfiguration.php b/src/Symfony/Components/DependencyInjection/BuilderConfiguration.php new file mode 100644 index 000000000000..49332632cc32 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/BuilderConfiguration.php @@ -0,0 +1,238 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * A BuilderConfiguration is a consistent set of definitions and parameters. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id: Definition.php 269 2009-03-26 20:39:16Z fabien $ + */ +class BuilderConfiguration +{ + protected $definitions = array(); + protected $parameters = array(); + protected $aliases = array(); + + public function __construct(array $definitions = array(), array $parameters = array()) + { + $this->setDefinitions($definitions); + $this->setParameters($parameters); + } + + /** + * Merges a BuilderConfiguration with the current one. + * + * @param BuilderConfiguration $configuration + */ + public function merge(BuilderConfiguration $configuration = null) + { + if (null === $configuration) + { + return; + } + + $this->addDefinitions($configuration->getDefinitions()); + $this->addAliases($configuration->getAliases()); + $this->addParameters($configuration->getParameters()); + } + + /** + * Sets the service container parameters. + * + * @param array $parameters An array of parameters + */ + public function setParameters(array $parameters) + { + $this->parameters = array(); + foreach ($parameters as $key => $value) + { + $this->parameters[strtolower($key)] = $value; + } + } + + /** + * Adds parameters to the service container parameters. + * + * @param array $parameters An array of parameters + */ + public function addParameters(array $parameters) + { + $this->setParameters(array_merge($this->parameters, $parameters)); + } + + /** + * Gets the service container parameters. + * + * @return array An array of parameters + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Returns true if a parameter name is defined. + * + * @param string $name The parameter name + * + * @return Boolean true if the parameter name is defined, false otherwise + */ + public function hasParameter($name) + { + return array_key_exists(strtolower($name), $this->parameters); + } + + /** + * Gets a service container parameter. + * + * @param string $name The parameter name + * + * @return mixed The parameter value + * + * @throws \InvalidArgumentException if the parameter is not defined + */ + public function getParameter($name) + { + if (!$this->hasParameter($name)) + { + throw new \InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name)); + } + + return $this->parameters[strtolower($name)]; + } + + /** + * Sets a service container parameter. + * + * @param string $name The parameter name + * @param mixed $parameters The parameter value + */ + public function setParameter($name, $value) + { + $this->parameters[strtolower($name)] = $value; + } + + /** + * Sets an alias for an existing service. + * + * @param string $alias The alias to create + * @param string $id The service to alias + */ + public function setAlias($alias, $id) + { + $this->aliases[$alias] = $id; + } + + /** + * Adds definition aliases. + * + * @param array $aliases An array of aliases + */ + public function addAliases(array $aliases) + { + foreach ($aliases as $alias => $id) + { + $this->setAlias($alias, $id); + } + } + + /** + * Gets all defined aliases. + * + * @return array An array of aliases + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * Sets a definition. + * + * @param string $id The identifier + * @param Definition $definition A Definition instance + */ + public function setDefinition($id, Definition $definition) + { + unset($this->aliases[$id]); + + return $this->definitions[$id] = $definition; + } + + /** + * Adds the definitions. + * + * @param array $definitions An array of definitions + */ + public function addDefinitions(array $definitions) + { + foreach ($definitions as $id => $definition) + { + $this->setDefinition($id, $definition); + } + } + + /** + * Sets the definitions. + * + * @param array $definitions An array of definitions + */ + public function setDefinitions(array $definitions) + { + $this->definitions = array(); + $this->addDefinitions($definitions); + } + + /** + * Gets all definitions. + * + * @return array An array of Definition instances + */ + public function getDefinitions() + { + return $this->definitions; + } + + /** + * Returns true if a service definition exists under the given identifier. + * + * @param string $id The service identifier + * + * @return Boolean true if the service definition exists, false otherwise + */ + public function hasDefinition($id) + { + return array_key_exists($id, $this->definitions); + } + + /** + * Gets a service definition. + * + * @param string $id The service identifier + * + * @return Definition A Definition instance + * + * @throws \InvalidArgumentException if the service definition does not exist + */ + public function getDefinition($id) + { + if (!$this->hasDefinition($id)) + { + throw new \InvalidArgumentException(sprintf('The service definition "%s" does not exist.', $id)); + } + + return $this->definitions[$id]; + } +} diff --git a/src/Symfony/Components/DependencyInjection/Container.php b/src/Symfony/Components/DependencyInjection/Container.php new file mode 100644 index 000000000000..9a991c00304e --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Container.php @@ -0,0 +1,382 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Container is a dependency injection container. + * + * It gives access to object instances (services), and parameters. + * + * Services and parameters are simple key/pair stores. + * + * Parameters keys are case insensitive. + * + * A service id can contain lowercased letters, digits, underscores, and dots. + * Underscores are used to separate words, and dots to group services + * under namespaces: + * + *
    + *
  • request
  • + *
  • mysql_session_storage
  • + *
  • symfony.mysql_session_storage
  • + *
+ * + * A service can also be defined by creating a method named + * getXXXService(), where XXX is the camelized version of the id: + * + *
    + *
  • request -> getRequestService()
  • + *
  • mysql_session_storage -> getMysqlSessionStorageService()
  • + *
  • symfony.mysql_session_storage -> getSymfony_MysqlSessionStorageService()
  • + *
+ * + * The container can have three possible behaviors when a service does not exist: + * + * * EXCEPTION_ON_INVALID_REFERENCE: Throws an exception (the default) + * * NULL_ON_INVALID_REFERENCE: Returns null + * * IGNORE_ON_INVALID_REFERENCE: Ignores the wrapping command asking for the reference + * (for instance, ignore a setter if the service does not exist) + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class Container implements ContainerInterface, \ArrayAccess, \Iterator +{ + protected + $serviceIds = array(), + $parameters = array(), + $services = array(), + $count = 0; + + const EXCEPTION_ON_INVALID_REFERENCE = 1; + const NULL_ON_INVALID_REFERENCE = 2; + const IGNORE_ON_INVALID_REFERENCE = 3; + + /** + * Constructor. + * + * @param array $parameters An array of parameters + */ + public function __construct(array $parameters = array()) + { + $this->setParameters($parameters); + $this->setService('service_container', $this); + } + + /** + * Sets the service container parameters. + * + * @param array $parameters An array of parameters + */ + public function setParameters(array $parameters) + { + $this->parameters = array(); + foreach ($parameters as $key => $value) + { + $this->parameters[strtolower($key)] = $value; + } + } + + /** + * Adds parameters to the service container parameters. + * + * @param array $parameters An array of parameters + */ + public function addParameters(array $parameters) + { + $this->setParameters(array_merge($this->parameters, $parameters)); + } + + /** + * Gets the service container parameters. + * + * @return array An array of parameters + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Gets a service container parameter. + * + * @param string $name The parameter name + * + * @return mixed The parameter value + * + * @throws \InvalidArgumentException if the parameter is not defined + */ + public function getParameter($name) + { + if (!$this->hasParameter($name)) + { + throw new \InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name)); + } + + return $this->parameters[strtolower($name)]; + } + + /** + * Sets a service container parameter. + * + * @param string $name The parameter name + * @param mixed $parameters The parameter value + */ + public function setParameter($name, $value) + { + $this->parameters[strtolower($name)] = $value; + } + + /** + * Returns true if a parameter name is defined. + * + * @param string $name The parameter name + * + * @return Boolean true if the parameter name is defined, false otherwise + */ + public function hasParameter($name) + { + return array_key_exists(strtolower($name), $this->parameters); + } + + /** + * Sets a service. + * + * @param string $id The service identifier + * @param object $service The service instance + */ + public function setService($id, $service) + { + $this->services[$id] = $service; + } + + /** + * Returns true if the given service is defined. + * + * @param string $id The service identifier + * + * @return Boolean true if the service is defined, false otherwise + */ + public function hasService($id) + { + return isset($this->services[$id]) || method_exists($this, 'get'.self::camelize($id).'Service'); + } + + /** + * Gets a service. + * + * If a service is both defined through a setService() method and + * with a set*Service() method, the former has always precedence. + * + * @param string $id The service identifier + * @param int $invalidBehavior The behavior when the service does not exist + * + * @return object The associated service + * + * @throws \InvalidArgumentException if the service is not defined + * + * @see Reference + */ + public function getService($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE) + { + if (isset($this->services[$id])) + { + return $this->services[$id]; + } + + if (method_exists($this, $method = 'get'.self::camelize($id).'Service')) + { + return $this->$method(); + } + + if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) + { + throw new \InvalidArgumentException(sprintf('The service "%s" does not exist.', $id)); + } + else + { + return null; + } + } + + /** + * Gets all service ids. + * + * @return array An array of all defined service ids + */ + public function getServiceIds() + { + $ids = array(); + $r = new \ReflectionClass($this); + foreach ($r->getMethods() as $method) + { + if (preg_match('/^get(.+)Service$/', $name = $method->getName(), $match)) + { + $ids[] = self::underscore($match[1]); + } + } + + return array_merge($ids, array_keys($this->services)); + } + + /** + * Returns true if the parameter name is defined (implements the ArrayAccess interface). + * + * @param string The parameter name + * + * @return Boolean true if the parameter name is defined, false otherwise + */ + public function offsetExists($name) + { + return $this->hasParameter($name); + } + + /** + * Gets a service container parameter (implements the ArrayAccess interface). + * + * @param string The parameter name + * + * @return mixed The parameter value + */ + public function offsetGet($name) + { + return $this->getParameter($name); + } + + /** + * Sets a parameter (implements the ArrayAccess interface). + * + * @param string The parameter name + * @param mixed The parameter value + */ + public function offsetSet($name, $value) + { + $this->setParameter($name, $value); + } + + /** + * Removes a parameter (implements the ArrayAccess interface). + * + * @param string The parameter name + */ + public function offsetUnset($name) + { + unset($this->parameters[$name]); + } + + /** + * Returns true if the container has a service with the given identifier. + * + * @param string The service identifier + * + * @return Boolean true if the container has a service with the given identifier, false otherwise + */ + public function __isset($id) + { + return $this->hasService($id); + } + + /** + * Gets the service associated with the given identifier. + * + * @param string The service identifier + * + * @return mixed The service instance associated with the given identifier + */ + public function __get($id) + { + return $this->getService($id); + } + + /** + * Sets a service. + * + * @param string The service identifier + * @param mixed A service instance + */ + public function __set($id, $service) + { + $this->setService($id, $service); + } + + /** + * Removes a service by identifier. + * + * @param string The service identifier + */ + public function __unset($id) + { + throw new \LogicException('You can\'t unset a service.'); + } + + /** + * Resets the service identifiers array to the beginning (implements the Iterator interface). + */ + public function rewind() + { + $this->serviceIds = $this->getServiceIds(); + + $this->count = count($this->serviceIds); + } + + /** + * Gets the key associated with the current service (implements the Iterator interface). + * + * @return string The service identifier + */ + public function key() + { + return current($this->serviceIds); + } + + /** + * Returns the current service (implements the Iterator interface). + * + * @return mixed The service + */ + public function current() + { + return $this->getService(current($this->serviceIds)); + } + + /** + * Moves to the next service (implements the Iterator interface). + */ + public function next() + { + next($this->serviceIds); + + --$this->count; + } + + /** + * Returns true if the current service is valid (implements the Iterator interface). + * + * @return boolean The validity of the current service; true if it is valid + */ + public function valid() + { + return $this->count > 0; + } + + static public function camelize($id) + { + return preg_replace(array('/(^|_|-)+(.)/e', '/\.(.)/e'), array("strtoupper('\\2')", "'_'.strtoupper('\\1')"), $id); + } + + static public function underscore($id) + { + return strtolower(preg_replace(array('/_/', '/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('.', '\\1_\\2', '\\1_\\2'), $id)); + } +} diff --git a/src/Symfony/Components/DependencyInjection/ContainerInterface.php b/src/Symfony/Components/DependencyInjection/ContainerInterface.php new file mode 100644 index 000000000000..d0b2ac12e1bc --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/ContainerInterface.php @@ -0,0 +1,41 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * ContainerInterface is the interface implemented by service container classes. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id$ + */ +interface ContainerInterface +{ + public function setParameters(array $parameters); + + public function addParameters(array $parameters); + + public function getParameters(); + + public function getParameter($name); + + public function setParameter($name, $value); + + public function hasParameter($name); + + public function setService($id, $service); + + public function getService($id); + + public function hasService($name); +} diff --git a/src/Symfony/Components/DependencyInjection/Definition.php b/src/Symfony/Components/DependencyInjection/Definition.php new file mode 100644 index 000000000000..30709c67a413 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Definition.php @@ -0,0 +1,245 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Definition represents a service definition. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id: Definition.php 269 2009-03-26 20:39:16Z fabien $ + */ +class Definition +{ + protected + $class = null, + $file = null, + $constructor = null, + $shared = true, + $arguments = array(), + $calls = array(), + $configurator = null; + + /** + * Constructor. + * + * @param string $class The service class + * @param array $arguments An array of arguments to pass to the service constructor + */ + public function __construct($class, array $arguments = array()) + { + $this->class = $class; + $this->arguments = $arguments; + } + + /** + * Sets the constructor method. + * + * @param string $method The method name + * + * @return Definition The current instance + */ + public function setConstructor($method) + { + $this->constructor = $method; + + return $this; + } + + /** + * Gets the constructor method. + * + * @return Definition The constructor method name + */ + public function getConstructor() + { + return $this->constructor; + } + + /** + * Sets the service class. + * + * @param string $class The service class + * + * @return Definition The current instance + */ + public function setClass($class) + { + $this->class = $class; + + return $this; + } + + /** + * Sets the constructor method. + * + * @return string The service class + */ + public function getClass() + { + return $this->class; + } + + /** + * Sets the constructor arguments to pass to the service constructor. + * + * @param array $arguments An array of arguments + * + * @return Definition The current instance + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + + return $this; + } + + /** + * Adds a constructor argument to pass to the service constructor. + * + * @param mixed $argument An argument + * + * @return Definition The current instance + */ + public function addArgument($argument) + { + $this->arguments[] = $argument; + + return $this; + } + + /** + * Gets the constructor arguments to pass to the service constructor. + * + * @return array The array of arguments + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Sets the methods to call after service initialization. + * + * @param array $calls An array of method calls + * + * @return Definition The current instance + */ + public function setMethodCalls(array $calls = array()) + { + $this->calls = array(); + foreach ($calls as $call) + { + $this->addMethodCall($call[0], $call[1]); + } + + return $this; + } + + /** + * Adds a method to call after service initialization. + * + * @param string $method The method name to call + * @param array $arguments An array of arguments to pass to the method call + * + * @return Definition The current instance + */ + public function addMethodCall($method, array $arguments = array()) + { + $this->calls[] = array($method, $arguments); + + return $this; + } + + /** + * Gets the methods to call after service initialization. + * + * @return array An array of method calls + */ + public function getMethodCalls() + { + return $this->calls; + } + + /** + * Sets a file to require before creating the service. + * + * @param string $file A full pathname to include + * + * @return Definition The current instance + */ + public function setFile($file) + { + $this->file = $file; + + return $this; + } + + /** + * Gets the file to require before creating the service. + * + * @return string The full pathname to include + */ + public function getFile() + { + return $this->file; + } + + /** + * Sets if the service must be shared or not. + * + * @param Boolean $shared Whether the service must be shared or not + * + * @return Definition The current instance + */ + public function setShared($shared) + { + $this->shared = (Boolean) $shared; + + return $this; + } + + /** + * Returns true if the service must be shared. + * + * @return Boolean true if the service is shared, false otherwise + */ + public function isShared() + { + return $this->shared; + } + + /** + * Sets a configurator to call after the service is fully initialized. + * + * @param mixed $callable A PHP callable + * + * @return Definition The current instance + */ + public function setConfigurator($callable) + { + $this->configurator = $callable; + + return $this; + } + + /** + * Gets the configurator to call after the service is fully initialized. + * + * @return mixed The PHP callable to call + */ + public function getConfigurator() + { + return $this->configurator; + } +} diff --git a/src/Symfony/Components/DependencyInjection/Dumper/Dumper.php b/src/Symfony/Components/DependencyInjection/Dumper/Dumper.php new file mode 100644 index 000000000000..2f337f82ec33 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Dumper/Dumper.php @@ -0,0 +1,49 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Dumper is the abstract class for all built-in dumpers. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id$ + */ +abstract class Dumper implements DumperInterface +{ + protected $container; + + /** + * Constructor. + * + * @param Builder $container The service container to dump + */ + public function __construct(Builder $container) + { + $this->container = $container; + } + + /** + * Dumps the service container. + * + * @param array $options An array of options + * + * @return string The representation of the service container + */ + public function dump(array $options = array()) + { + throw new \LogicException('You must extend this abstract class and implement the dump() method.'); + } +} diff --git a/src/Symfony/Components/DependencyInjection/Dumper/DumperInterface.php b/src/Symfony/Components/DependencyInjection/Dumper/DumperInterface.php new file mode 100644 index 000000000000..860dcb64da11 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Dumper/DumperInterface.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * DumperInterface is the interface implemented by service container dumper classes. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id$ + */ +interface DumperInterface +{ + function dump(array $options = array()); +} diff --git a/src/Symfony/Components/DependencyInjection/Dumper/GraphvizDumper.php b/src/Symfony/Components/DependencyInjection/Dumper/GraphvizDumper.php new file mode 100644 index 000000000000..e336e3658512 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Dumper/GraphvizDumper.php @@ -0,0 +1,238 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * GraphvizDumper dumps a service container as a graphviz file. + * + * You can convert the generated dot file with the dot utility (http://www.graphviz.org/): + * + * dot -Tpng container.dot > foo.png + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class GraphvizDumper extends Dumper +{ + protected $nodes, $edges; + + /** + * Dumps the service container as a graphviz graph. + * + * Available options: + * + * * graph: The default options for the whole graph + * * node: The default options for nodes + * * edge: The default options for edges + * * node.instance: The default options for services that are defined directly by object instances + * * node.definition: The default options for services that are defined via service definition instances + * * node.missing: The default options for missing services + * + * @param array $options An array of options + * + * @return string The dot representation of the service container + */ + public function dump(array $options = array()) + { + $this->options = array( + 'graph' => array('ratio' => 'compress'), + 'node' => array('fontsize' => 11, 'fontname' => 'Arial', 'shape' => 'record'), + 'edge' => array('fontsize' => 9, 'fontname' => 'Arial', 'color' => 'grey', 'arrowhead' => 'open', 'arrowsize' => 0.5), + 'node.instance' => array('fillcolor' => '#9999ff', 'style' => 'filled'), + 'node.definition' => array('fillcolor' => '#eeeeee'), + 'node.missing' => array('fillcolor' => '#ff9999', 'style' => 'filled'), + ); + + foreach (array('graph', 'node', 'edge', 'node.instance', 'node.definition', 'node.missing') as $key) + { + if (isset($options[$key])) + { + $this->options[$key] = array_merge($this->options[$key], $options[$key]); + } + } + + $this->nodes = $this->findNodes(); + + $this->edges = array(); + foreach ($this->container->getDefinitions() as $id => $definition) + { + $this->edges[$id] = $this->findEdges($id, $definition->getArguments(), true, ''); + + foreach ($definition->getMethodCalls() as $call) + { + $this->edges[$id] = array_merge( + $this->edges[$id], + $this->findEdges($id, $call[1], false, $call[0].'()') + ); + } + } + + return $this->startDot().$this->addNodes().$this->addEdges().$this->endDot(); + } + + protected function addNodes() + { + $code = ''; + foreach ($this->nodes as $id => $node) + { + $aliases = $this->getAliases($id); + + $code .= sprintf(" node_%s [label=\"%s\\n%s\\n\", shape=%s%s];\n", $this->dotize($id), $id.($aliases ? ' ('.implode(', ', $aliases).')' : ''), $node['class'], $this->options['node']['shape'], $this->addAttributes($node['attributes'])); + } + + return $code; + } + + protected function addEdges() + { + $code = ''; + foreach ($this->edges as $id => $edges) + { + foreach ($edges as $edge) + { + $code .= sprintf(" node_%s -> node_%s [label=\"%s\" style=\"%s\"];\n", $this->dotize($id), $this->dotize($edge['to']), $edge['name'], $edge['required'] ? 'filled' : 'dashed'); + } + } + + return $code; + } + + protected function findEdges($id, $arguments, $required, $name) + { + $edges = array(); + foreach ($arguments as $argument) + { + if (is_object($argument) && $argument instanceof Parameter) + { + $argument = $this->container->hasParameter($argument) ? $this->container->getParameter($argument) : null; + } + elseif (is_string($argument) && preg_match('/^%([^%]+)%$/', $argument, $match)) + { + $argument = $this->container->hasParameter($match[1]) ? $this->container->getParameter($match[1]) : null; + } + + if ($argument instanceof Reference) + { + if (!$this->container->hasService((string) $argument)) + { + $this->nodes[(string) $argument] = array('name' => $name, 'required' => $required, 'class' => '', 'attributes' => $this->options['node.missing']); + } + + $edges[] = array('name' => $name, 'required' => $required, 'to' => $argument); + } + elseif (is_array($argument)) + { + $edges = array_merge($edges, $this->findEdges($id, $argument, $required, $name)); + } + } + + return $edges; + } + + protected function findNodes() + { + $nodes = array(); + + $container = clone $this->container; + + foreach ($container->getDefinitions() as $id => $definition) + { + $nodes[$id] = array('class' => str_replace('\\', '\\\\', $this->getValue($definition->getClass())), 'attributes' => array_merge($this->options['node.definition'], array('style' => $definition->isShared() ? 'filled' : 'dotted'))); + + $container->setDefinition($id, new Definition('stdClass')); + } + + foreach ($container as $id => $service) + { + if (in_array($id, array_keys($container->getAliases()))) + { + continue; + } + + if (!$container->hasDefinition($id)) + { + $nodes[$id] = array('class' => str_replace('\\', '\\\\', get_class($service)), 'attributes' => $this->options['node.instance']); + } + } + + return $nodes; + } + + protected function getValue($value, $default = '') + { + return Builder::resolveValue($value, $this->container->getParameters()); + } + + protected function startDot() + { + $parameters = var_export($this->container->getParameters(), true); + + return sprintf("digraph sc {\n %s\n node [%s];\n edge [%s];\n\n", + $this->addOptions($this->options['graph']), + $this->addOptions($this->options['node']), + $this->addOptions($this->options['edge']) + ); + } + + protected function endDot() + { + return "}\n"; + } + + protected function addAttributes($attributes) + { + $code = array(); + foreach ($attributes as $k => $v) + { + $code[] = sprintf('%s="%s"', $k, $v); + } + + return $code ? ', '.implode(', ', $code) : ''; + } + + protected function addOptions($options) + { + $code = array(); + foreach ($options as $k => $v) + { + $code[] = sprintf('%s="%s"', $k, $v); + } + + return implode(' ', $code); + } + + protected function dotize($id) + { + return strtolower(preg_replace('/[^\w]/i', '_', $id)); + } + + protected function getAliases($id) + { + $aliases = array(); + foreach ($this->container->getAliases() as $alias => $origin) + { + if ($id == $origin) + { + $aliases[] = $alias; + } + } + + return $aliases; + } +} diff --git a/src/Symfony/Components/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Components/DependencyInjection/Dumper/PhpDumper.php new file mode 100644 index 000000000000..9e7d9b2bca76 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Dumper/PhpDumper.php @@ -0,0 +1,460 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * PhpDumper dumps a service container as a PHP class. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class PhpDumper extends Dumper +{ + /** + * Dumps the service container as a PHP class. + * + * Available options: + * + * * class: The class name + * * base_class: The base class name + * + * @param array $options An array of options + * + * @return string A PHP class representing of the service container + */ + public function dump(array $options = array()) + { + $options = array_merge(array( + 'class' => 'ProjectServiceContainer', + 'base_class' => 'Container', + ), $options); + + return + $this->startClass($options['class'], $options['base_class']). + $this->addConstructor(). + $this->addServices(). + $this->addDefaultParametersMethod(). + $this->endClass() + ; + } + + protected function addServiceInclude($id, $definition) + { + if (null !== $definition->getFile()) + { + return sprintf(" require_once %s;\n\n", $this->dumpValue($definition->getFile())); + } + } + + protected function addServiceShared($id, $definition) + { + if ($definition->isShared()) + { + return <<shared['$id'])) return \$this->shared['$id']; + + +EOF; + } + } + + protected function addServiceReturn($id, $definition) + { + if ($definition->isShared()) + { + return <<shared['$id'] = \$instance; + } + +EOF; + } + else + { + return <<dumpValue($definition->getClass()); + + $arguments = array(); + foreach ($definition->getArguments() as $value) + { + $arguments[] = $this->dumpValue($value); + } + + if (null !== $definition->getConstructor()) + { + return sprintf(" \$instance = call_user_func(array(%s, '%s')%s);\n", $class, $definition->getConstructor(), $arguments ? ', '.implode(', ', $arguments) : ''); + } + else + { + if ($class != "'".$definition->getClass()."'") + { + return sprintf(" \$class = %s;\n \$instance = new \$class(%s);\n", $class, implode(', ', $arguments)); + } + else + { + return sprintf(" \$instance = new %s(%s);\n", $definition->getClass(), implode(', ', $arguments)); + } + } + } + + protected function addServiceMethodCalls($id, $definition) + { + $calls = ''; + foreach ($definition->getMethodCalls() as $call) + { + $arguments = array(); + foreach ($call[1] as $value) + { + $arguments[] = $this->dumpValue($value); + } + + $calls .= $this->wrapServiceConditionals($call[1], sprintf(" \$instance->%s(%s);\n", $call[0], implode(', ', $arguments))); + } + + return $calls; + } + + protected function addServiceConfigurator($id, $definition) + { + if (!$callable = $definition->getConfigurator()) + { + return ''; + } + + if (is_array($callable)) + { + if (is_object($callable[0]) && $callable[0] instanceof Reference) + { + return sprintf(" %s->%s(\$instance);\n", $this->getServiceCall((string) $callable[0]), $callable[1]); + } + else + { + return sprintf(" call_user_func(array(%s, '%s'), \$instance);\n", $this->dumpValue($callable[0]), $callable[1]); + } + } + else + { + return sprintf(" %s(\$instance);\n", $callable); + } + } + + protected function addService($id, $definition) + { + $name = Container::camelize($id); + $class = $definition->getClass(); + $type = 0 === strpos($class, '%') ? 'Object' : $class; + + $doc = ''; + if ($definition->isShared()) + { + $doc = <<addServiceInclude($id, $definition). + $this->addServiceShared($id, $definition). + $this->addServiceInstance($id, $definition). + $this->addServiceMethodCalls($id, $definition). + $this->addServiceConfigurator($id, $definition). + $this->addServiceReturn($id, $definition) + ; + + return $code; + } + + protected function addServiceAlias($alias, $id) + { + $name = Container::camelize($alias); + $type = 'Object'; + + if ($this->container->hasDefinition($id)) + { + $class = $this->container->getDefinition($id)->getClass(); + $type = 0 === strpos($class, '%') ? 'Object' : $class; + } + + return <<getServiceCall($id)}; + } + +EOF; + } + + protected function addServices() + { + $code = ''; + foreach ($this->container->getDefinitions() as $id => $definition) + { + $code .= $this->addService($id, $definition); + } + + foreach ($this->container->getAliases() as $alias => $id) + { + $code .= $this->addServiceAlias($alias, $id); + } + + return $code; + } + + protected function startClass($class, $baseClass) + { + $properties = array(); + foreach ($this->container->getDefinitions() as $id => $definition) + { + $type = 0 === strpos($definition->getClass(), '%') ? 'Object' : $definition->getClass(); + $properties[] = sprintf(' * @property %s $%s', $type, $id); + } + + foreach ($this->container->getAliases() as $alias => $id) + { + $type = 'Object'; + if ($this->container->hasDefinition($id)) + { + $sclass = $this->container->getDefinition($id)->getClass(); + $type = 0 === strpos($sclass, '%') ? 'Object' : $sclass; + } + + $properties[] = sprintf(' * @property %s $%s', $type, $alias); + } + $properties = implode("\n", $properties); + if ($properties) + { + $properties = "\n *\n".$properties; + } + + return <<container->getParameters()) + { + return ''; + } + + return <<getDefaultParameters()); + } + +EOF; + } + + protected function addDefaultParametersMethod() + { + if (!$this->container->getParameters()) + { + return ''; + } + + $parameters = $this->exportParameters($this->container->getParameters()); + + return << $value) + { + if (is_array($value)) + { + $value = $this->exportParameters($value, $indent + 2); + } + elseif ($value instanceof Reference) + { + $value = sprintf("new Reference('%s')", $value); + } + else + { + $value = var_export($value, true); + } + + $php[] = sprintf('%s%s => %s,', str_repeat(' ', $indent), var_export($key, true), $value); + } + + return sprintf("array(\n%s\n%s)", implode("\n", $php), str_repeat(' ', $indent - 2)); + } + + protected function endClass() + { + return <<hasService('%s')", $service); + } + + // re-indent the wrapped code + $code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code))); + + return sprintf(" if (%s)\n {\n%s }\n", implode(' && ', $conditions), $code); + } + + protected function dumpValue($value) + { + if (is_array($value)) + { + $code = array(); + foreach ($value as $k => $v) + { + $code[] = sprintf("%s => %s", $this->dumpValue($k), $this->dumpValue($v)); + } + + return sprintf("array(%s)", implode(', ', $code)); + } + elseif (is_object($value) && $value instanceof Reference) + { + return $this->getServiceCall((string) $value, $value); + } + elseif (is_object($value) && $value instanceof Parameter) + { + return sprintf("\$this->getParameter('%s')", strtolower($value)); + } + elseif (is_string($value)) + { + if (preg_match('/^%([^%]+)%$/', $value, $match)) + { + // we do this to deal with non string values (boolean, integer, ...) + // the preg_replace_callback converts them to strings + return sprintf("\$this->getParameter('%s')", strtolower($match[1])); + } + else + { + $replaceParameters = function ($match) + { + return sprintf("'.\$this->getParameter('%s').'", strtolower($match[2])); + }; + + $code = str_replace('%%', '%', preg_replace_callback('/(?getInvalidBehavior()) + { + return sprintf('$this->getService(\'%s\', Container::NULL_ON_INVALID_REFERENCE)', $id); + } + else + { + return sprintf('$this->getService(\'%s\')', $id); + } + } +} diff --git a/src/Symfony/Components/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Components/DependencyInjection/Dumper/XmlDumper.php new file mode 100644 index 000000000000..96b3d0b565d5 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Dumper/XmlDumper.php @@ -0,0 +1,232 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * XmlDumper dumps a service container as an XML string. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class XmlDumper extends Dumper +{ + /** + * Dumps the service container as an XML string. + * + * @param array $options An array of options + * + * @return string An xml string representing of the service container + */ + public function dump(array $options = array()) + { + return $this->startXml().$this->addParameters().$this->addServices().$this->endXml(); + } + + protected function addParameters() + { + if (!$this->container->getParameters()) + { + return ''; + } + + return sprintf(" \n%s \n", $this->convertParameters($this->escape($this->container->getParameters()), 'parameter', 4)); + } + + protected function addService($id, $definition) + { + $code = sprintf(" \n", + $id, + $definition->getClass(), + $definition->getConstructor() ? sprintf(' constructor="%s"', $definition->getConstructor()) : '', + !$definition->isShared() ? ' shared="false"' : '' + ); + + if ($definition->getFile()) + { + $code .= sprintf(" %s\n", $definition->getFile()); + } + + if ($definition->getArguments()) + { + $code .= $this->convertParameters($definition->getArguments(), 'argument', 6); + } + + foreach ($definition->getMethodCalls() as $call) + { + if (count($call[1])) + { + $code .= sprintf(" \n%s \n", $call[0], $this->convertParameters($call[1], 'argument', 8)); + } + else + { + $code .= sprintf(" \n", $call[0]); + } + } + + if ($callable = $definition->getConfigurator()) + { + if (is_array($callable)) + { + if (is_object($callable[0]) && $callable[0] instanceof Reference) + { + $code .= sprintf(" \n", $callable[0], $callable[1]); + } + else + { + $code .= sprintf(" \n", $callable[0], $callable[1]); + } + } + else + { + $code .= sprintf(" \n", $callable); + } + } + + $code .= " \n"; + + return $code; + } + + protected function addServiceAlias($alias, $id) + { + return sprintf(" \n", $alias, $id); + } + + protected function addServices() + { + if (!$this->container->getDefinitions()) + { + return ''; + } + + $code = ''; + foreach ($this->container->getDefinitions() as $id => $definition) + { + $code .= $this->addService($id, $definition); + } + + foreach ($this->container->getAliases() as $alias => $id) + { + $code .= $this->addServiceAlias($alias, $id); + } + + return sprintf(" \n%s \n", $code); + } + + protected function convertParameters($parameters, $type='parameter', $depth = 2) + { + $white = str_repeat(' ', $depth); + $xml = ''; + $withKeys = array_keys($parameters) !== range(0, count($parameters) - 1); + foreach ($parameters as $key => $value) + { + $attributes = ''; + $key = $withKeys ? sprintf(' key="%s"', $key) : ''; + if (is_array($value)) + { + $value = "\n".$this->convertParameters($value, $type, $depth + 2).$white; + $attributes = ' type="collection"'; + } + + if (is_object($value) && $value instanceof Reference) + { + $xml .= sprintf("%s<%s%s type=\"service\" id=\"%s\" %s/>\n", $white, $type, $key, (string) $value, $this->getXmlInvalidBehavior($value)); + } + else + { + if (in_array($value, array('null', 'true', 'false'), true)) + { + $attributes = ' type="string"'; + } + + $xml .= sprintf("%s<%s%s%s>%s\n", $white, $type, $key, $attributes, self::phpToXml($value), $type); + } + } + + return $xml; + } + + protected function startXml() + { + return << + + + +EOF; + } + + protected function endXml() + { + return "\n"; + } + + protected function getXmlInvalidBehavior(Reference $reference) + { + switch ($reference->getInvalidBehavior()) + { + case Container::NULL_ON_INVALID_REFERENCE: + return 'on-invalid="null" '; + case Container::IGNORE_ON_INVALID_REFERENCE: + return 'on-invalid="ignore" '; + default: + return ''; + } + } + + protected function escape($arguments) + { + $args = array(); + foreach ($arguments as $k => $v) + { + if (is_array($v)) + { + $args[$k] = $this->escape($v); + } + elseif (is_string($v)) + { + $args[$k] = str_replace('%', '%%', $v); + } + else + { + $args[$k] = $v; + } + } + + return $args; + } + + static public function phpToXml($value) + { + switch (true) + { + case null === $value: + return 'null'; + case true === $value: + return 'true'; + case false === $value: + return 'false'; + case is_object($value) && $value instanceof Parameter: + return '%'.$value.'%'; + case is_object($value) || is_resource($value): + throw new \RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); + default: + return $value; + } + } +} diff --git a/src/Symfony/Components/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Components/DependencyInjection/Dumper/YamlDumper.php new file mode 100644 index 000000000000..bc4e21be4537 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Dumper/YamlDumper.php @@ -0,0 +1,215 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * YamlDumper dumps a service container as a YAML string. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class YamlDumper extends Dumper +{ + /** + * Dumps the service container as an YAML string. + * + * @param array $options An array of options + * + * @return string A YAML string representing of the service container + */ + public function dump(array $options = array()) + { + return $this->addParameters()."\n".$this->addServices(); + } + + protected function addService($id, $definition) + { + $code = " $id:\n"; + $code .= sprintf(" class: %s\n", $definition->getClass()); + + if ($definition->getFile()) + { + $code .= sprintf(" file: %s\n", $definition->getFile()); + } + + if ($definition->getConstructor()) + { + $code .= sprintf(" constructor: %s\n", $definition->getConstructor()); + } + + if ($definition->getArguments()) + { + $code .= sprintf(" arguments: %s\n", YAML::dump($this->dumpValue($definition->getArguments()), 0)); + } + + if ($definition->getMethodCalls()) + { + $code .= sprintf(" calls:\n %s\n", str_replace("\n", "\n ", YAML::dump($this->dumpValue($definition->getMethodCalls()), 1))); + } + + if (!$definition->isShared()) + { + $code .= " shared: false\n"; + } + + if ($callable = $definition->getConfigurator()) + { + if (is_array($callable)) + { + if (is_object($callable[0]) && $callable[0] instanceof Reference) + { + $callable = array($this->getServiceCall((string) $callable[0], $callable[0]), $callable[1]); + } + else + { + $callable = array($callable[0], $callable[1]); + } + } + + $code .= sprintf(" configurator: %s\n", YAML::dump($callable, 0)); + } + + return $code; + } + + protected function addServiceAlias($alias, $id) + { + return sprintf(" %s: @%s\n", $alias, $id); + } + + protected function addServices() + { + if (!$this->container->getDefinitions()) + { + return ''; + } + + $code = "services:\n"; + foreach ($this->container->getDefinitions() as $id => $definition) + { + $code .= $this->addService($id, $definition); + } + + foreach ($this->container->getAliases() as $alias => $id) + { + $code .= $this->addServiceAlias($alias, $id); + } + + return $code; + } + + protected function addParameters() + { + if (!$this->container->getParameters()) + { + return ''; + } + + return YAML::dump(array('parameters' => $this->prepareParameters($this->container->getParameters())), 2); + } + + protected function dumpValue($value) + { + if (is_array($value)) + { + $code = array(); + foreach ($value as $k => $v) + { + $code[$k] = $this->dumpValue($v); + } + + return $code; + } + elseif (is_object($value) && $value instanceof Reference) + { + return $this->getServiceCall((string) $value, $value); + } + elseif (is_object($value) && $value instanceof Parameter) + { + return $this->getParameterCall((string) $value); + } + elseif (is_object($value) || is_resource($value)) + { + throw new \RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); + } + else + { + return $value; + } + } + + protected function getServiceCall($id, Reference $reference = null) + { + if (null !== $reference && Container::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) + { + return sprintf('@@%s', $id); + } + else + { + return sprintf('@%s', $id); + } + } + + protected function getParameterCall($id) + { + return sprintf('%%%s%%', $id); + } + + protected function prepareParameters($parameters) + { + $filtered = array(); + foreach ($parameters as $key => $value) + { + if (is_array($value)) + { + $value = $this->prepareParameters($value); + } + elseif ($value instanceof Reference) + { + $value = '@'.$value; + } + + $filtered[$key] = $value; + } + + return $this->escape($filtered); + } + + protected function escape($arguments) + { + $args = array(); + foreach ($arguments as $k => $v) + { + if (is_array($v)) + { + $args[$k] = $this->escape($v); + } + elseif (is_string($v)) + { + $args[$k] = str_replace('%', '%%', $v); + } + else + { + $args[$k] = $v; + } + } + + return $args; + } +} diff --git a/src/Symfony/Components/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Components/DependencyInjection/Loader/FileLoader.php new file mode 100644 index 000000000000..8e555784f414 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Loader/FileLoader.php @@ -0,0 +1,80 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * FileLoader is the abstract class used by all built-in loaders that are file based. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id$ + */ +abstract class FileLoader extends Loader +{ + protected + $paths = array(); + + /** + * Constructor. + * + * @param string|array $paths A path or an array of paths where to look for resources + */ + public function __construct($paths = array()) + { + if (!is_array($paths)) + { + $paths = array($paths); + } + + $this->paths = $paths; + } + + protected function getAbsolutePath($file, $currentPath = null) + { + if (self::isAbsolutePath($file)) + { + return $file; + } + else if (null !== $currentPath && file_exists($currentPath.DIRECTORY_SEPARATOR.$file)) + { + return $currentPath.DIRECTORY_SEPARATOR.$file; + } + else + { + foreach ($this->paths as $path) + { + if (file_exists($path.DIRECTORY_SEPARATOR.$file)) + { + return $path.DIRECTORY_SEPARATOR.$file; + } + } + } + + return $file; + } + + static protected function isAbsolutePath($file) + { + if ($file[0] == '/' || $file[0] == '\\' || + (strlen($file) > 3 && ctype_alpha($file[0]) && + $file[1] == ':' && + ($file[2] == '\\' || $file[2] == '/') + ) + ) + { + return true; + } + + return false; + } +} diff --git a/src/Symfony/Components/DependencyInjection/Loader/IniFileLoader.php b/src/Symfony/Components/DependencyInjection/Loader/IniFileLoader.php new file mode 100644 index 000000000000..24ac3a5a41d0 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Loader/IniFileLoader.php @@ -0,0 +1,60 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * IniFileLoader loads parameters from INI files. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class IniFileLoader extends FileLoader +{ + public function load($files) + { + if (!is_array($files)) + { + $files = array($files); + } + + $configuration = new BuilderConfiguration(); + + foreach ($files as $file) + { + $path = $this->getAbsolutePath($file); + if (!file_exists($path)) + { + throw new \InvalidArgumentException(sprintf('The %s file does not exist.', $file)); + } + + $result = parse_ini_file($path, true); + if (false === $result || array() === $result) + { + throw new \InvalidArgumentException(sprintf('The %s file is not valid.', $file)); + } + + if (isset($result['parameters']) && is_array($result['parameters'])) + { + foreach ($result['parameters'] as $key => $value) + { + $configuration->setParameter(strtolower($key), $value); + } + } + } + + return $configuration; + } +} diff --git a/src/Symfony/Components/DependencyInjection/Loader/Loader.php b/src/Symfony/Components/DependencyInjection/Loader/Loader.php new file mode 100644 index 000000000000..df8f9c7531af --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Loader/Loader.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Loader is the abstract class used by all built-in loaders. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id: Loader.php 267 2009-03-26 19:56:18Z fabien $ + */ +abstract class Loader implements LoaderInterface +{ + static protected $extensions = array(); + + static public function registerExtension(LoaderExtensionInterface $extension) + { + static::$extensions[$extension->getNamespace()] = $extension; + } + + static public function getExtension($name) + { + return isset(static::$extensions[$name]) ? static::$extensions[$name] : null; + } +} diff --git a/src/Symfony/Components/DependencyInjection/Loader/LoaderExtension.php b/src/Symfony/Components/DependencyInjection/Loader/LoaderExtension.php new file mode 100644 index 000000000000..f6435048e28c --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Loader/LoaderExtension.php @@ -0,0 +1,41 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * LoaderExtension is a helper class that helps organize extensions better. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id$ + */ +abstract class LoaderExtension implements LoaderExtensionInterface +{ + /** + * Loads a specific configuration. + * + * @param string The tag name + * @param array An array of configuration values + * + * @return BuilderConfiguration A BuilderConfiguration instance + */ + public function load($tag, array $config) + { + if (!method_exists($this, $method = $tag.'Load')) + { + throw new \InvalidArgumentException(sprintf('The tag "%s" is not defined in the "%s" extension.', $tag, $this->getNamespace())); + } + + return $this->$method($config); + } +} diff --git a/src/Symfony/Components/DependencyInjection/Loader/LoaderExtensionInterface.php b/src/Symfony/Components/DependencyInjection/Loader/LoaderExtensionInterface.php new file mode 100644 index 000000000000..d9076202d966 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Loader/LoaderExtensionInterface.php @@ -0,0 +1,40 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * LoaderExtensionInterface is the interface implemented by loader extension classes. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id$ + */ +interface LoaderExtensionInterface +{ + /** + * Loads a specific configuration. + * + * @param string The tag name + * @param array An array of configuration values + * + * @return BuilderConfiguration A BuilderConfiguration instance + */ + public function load($tag, array $config); + + /** + * Returns the namespace to be used for this extension. + * + * @return string The namespace + */ + public function getNamespace(); +} diff --git a/src/Symfony/Components/DependencyInjection/Loader/LoaderInterface.php b/src/Symfony/Components/DependencyInjection/Loader/LoaderInterface.php new file mode 100644 index 000000000000..36da1d4025c0 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Loader/LoaderInterface.php @@ -0,0 +1,67 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * LoaderInterface is the interface implemented by all loader classes. + * + * $loader = new XXXLoader(); + * $config = $loader->load('resource_name'); + * + * $container = new Builder(); + * $container->merge($config); + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id$ + */ +interface LoaderInterface +{ + /** + * Loads a resource. + * + * A resource can be anything that can be converted to a + * BuilderConfiguration instance. + * + * Some loaders support an array of resources as an argument to the + * constructor. + * + * If multiple resources are loaded, the services and parameters are merged. + * + * Remember that services and parameters are simple key/pair stores. + * + * When overriding a value, the old one is totally replaced, even if it is + * a "complex" value (an array for instance): + * + *
+   *   file1.xml
+   *   
+   *     true
+   *     false
+   *   
+   *
+   *   file2.xml
+   *   foo
+   * 
+ * + * If you load file1.xml and file2.xml in this order, the value of complex + * will be "foo". + * + * @param mixed $resource The resource path + * + * @return BuilderConfiguration A BuilderConfiguration instance + */ + function load($resource); + + static function registerExtension(LoaderExtensionInterface $extension); +} diff --git a/src/Symfony/Components/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Components/DependencyInjection/Loader/XmlFileLoader.php new file mode 100644 index 000000000000..8256a8bba042 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Loader/XmlFileLoader.php @@ -0,0 +1,369 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * XmlFileLoader loads XML files service definitions. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id: LoaderFileXml.php 267 2009-03-26 19:56:18Z fabien $ + */ +class XmlFileLoader extends FileLoader +{ + /** + * Loads an array of XML files. + * + * @param array $files An array of XML files + * + * @return array An array of definitions and parameters + */ + public function load($files) + { + if (!is_array($files)) + { + $files = array($files); + } + + return $this->parse($this->getFilesAsXml($files)); + } + + protected function parse(array $xmls) + { + $configuration = new BuilderConfiguration(); + + foreach ($xmls as $file => $xml) + { + // anonymous services + $xml = $this->processAnonymousServices($configuration, $xml, $file); + + // imports + $this->parseImports($configuration, $xml, $file); + + // parameters + $this->parseParameters($configuration, $xml, $file); + + // services + $this->parseDefinitions($configuration, $xml, $file); + + // extensions + $this->loadFromExtensions($configuration, $xml); + } + + return $configuration; + } + + protected function parseParameters(BuilderConfiguration $configuration, $xml, $file) + { + if (!$xml->parameters) + { + return array(); + } + + $configuration->addParameters($xml->parameters->getArgumentsAsPhp('parameter')); + } + + protected function parseImports(BuilderConfiguration $configuration, $xml, $file) + { + if (!$xml->imports) + { + return; + } + + foreach ($xml->imports->import as $import) + { + $configuration->merge($this->parseImport($import, $file)); + } + } + + protected function parseImport($import, $file) + { + if (isset($import['class']) && $import['class'] != get_class($this)) + { + $class = (string) $import['class']; + $loader = new $class($this->paths); + } + else + { + $loader = $this; + } + + $importedFile = $this->getAbsolutePath((string) $import['resource'], dirname($file)); + + return $loader->load($importedFile); + } + + protected function parseDefinitions(BuilderConfiguration $configuration, $xml, $file) + { + if (!$xml->services) + { + return array(); + } + + $definitions = array(); + foreach ($xml->services->service as $service) + { + $this->parseDefinition($configuration, (string) $service['id'], $service, $file); + } + } + + protected function parseDefinition(BuilderConfiguration $configuration, $id, $service, $file) + { + if ((string) $service['alias']) + { + $configuration->setAlias($id, (string) $service['alias']); + + return; + } + + $definition = new Definition((string) $service['class']); + + foreach (array('shared', 'constructor') as $key) + { + $method = 'set'.ucfirst($key); + if (isset($service[$key])) + { + $definition->$method((string) $service->getAttributeAsPhp($key)); + } + } + + if ($service->file) + { + $definition->setFile((string) $service->file); + } + + $definition->setArguments($service->getArgumentsAsPhp('argument')); + + if (isset($service->configurator)) + { + if (isset($service->configurator['function'])) + { + $definition->setConfigurator((string) $service->configurator['function']); + } + else + { + if (isset($service->configurator['service'])) + { + $class = new Reference((string) $service->configurator['service']); + } + else + { + $class = (string) $service->configurator['class']; + } + + $definition->setConfigurator(array($class, (string) $service->configurator['method'])); + } + } + + foreach ($service->call as $call) + { + $definition->addMethodCall((string) $call['method'], $call->getArgumentsAsPhp('argument')); + } + + $configuration->setDefinition($id, $definition); + } + + protected function getFilesAsXml(array $files) + { + $xmls = array(); + foreach ($files as $file) + { + $path = $this->getAbsolutePath($file); + + if (!file_exists($path)) + { + throw new \InvalidArgumentException(sprintf('The service file "%s" does not exist (in: %s).', $file, implode(', ', $this->paths))); + } + + $dom = new \DOMDocument(); + libxml_use_internal_errors(true); + if (!$dom->load(realpath($path), LIBXML_COMPACT)) + { + throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors())); + } + libxml_use_internal_errors(false); + $this->validate($dom, $path); + + $xmls[$path] = simplexml_import_dom($dom, 'Symfony\Components\DependencyInjection\SimpleXMLElement'); + } + + return $xmls; + } + + protected function processAnonymousServices(BuilderConfiguration $configuration, $xml, $file) + { + $definitions = array(); + $count = 0; + + // find anonymous service definitions + $xml->registerXPathNamespace('container', 'http://symfony-project.org/2.0/container'); + $nodes = $xml->xpath('//container:argument[@type="service"][not(@id)]'); + foreach ($nodes as $node) + { + // give it a unique names + $node['id'] = sprintf('_%s_%d', md5($file), ++$count); + + $definitions[(string) $node['id']] = array($node->service, $file); + $node->service['id'] = (string) $node['id']; + } + + // resolve definitions + krsort($definitions); + foreach ($definitions as $id => $def) + { + $this->parseDefinition($configuration, $id, $def[0], $def[1]); + + $oNode = dom_import_simplexml($def[0]); + $oNode->parentNode->removeChild($oNode); + } + + return $xml; + } + + protected function validate($dom, $file) + { + libxml_use_internal_errors(true); + if (!$dom->schemaValidate(__DIR__.'/services.xsd')) + { + throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors())); + } + libxml_use_internal_errors(false); + + // validate extensions + foreach ($dom->documentElement->childNodes as $node) + { + if (!$node instanceof \DOMElement || in_array($node->tagName, array('imports', 'parameters', 'services'))) + { + continue; + } + + // can it be handled by an extension? + if (false !== strpos($node->tagName, ':')) + { + list($namespace, $tag) = explode(':', $node->tagName); + if (!static::getExtension($namespace)) + { + throw new \InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in %s).', $node->tagName, $file)); + } + + continue; + } + + throw new \InvalidArgumentException(sprintf('The "%s" tag is not valid (in %s).', $node->tagName, $file)); + } + } + + protected function getXmlErrors() + { + $errors = array(); + foreach (libxml_get_errors() as $error) + { + $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)', + LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', + $error->code, + trim($error->message), + $error->file ? $error->file : 'n/a', + $error->line, + $error->column + ); + } + + libxml_clear_errors(); + + return $errors; + } + + protected function loadFromExtensions(BuilderConfiguration $configuration, $xml) + { + foreach (dom_import_simplexml($xml)->getElementsByTagNameNS('*', '*') as $element) + { + if (!$element->prefix) + { + continue; + } + + $values = static::convertDomElementToArray($element); + $config = $this->getExtension($element->prefix)->load($element->localName, is_array($values) ? $values : array($values)); + + $configuration->merge($config); + } + } + + /** + * Converts a \DomElement object to a PHP array. + * + * The following rules applies during the conversion: + * + * * Each tag is converted to a key value or an array + * if there is more than one "value" + * + * * The content of a tag is set under a "value" key (bar) + * if the tag also has some nested tags + * + * * The attributes are converted to keys () + * + * * The nested-tags are converted to keys (bar) + * + * @param \DomElement $element A \DomElement instance + * + * @return array A PHP array + */ + static public function convertDomElementToArray(\DomElement $element) + { + $empty = true; + $config = array(); + foreach ($element->attributes as $name => $node) + { + $config[$name] = SimpleXMLElement::phpize($node->value); + $empty = false; + } + + $nodeValue = false; + foreach ($element->childNodes as $node) + { + if ($node instanceof \DOMText) + { + if (trim($node->nodeValue)) + { + $nodeValue = trim($node->nodeValue); + $empty = false; + } + } + elseif (!$node instanceof \DOMComment) + { + $config[$node->tagName] = static::convertDomElementToArray($node); + $empty = false; + } + } + + if (false !== $nodeValue) + { + $value = SimpleXMLElement::phpize($nodeValue); + if (count($config)) + { + $config['value'] = $value; + } + else + { + $config = $value; + } + } + + return !$empty ? $config : null; + } +} diff --git a/src/Symfony/Components/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Components/DependencyInjection/Loader/YamlFileLoader.php new file mode 100644 index 000000000000..c5b25e7d9e2f --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Loader/YamlFileLoader.php @@ -0,0 +1,271 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * YamlFileLoader loads YAML files service definitions. + * + * The YAML format does not support anonymous services (cf. the XML loader). + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id: sfServiceContainerLoaderFileYaml.php 269 2009-03-26 20:39:16Z fabien $ + */ +class YamlFileLoader extends FileLoader +{ + /** + * Loads an array of XML files. + * + * @param array $files An array of XML files + * + * @return array An array of definitions and parameters + */ + public function load($files) + { + if (!is_array($files)) + { + $files = array($files); + } + + return $this->parse($this->getFilesAsArray($files)); + } + + protected function parse($data) + { + $configuration = new BuilderConfiguration(); + + foreach ($data as $file => $content) + { + if (!$content) + { + continue; + } + + // imports + $this->parseImports($configuration, $content, $file); + + // parameters + if (isset($content['parameters'])) + { + foreach ($content['parameters'] as $key => $value) + { + $configuration->setParameter(strtolower($key), $this->resolveServices($value)); + } + } + + // services + $this->parseDefinitions($configuration, $content, $file); + + // extensions + $this->loadFromExtensions($configuration, $content); + } + + return $configuration; + } + + protected function parseImports(BuilderConfiguration $configuration, $content, $file) + { + if (!isset($content['imports'])) + { + return; + } + + foreach ($content['imports'] as $import) + { + $configuration->merge($this->parseImport($import, $file)); + } + } + + protected function parseImport($import, $file) + { + if (isset($import['class']) && $import['class'] != get_class($this)) + { + $class = $import['class']; + $loader = new $class($this->paths); + } + else + { + $loader = $this; + } + + $importedFile = $this->getAbsolutePath($import['resource'], dirname($file)); + + return $loader->load($importedFile); + } + + protected function parseDefinitions(BuilderConfiguration $configuration, $content, $file) + { + if (!isset($content['services'])) + { + return; + } + + foreach ($content['services'] as $id => $service) + { + $this->parseDefinition($configuration, $id, $service, $file); + } + } + + protected function parseDefinition(BuilderConfiguration $configuration, $id, $service, $file) + { + if (is_string($service) && 0 === strpos($service, '@')) + { + $configuration->setAlias($id, substr($service, 1)); + + return; + } + + $definition = new Definition($service['class']); + + if (isset($service['shared'])) + { + $definition->setShared($service['shared']); + } + + if (isset($service['constructor'])) + { + $definition->setConstructor($service['constructor']); + } + + if (isset($service['file'])) + { + $definition->setFile($service['file']); + } + + if (isset($service['arguments'])) + { + $definition->setArguments($this->resolveServices($service['arguments'])); + } + + if (isset($service['configurator'])) + { + if (is_string($service['configurator'])) + { + $definition->setConfigurator($service['configurator']); + } + else + { + $definition->setConfigurator(array($this->resolveServices($service['configurator'][0]), $service['configurator'][1])); + } + } + + if (isset($service['calls'])) + { + foreach ($service['calls'] as $call) + { + $definition->addMethodCall($call[0], $this->resolveServices($call[1])); + } + } + + $configuration->setDefinition($id, $definition); + } + + protected function getFilesAsArray(array $files) + { + $yamls = array(); + foreach ($files as $file) + { + $path = $this->getAbsolutePath($file); + + if (!file_exists($path)) + { + throw new \InvalidArgumentException(sprintf('The service file "%s" does not exist (in: %s).', $file, implode(', ', $this->paths))); + } + + $yamls[$path] = $this->validate(YAML::load($path), $path); + } + + return $yamls; + } + + protected function validate($content, $file) + { + if (null === $content) + { + return $content; + } + + if (!is_array($content)) + { + throw new \InvalidArgumentException(sprintf('The service file "%s" is not valid.', $file)); + } + + foreach (array_keys($content) as $key) + { + if (in_array($key, array('imports', 'parameters', 'services'))) + { + continue; + } + + // can it be handled by an extension? + if (false !== strpos($key, '.')) + { + list($namespace, $tag) = explode('.', $key); + if (!static::getExtension($namespace)) + { + throw new \InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in %s).', $key, $file)); + } + + continue; + } + + throw new \InvalidArgumentException(sprintf('The "%s" tag is not valid (in %s).', $key, $file)); + } + + return $content; + } + + protected function resolveServices($value) + { + if (is_array($value)) + { + $value = array_map(array($this, 'resolveServices'), $value); + } + else if (is_string($value) && 0 === strpos($value, '@@')) + { + $value = new Reference(substr($value, 2), Container::IGNORE_ON_INVALID_REFERENCE); + } + else if (is_string($value) && 0 === strpos($value, '@')) + { + $value = new Reference(substr($value, 1)); + } + + return $value; + } + + protected function loadFromExtensions(BuilderConfiguration $configuration, $content) + { + foreach ($content as $key => $config) + { + if (in_array($key, array('imports', 'parameters', 'services'))) + { + continue; + } + + list($namespace, $tag) = explode('.', $key); + + if (!is_array($config)) + { + $config = array(); + } + + $configuration->merge(static::getExtension($namespace)->load($tag, $config)); + } + } +} diff --git a/src/Symfony/Components/DependencyInjection/Loader/services.xsd b/src/Symfony/Components/DependencyInjection/Loader/services.xsd new file mode 100644 index 000000000000..45fbc989ab01 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Loader/services.xsd @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Components/DependencyInjection/Parameter.php b/src/Symfony/Components/DependencyInjection/Parameter.php new file mode 100644 index 000000000000..01a8adda26f7 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Parameter.php @@ -0,0 +1,46 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Parameter represents a parameter reference. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id: Reference.php 267 2009-03-26 19:56:18Z fabien $ + */ +class Parameter +{ + protected + $id = null; + + /** + * Constructor. + * + * @param string $id The parameter key + */ + public function __construct($id) + { + $this->id = $id; + } + + /** + * __toString. + * + * @return string The parameter key + */ + public function __toString() + { + return (string) $this->id; + } +} diff --git a/src/Symfony/Components/DependencyInjection/Reference.php b/src/Symfony/Components/DependencyInjection/Reference.php new file mode 100644 index 000000000000..bf9eaefb6425 --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/Reference.php @@ -0,0 +1,54 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Reference represents a service reference. + * + * @package symfony + * @subpackage dependency_injection + * @author Fabien Potencier + * @version SVN: $Id: Reference.php 267 2009-03-26 19:56:18Z fabien $ + */ +class Reference +{ + protected $id, $invalidBehavior; + + /** + * Constructor. + * + * @param string $id The service identifier + * @param int $invalidBehavior The behavior when the service does not exist + * + * @see Container + */ + public function __construct($id, $invalidBehavior = Container::EXCEPTION_ON_INVALID_REFERENCE) + { + $this->id = $id; + $this->invalidBehavior = $invalidBehavior; + } + + /** + * __toString. + * + * @return string The service identifier + */ + public function __toString() + { + return (string) $this->id; + } + + public function getInvalidBehavior() + { + return $this->invalidBehavior; + } +} diff --git a/src/Symfony/Components/DependencyInjection/SimpleXMLElement.php b/src/Symfony/Components/DependencyInjection/SimpleXMLElement.php new file mode 100644 index 000000000000..3c1cf337e09a --- /dev/null +++ b/src/Symfony/Components/DependencyInjection/SimpleXMLElement.php @@ -0,0 +1,87 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class SimpleXMLElement extends \SimpleXMLElement +{ + public function getAttributeAsPhp($name) + { + return self::phpize($this[$name]); + } + + public function getArgumentsAsPhp($name) + { + $arguments = array(); + foreach ($this->$name as $arg) + { + $key = isset($arg['key']) ? (string) $arg['key'] : (!$arguments ? 0 : max(array_keys($arguments)) + 1); + + // parameter keys are case insensitive + if ('parameter' == $name) + { + $key = strtolower($key); + } + + switch ($arg['type']) + { + case 'service': + $invalidBehavior = Container::EXCEPTION_ON_INVALID_REFERENCE; + if (isset($arg['on-invalid']) && 'ignore' == $arg['on-invalid']) + { + $invalidBehavior = Container::IGNORE_ON_INVALID_REFERENCE; + } + elseif (isset($arg['on-invalid']) && 'null' == $arg['on-invalid']) + { + $invalidBehavior = Container::NULL_ON_INVALID_REFERENCE; + } + $arguments[$key] = new Reference((string) $arg['id'], $invalidBehavior); + break; + case 'collection': + $arguments[$key] = $arg->getArgumentsAsPhp($name); + break; + case 'string': + $arguments[$key] = (string) $arg; + break; + case 'constant': + $arguments[$key] = constant((string) $arg); + break; + default: + $arguments[$key] = self::phpize($arg); + } + } + + return $arguments; + } + + static public function phpize($value) + { + $value = (string) $value; + + switch (true) + { + case 'null' == strtolower($value): + return null; + case ctype_digit($value): + return '0' == $value[0] ? octdec($value) : intval($value); + case 'true' === strtolower($value): + return true; + case 'false' === strtolower($value): + return false; + case is_numeric($value): + return '0x' == $value[0].$value[1] ? hexdec($value) : floatval($value); + case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $value): + return floatval(str_replace(',', '', $value)); + default: + return (string) $value; + } + } +} diff --git a/src/Symfony/Components/EventDispatcher/Event.php b/src/Symfony/Components/EventDispatcher/Event.php new file mode 100644 index 000000000000..740e598ee18c --- /dev/null +++ b/src/Symfony/Components/EventDispatcher/Event.php @@ -0,0 +1,162 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Event. + * + * @package symfony + * @subpackage event_dispatcher + * @author Fabien Potencier + * @version SVN: $Id: Event.class.php 8698 2008-04-30 16:35:28Z fabien $ + */ +class Event implements \ArrayAccess +{ + protected $value = null; + protected $processed = false; + protected $subject; + protected $name; + protected $parameters; + + /** + * Constructs a new Event. + * + * @param mixed $subject The subject + * @param string $name The event name + * @param array $parameters An array of parameters + */ + public function __construct($subject, $name, $parameters = array()) + { + $this->subject = $subject; + $this->name = $name; + $this->parameters = $parameters; + } + + /** + * Returns the subject. + * + * @return mixed The subject + */ + public function getSubject() + { + return $this->subject; + } + + /** + * Returns the event name. + * + * @return string The event name + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the return value for this event. + * + * @param mixed $value The return value + */ + public function setReturnValue($value) + { + $this->value = $value; + } + + /** + * Returns the return value. + * + * @return mixed The return value + */ + public function getReturnValue() + { + return $this->value; + } + + /** + * Sets the processed flag. + * + * @param Boolean $processed The processed flag value + */ + public function setProcessed($processed) + { + $this->processed = (boolean) $processed; + } + + /** + * Returns whether the event has been processed by a listener or not. + * + * @return Boolean true if the event has been processed, false otherwise + */ + public function isProcessed() + { + return $this->processed; + } + + /** + * Returns the event parameters. + * + * @return array The event parameters + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Returns true if the parameter exists (implements the ArrayAccess interface). + * + * @param string $name The parameter name + * + * @return Boolean true if the parameter exists, false otherwise + */ + public function offsetExists($name) + { + return array_key_exists($name, $this->parameters); + } + + /** + * Returns a parameter value (implements the ArrayAccess interface). + * + * @param string $name The parameter name + * + * @return mixed The parameter value + */ + public function offsetGet($name) + { + if (!array_key_exists($name, $this->parameters)) + { + throw new \InvalidArgumentException(sprintf('The event "%s" has no "%s" parameter.', $this->name, $name)); + } + + return $this->parameters[$name]; + } + + /** + * Sets a parameter (implements the ArrayAccess interface). + * + * @param string $name The parameter name + * @param mixed $value The parameter value + */ + public function offsetSet($name, $value) + { + $this->parameters[$name] = $value; + } + + /** + * Removes a parameter (implements the ArrayAccess interface). + * + * @param string $name The parameter name + */ + public function offsetUnset($name) + { + unset($this->parameters[$name]); + } +} diff --git a/src/Symfony/Components/EventDispatcher/EventDispatcher.php b/src/Symfony/Components/EventDispatcher/EventDispatcher.php new file mode 100644 index 000000000000..d8a2f83d30fc --- /dev/null +++ b/src/Symfony/Components/EventDispatcher/EventDispatcher.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * EventDispatcher implements a dispatcher object. + * + * @see http://developer.apple.com/documentation/Cocoa/Conceptual/Notifications/index.html Apple's Cocoa framework + * + * @package symfony + * @subpackage event_dispatcher + * @author Fabien Potencier + * @version SVN: $Id: EventDispatcher.class.php 10631 2008-08-03 16:50:47Z fabien $ + */ +class EventDispatcher +{ + protected $listeners = array(); + + /** + * Connects a listener to a given event name. + * + * @param string $name An event name + * @param mixed $listener A PHP callable + */ + public function connect($name, $listener) + { + if (!isset($this->listeners[$name])) + { + $this->listeners[$name] = array(); + } + + $this->listeners[$name][] = $listener; + } + + /** + * Disconnects a listener for a given event name. + * + * @param string $name An event name + * @param mixed $listener A PHP callable + * + * @return mixed false if listener does not exist, null otherwise + */ + public function disconnect($name, $listener) + { + if (!isset($this->listeners[$name])) + { + return false; + } + + foreach ($this->listeners[$name] as $i => $callable) + { + if ($listener === $callable) + { + unset($this->listeners[$name][$i]); + } + } + } + + /** + * Notifies all listeners of a given event. + * + * @param Event $event A Event instance + * + * @return Event The Event instance + */ + public function notify(Event $event) + { + foreach ($this->getListeners($event->getName()) as $listener) + { + call_user_func($listener, $event); + } + + return $event; + } + + /** + * Notifies all listeners of a given event until one returns a non null value. + * + * @param Event $event A Event instance + * + * @return Event The Event instance + */ + public function notifyUntil(Event $event) + { + foreach ($this->getListeners($event->getName()) as $listener) + { + if (call_user_func($listener, $event)) + { + $event->setProcessed(true); + break; + } + } + + return $event; + } + + /** + * Filters a value by calling all listeners of a given event. + * + * @param Event $event A Event instance + * @param mixed $value The value to be filtered + * + * @return Event The Event instance + */ + public function filter(Event $event, $value) + { + foreach ($this->getListeners($event->getName()) as $listener) + { + $value = call_user_func($listener, $event, $value); + } + + $event->setReturnValue($value); + + return $event; + } + + /** + * Returns true if the given event name has some listeners. + * + * @param string $name The event name + * + * @return Boolean true if some listeners are connected, false otherwise + */ + public function hasListeners($name) + { + if (!isset($this->listeners[$name])) + { + $this->listeners[$name] = array(); + } + + return (boolean) count($this->listeners[$name]); + } + + /** + * Returns all listeners associated with a given event name. + * + * @param string $name The event name + * + * @return array An array of listeners + */ + public function getListeners($name) + { + if (!isset($this->listeners[$name])) + { + return array(); + } + + return $this->listeners[$name]; + } +} diff --git a/src/Symfony/Components/OutputEscaper/ArrayDecorator.php b/src/Symfony/Components/OutputEscaper/ArrayDecorator.php new file mode 100644 index 000000000000..f35dc52d448c --- /dev/null +++ b/src/Symfony/Components/OutputEscaper/ArrayDecorator.php @@ -0,0 +1,168 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Output escaping decorator class for arrays. + * + * @see Escaper + * @package symfony + * @subpackage output_escaper + * @author Fabien Potencier + * @author Mike Squire + * @version SVN: $Id: ArrayDecorator.class.php 15624 2009-02-19 10:53:30Z fabien $ + */ +class ArrayDecorator extends GetterDecorator implements \Iterator, \ArrayAccess, \Countable +{ + /** + * Used by the iterator to know if the current element is valid. + * + * @var int + */ + private $count; + + /** + * Reset the array to the beginning (as required for the Iterator interface). + */ + public function rewind() + { + reset($this->value); + + $this->count = count($this->value); + } + + /** + * Get the key associated with the current value (as required by the Iterator interface). + * + * @return string The key + */ + public function key() + { + return key($this->value); + } + + /** + * Escapes and return the current value (as required by the Iterator interface). + * + * This escapes the value using {@link Escaper::escape()} with + * whatever escaping method is set for this instance. + * + * @return mixed The escaped value + */ + public function current() + { + return Escaper::escape($this->escapingMethod, current($this->value)); + } + + /** + * Moves to the next element (as required by the Iterator interface). + */ + public function next() + { + next($this->value); + + $this->count --; + } + + /** + * Returns true if the current element is valid (as required by the Iterator interface). + * + * The current element will not be valid if {@link next()} has fallen off the + * end of the array or if there are no elements in the array and {@link + * rewind()} was called. + * + * @return bool The validity of the current element; true if it is valid + */ + public function valid() + { + return $this->count > 0; + } + + /** + * Returns true if the supplied offset isset in the array (as required by the ArrayAccess interface). + * + * @param string $offset The offset of the value to check existance of + * + * @return bool true if the offset isset; false otherwise + */ + public function offsetExists($offset) + { + return isset($this->value[$offset]); + } + + /** + * Returns the element associated with the offset supplied (as required by the ArrayAccess interface). + * + * @param string $offset The offset of the value to get + * + * @return mixed The escaped value + */ + public function offsetGet($offset) + { + return Escaper::escape($this->escapingMethod, $this->value[$offset]); + } + + /** + * Throws an exception saying that values cannot be set (this method is + * required for the ArrayAccess interface). + * + * This (and the other Escaper classes) are designed to be read only + * so this is an illegal operation. + * + * @param string $offset (ignored) + * @param string $value (ignored) + * + * @throws \LogicException + */ + public function offsetSet($offset, $value) + { + throw new \LogicException('Cannot set values.'); + } + + /** + * Throws an exception saying that values cannot be unset (this method is + * required for the ArrayAccess interface). + * + * This (and the other Escaper classes) are designed to be read only + * so this is an illegal operation. + * + * @param string $offset (ignored) + * + * @throws LogicException + */ + public function offsetUnset($offset) + { + throw new \LogicException('Cannot unset values.'); + } + + /** + * Returns the size of the array (are required by the Countable interface). + * + * @return int The size of the array + */ + public function count() + { + return count($this->value); + } + + /** + * Returns the (unescaped) value from the array associated with the key supplied. + * + * @param string $key The key into the array to use + * + * @return mixed The value + */ + public function getRaw($key) + { + return $this->value[$key]; + } +} diff --git a/src/Symfony/Components/OutputEscaper/Escaper.php b/src/Symfony/Components/OutputEscaper/Escaper.php new file mode 100644 index 000000000000..57014f972e14 --- /dev/null +++ b/src/Symfony/Components/OutputEscaper/Escaper.php @@ -0,0 +1,272 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Abstract class that provides an interface for escaping of output. + * + * @package symfony + * @subpackage output_escaper + * @author Fabien Potencier + * @author Mike Squire + * @version SVN: $Id: Escaper.class.php 21908 2009-09-11 12:06:21Z fabien $ + */ +abstract class Escaper +{ + /** + * The value that is to be escaped. + * + * @var mixed + */ + protected $value; + + /** + * The escaping method that is going to be applied to the value and its + * children. This is actually a PHP callable. + * + * @var string + */ + protected $escapingMethod; + + static protected $charset = 'UTF-8'; + + static protected $safeClasses = array(); + + static protected $strategies = array(); + + /** + * Constructor stores the escaping method and value. + * + * Since Escaper is an abstract class, instances cannot be created + * directly but the constructor will be inherited by sub-classes. + * + * @param string $escapingMethod Escaping method + * @param string $value Escaping value + */ + public function __construct($escapingMethod, $value) + { + $this->value = $value; + $this->escapingMethod = $escapingMethod; + } + + /** + * Decorates a PHP variable with something that will escape any data obtained + * from it. + * + * The following cases are dealt with: + * + * - The value is null or false: null or false is returned. + * - The value is scalar: the result of applying the escaping method is + * returned. + * - The value is an array or an object that implements the ArrayAccess + * interface: the array is decorated such that accesses to elements yield + * an escaped value. + * - The value implements the Traversable interface (either an Iterator, an + * IteratorAggregate or an internal PHP class that implements + * Traversable): decorated much like the array. + * - The value is another type of object: decorated such that the result of + * method calls is escaped. + * + * The escaping method is actually a PHP callable. This class hosts a set + * of standard escaping methods. + * + * @param string $escapingMethod The escaping method (a PHP callable) to apply to the value + * @param mixed $value The value to escape + * + * @return mixed Escaping value + * + * @throws \InvalidArgumentException If the escaping fails + */ + static public function escape($escapingMethod, $value) + { + if (null === $value) + { + return $value; + } + + // Scalars are anything other than arrays, objects and resources. + if (is_scalar($value)) + { + return call_user_func($escapingMethod, $value); + } + + if (is_array($value)) + { + return new ArrayDecorator($escapingMethod, $value); + } + + if (is_object($value)) + { + if ($value instanceof Escaper) + { + // avoid double decoration + $copy = clone $value; + + $copy->escapingMethod = $escapingMethod; + + return $copy; + } + else if (self::isClassMarkedAsSafe(get_class($value))) + { + // the class or one of its children is marked as safe + // return the unescaped object + return $value; + } + else if ($value instanceof Safe) + { + // do not escape objects marked as safe + // return the original object + return $value->getValue(); + } + else if ($value instanceof \Traversable) + { + return new IteratorDecorator($escapingMethod, $value); + } + else + { + return new ObjectDecorator($escapingMethod, $value); + } + } + + // it must be a resource; cannot escape that. + throw new \InvalidArgumentException(sprintf('Unable to escape value "%s".', var_export($value, true))); + } + + /** + * Unescapes a value that has been escaped previously with the escape() method. + * + * @param mixed $value The value to unescape + * + * @return mixed Unescaped value + * + * @throws \InvalidArgumentException If the escaping fails + */ + static public function unescape($value) + { + if (null === $value || is_bool($value)) + { + return $value; + } + + if (is_scalar($value)) + { + return html_entity_decode($value, ENT_QUOTES, self::$charset); + } + elseif (is_array($value)) + { + foreach ($value as $name => $v) + { + $value[$name] = self::unescape($v); + } + + return $value; + } + elseif (is_object($value)) + { + return $value instanceof Escaper ? $value->getRawValue() : $value; + } + + return $value; + } + + /** + * Returns true if the class if marked as safe. + * + * @param string $class A class name + * + * @return bool true if the class if safe, false otherwise + */ + static public function isClassMarkedAsSafe($class) + { + if (in_array($class, self::$safeClasses)) + { + return true; + } + + foreach (self::$safeClasses as $safeClass) + { + if (is_subclass_of($class, $safeClass)) + { + return true; + } + } + + return false; + } + + /** + * Marks an array of classes (and all its children) as being safe for output. + * + * @param array $classes An array of class names + */ + static public function markClassesAsSafe(array $classes) + { + self::$safeClasses = array_unique(array_merge(self::$safeClasses, $classes)); + } + + /** + * Marks a class (and all its children) as being safe for output. + * + * @param string $class A class name + */ + static public function markClassAsSafe($class) + { + self::markClassesAsSafe(array($class)); + } + + /** + * Returns the raw value associated with this instance. + * + * Concrete instances of Escaper classes decorate a value which is + * stored by the constructor. This returns that original, unescaped, value. + * + * @return mixed The original value used to construct the decorator + */ + public function getRawValue() + { + return $this->value; + } + + /** + * Gets a value from the escaper. + * + * @param string $var Value to get + * + * @return mixed Value + */ + public function __get($var) + { + return $this->escape($this->escapingMethod, $this->value->$var); + } + + /** + * Sets the current charset. + * + * @param string $charset The current charset + */ + static public function setCharset($charset) + { + self::$charset = $charset; + } + + /** + * Gets the current charset. + * + * @return string The current charset + */ + static public function getCharset() + { + return self::$charset; + } +} diff --git a/src/Symfony/Components/OutputEscaper/GetterDecorator.php b/src/Symfony/Components/OutputEscaper/GetterDecorator.php new file mode 100644 index 000000000000..b4f779a84d4f --- /dev/null +++ b/src/Symfony/Components/OutputEscaper/GetterDecorator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Abstract output escaping decorator class for "getter" objects. + * + * @see Escaper + * @package symfony + * @subpackage output_escaper + * @author Fabien Potencier + * @author Mike Squire + * @version SVN: $Id: GetterDecorator.class.php 15624 2009-02-19 10:53:30Z fabien $ + */ +abstract class GetterDecorator extends Escaper +{ + /** + * Returns the raw, unescaped value associated with the key supplied. + * + * The key might be an index into an array or a value to be passed to the + * decorated object's get() method. + * + * @param string $key The key to retrieve + * + * @return mixed The value + */ + public abstract function getRaw($key); + + /** + * Returns the escaped value associated with the key supplied. + * + * Typically (using this implementation) the raw value is obtained using the + * {@link getRaw()} method, escaped and the result returned. + * + * @param string $key The key to retieve + * @param string $escapingMethod The escaping method (a PHP function) to use + * + * @return mixed The escaped value + */ + public function get($key, $escapingMethod = null) + { + if (!$escapingMethod) + { + $escapingMethod = $this->escapingMethod; + } + + return Escaper::escape($escapingMethod, $this->getRaw($key)); + } +} diff --git a/src/Symfony/Components/OutputEscaper/IteratorDecorator.php b/src/Symfony/Components/OutputEscaper/IteratorDecorator.php new file mode 100644 index 000000000000..86b3b57ddf86 --- /dev/null +++ b/src/Symfony/Components/OutputEscaper/IteratorDecorator.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Output escaping iterator decorator. + * + * This takes an object that implements the Traversable interface and turns it + * into an iterator with each value escaped. + * + * @see Escaper + * @package symfony + * @subpackage output_escaper + * @author Fabien Potencier + * @author Mike Squire + * @version SVN: $Id: IteratorDecorator.class.php 15624 2009-02-19 10:53:30Z fabien $ + */ +class IteratorDecorator extends ObjectDecorator implements \Iterator, \Countable, \ArrayAccess +{ + /** + * The iterator to be used. + * + * @var \IteratorIterator + */ + private $iterator; + + /** + * Constructs a new escaping iteratoror using the escaping method and value supplied. + * + * @param string $escapingMethod The escaping method to use + * @param \Traversable $value The iterator to escape + */ + public function __construct($escapingMethod, \Traversable $value) + { + // Set the original value for __call(). Set our own iterator because passing + // it to IteratorIterator will lose any other method calls. + + parent::__construct($escapingMethod, $value); + + $this->iterator = new \IteratorIterator($value); + } + + /** + * Resets the iterator (as required by the Iterator interface). + * + * @return bool true, if the iterator rewinds successfully otherwise false + */ + public function rewind() + { + return $this->iterator->rewind(); + } + + /** + * Escapes and gets the current element (as required by the Iterator interface). + * + * @return mixed The escaped value + */ + public function current() + { + return Escaper::escape($this->escapingMethod, $this->iterator->current()); + } + + /** + * Gets the current key (as required by the Iterator interface). + * + * @return string Iterator key + */ + public function key() + { + return $this->iterator->key(); + } + + /** + * Moves to the next element in the iterator (as required by the Iterator interface). + */ + public function next() + { + return $this->iterator->next(); + } + + /** + * Returns whether the current element is valid or not (as required by the + * Iterator interface). + * + * @return bool true if the current element is valid; false otherwise + */ + public function valid() + { + return $this->iterator->valid(); + } + + /** + * Returns true if the supplied offset isset in the array (as required by the ArrayAccess interface). + * + * @param string $offset The offset of the value to check existance of + * + * @return bool true if the offset isset; false otherwise + */ + public function offsetExists($offset) + { + return isset($this->value[$offset]); + } + + /** + * Returns the element associated with the offset supplied (as required by the ArrayAccess interface). + * + * @param string $offset The offset of the value to get + * + * @return mixed The escaped value + */ + public function offsetGet($offset) + { + return Escaper::escape($this->escapingMethod, $this->value[$offset]); + } + + /** + * Throws an exception saying that values cannot be set (this method is + * required for the ArrayAccess interface). + * + * This (and the other Escaper classes) are designed to be read only + * so this is an illegal operation. + * + * @param string $offset (ignored) + * @param string $value (ignored) + * + * @throws \LogicException + */ + public function offsetSet($offset, $value) + { + throw new \LogicException('Cannot set values.'); + } + + /** + * Throws an exception saying that values cannot be unset (this method is + * required for the ArrayAccess interface). + * + * This (and the other Escaper classes) are designed to be read only + * so this is an illegal operation. + * + * @param string $offset (ignored) + * + * @throws \LogicException + */ + public function offsetUnset($offset) + { + throw new \LogicException('Cannot unset values.'); + } + + /** + * Returns the size of the array (are required by the Countable interface). + * + * @return int The size of the array + */ + public function count() + { + return count($this->value); + } +} diff --git a/src/Symfony/Components/OutputEscaper/ObjectDecorator.php b/src/Symfony/Components/OutputEscaper/ObjectDecorator.php new file mode 100644 index 000000000000..a91373086d20 --- /dev/null +++ b/src/Symfony/Components/OutputEscaper/ObjectDecorator.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Output escaping object decorator that intercepts all method calls and escapes + * their return values. + * + * @see Escaper + * @package symfony + * @subpackage output_escaper + * @author Fabien Potencier + * @author Mike Squire + * @version SVN: $Id: ObjectDecorator.class.php 15624 2009-02-19 10:53:30Z fabien $ + */ +class ObjectDecorator extends GetterDecorator +{ + /** + * Magic PHP method that intercepts method calls, calls them on the objects + * that is being escaped and escapes the result. + * + * The calling of the method is changed slightly to accommodate passing a + * specific escaping strategy. An additional parameter is appended to the + * argument list which is the escaping strategy. The decorator will remove + * and use this parameter as the escaping strategy if it begins with 'esc_' + * (the prefix all escaping helper functions have). + * + * For example if an object, $o, implements methods a() and b($arg): + * + * $o->a() // Escapes the return value of a() + * $o->a(ESC_RAW) // Uses the escaping method ESC_RAW with a() + * $o->b('a') // Escapes the return value of b('a') + * $o->b('a', ESC_RAW); // Uses the escaping method ESC_RAW with b('a') + * + * @param string $method The method on the object to be called + * @param array $args An array of arguments to be passed to the method + * + * @return mixed The escaped value returned by the method + */ + public function __call($method, $args) + { + if (count($args) > 0) + { + $escapingMethod = $args[count($args) - 1]; + if (is_string($escapingMethod) && substr($escapingMethod, 0, 4) === 'esc_') + { + array_pop($args); + } + else + { + $escapingMethod = $this->escapingMethod; + } + } + else + { + $escapingMethod = $this->escapingMethod; + } + + $value = call_user_func_array(array($this->value, $method), $args); + + return Escaper::escape($escapingMethod, $value); + } + + /** + * Returns the result of calling the get() method on the object, bypassing + * any escaping, if that method exists. + * + * If there is not a callable get() method this will throw an exception. + * + * @param string $key The parameter to be passed to the get() get method + * + * @return mixed The unescaped value returned + * + * @throws \LogicException if the object does not have a callable get() method + */ + public function getRaw($key) + { + if (!is_callable(array($this->value, 'get'))) + { + throw new \LogicException('Object does not have a callable get() method.'); + } + + return $this->value->get($key); + } + + /** + * Try to call decorated object __toString() method if exists. + * + * @return string + */ + public function __toString() + { + return $this->escape($this->escapingMethod, $this->value->__toString()); + } +} diff --git a/src/Symfony/Components/OutputEscaper/Safe.php b/src/Symfony/Components/OutputEscaper/Safe.php new file mode 100644 index 000000000000..b06f8bc35781 --- /dev/null +++ b/src/Symfony/Components/OutputEscaper/Safe.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Marks a variable as being safe for output. + * + * @package symfony + * @subpackage output_escaper + * @author Fabien Potencier + * @version SVN: $Id: Safe.class.php 16553 2009-03-24 16:49:06Z Kris.Wallsmith $ + */ +class Safe extends \ArrayIterator +{ + protected + $value = null; + + /** + * Constructor. + * + * @param mixed $value The value to mark as safe + */ + public function __construct($value) + { + $this->value = $value; + + if (is_array($value) || is_object($value)) + { + parent::__construct($value); + } + } + + public function __toString() + { + return (string) $this->value; + } + + public function __get($key) + { + return $this->value->$key; + } + + public function __set($key, $value) + { + $this->value->$key = $value; + } + + public function __call($method, $arguments) + { + return call_user_func_array(array($this->value, $method), $arguments); + } + + public function __isset($key) + { + return isset($this->value->$key); + } + + public function __unset($key) + { + unset($this->value->$key); + } + + /** + * Returns the embedded value. + * + * @return mixed The embedded value + */ + public function getValue() + { + return $this->value; + } +} diff --git a/src/Symfony/Components/OutputEscaper/escaping_helpers.php b/src/Symfony/Components/OutputEscaper/escaping_helpers.php new file mode 100644 index 000000000000..b48f027e33e0 --- /dev/null +++ b/src/Symfony/Components/OutputEscaper/escaping_helpers.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * The functions are primarily used by the output escaping component. + * + * Each function specifies a way for applying a transformation to a string + * passed to it. The purpose is for the string to be "escaped" so it is + * suitable for the format it is being displayed in. + * + * For example, the string: "It's required that you enter a username & password.\n" + * If this were to be displayed as HTML it would be sensible to turn the + * ampersand into '&' and the apostrophe into '&aps;'. However if it were + * going to be used as a string in JavaScript to be displayed in an alert box + * it would be right to leave the string as-is, but c-escape the apostrophe and + * the new line. + * + * For each function there is a define to avoid problems with strings being + * incorrectly specified. + * + * @package symfony + * @subpackage helper + * @author Mike Squire + * @version SVN: $Id: EscapingHelper.php 18907 2009-06-04 09:36:30Z FabianLange $ + */ + +/** + * Runs the PHP function htmlentities on the value passed. + * + * @param string $value the value to escape + * @return string the escaped value + */ +function esc_entities($value) +{ + // Numbers and boolean values get turned into strings which can cause problems + // with type comparisons (e.g. === or is_int() etc). + return is_string($value) ? htmlentities($value, ENT_QUOTES, Symfony\Components\OutputEscaper\Escaper::getCharset()) : $value; +} + +define('ESC_ENTITIES', 'esc_entities'); + +/** + * Runs the PHP function htmlspecialchars on the value passed. + * + * @param string $value the value to escape + * @return string the escaped value + */ +function esc_specialchars($value) +{ + // Numbers and boolean values get turned into strings which can cause problems + // with type comparisons (e.g. === or is_int() etc). + return is_string($value) ? htmlspecialchars($value, ENT_QUOTES, Symfony\Components\OutputEscaper\Escaper::getCharset()) : $value; +} + +define('ESC_SPECIALCHARS', 'esc_specialchars'); + +/** + * An identity function that merely returns that which it is given, the purpose + * being to be able to specify that the value is not to be escaped in any way. + * + * @param string $value the value to escape + * @return string the escaped value + */ +function esc_raw($value) +{ + return $value; +} + +define('ESC_RAW', 'esc_raw'); + +/** + * A function that c-escapes a string after applying {@link esc_entities()}. The + * assumption is that the value will be used to generate dynamic HTML in some + * way and the safest way to prevent mishap is to assume the value should have + * HTML entities set properly. + * + * The {@link esc_js_no_entities()} method should be used to escape a string + * that is ultimately not going to end up as text in an HTML document. + * + * @param string $value the value to escape + * @return string the escaped value + */ +function esc_js($value) +{ + return esc_js_no_entities(esc_entities($value)); +} + +define('ESC_JS', 'esc_js'); + +/** + * A function the c-escapes a string, making it suitable to be placed in a + * JavaScript string. + * + * @param string $value the value to escape + * @return string the escaped value + */ +function esc_js_no_entities($value) +{ + return str_replace(array("\\" , "\n" , "\r" , "\"" , "'" ), + array("\\\\", "\\n" , "\\r", "\\\"", "\\'"), + $value); +} + +define('ESC_JS_NO_ENTITIES', 'esc_js_no_entities'); diff --git a/src/Symfony/Components/Templating/DebuggerInterface.php b/src/Symfony/Components/Templating/DebuggerInterface.php new file mode 100644 index 000000000000..521052461166 --- /dev/null +++ b/src/Symfony/Components/Templating/DebuggerInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * DebuggerInterface is the interface you need to implement + * to debug template loader instances. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +interface DebuggerInterface +{ + /** + * Logs a message. + * + * @param string $message A message to log + */ + function log($message); +} diff --git a/src/Symfony/Components/Templating/Engine.php b/src/Symfony/Components/Templating/Engine.php new file mode 100644 index 000000000000..af98e246701a --- /dev/null +++ b/src/Symfony/Components/Templating/Engine.php @@ -0,0 +1,312 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Engine is the main class of the templating component. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class Engine +{ + protected + $loader = null, + $renderers = array(), + $current = null, + $helperSet = array(), + $parents = array(), + $stack = array(), + $slots = array(), + $openSlots = array(), + $charset = 'UTF-8'; + + /** + * Constructor. + * + * @param LoaderInterface $loader A loader instance + * @param array $renderers An array of renderer instances + * @param HelperSet $helperSet A helper set instance + */ + public function __construct(LoaderInterface $loader, array $renderers = array(), HelperSet $helperSet = null) + { + $this->loader = $loader; + $this->renderers = $renderers; + + $this->setHelperSet(null === $helperSet ? new HelperSet() : $helperSet); + + if (!isset($this->renderers['php'])) + { + $this->renderers['php'] = new PhpRenderer(); + } + + foreach ($this->renderers as $renderer) + { + $renderer->setEngine($this); + } + } + + /** + * Renders a template. + * + * @param string $name A template logical name + * @param array $parameters An array of parameters to pass to the template + * + * @return string The evaluated template as a string + * + * @throws \InvalidArgumentException if the renderer does not exist or if the template does not exist + * @throws \RuntimeException if the template cannot be rendered + */ + public function render($name, array $parameters = array()) + { + // split renderer & template + if (false !== $pos = strpos($name, ':')) + { + $renderer = substr($name, 0, $pos); + $name = substr($name, $pos + 1); + } + else + { + $renderer = 'php'; + } + + // load + $template = $this->loader->load($name, $renderer); + + if (false === $template) + { + throw new \InvalidArgumentException(sprintf('The template "%s" does not exist (renderer: %s).', $name, $renderer)); + } + + $this->current = $name; + $this->parents[$name] = null; + + // renderer + if ($template->getRenderer()) + { + $renderer = $template->getRenderer(); + } + + if (!isset($this->renderers[$renderer])) + { + throw new \InvalidArgumentException(sprintf('The renderer "%s" is not registered.', $renderer)); + } + + // render + if (false === $content = $this->renderers[$renderer]->evaluate($template, $parameters)) + { + throw new \RuntimeException(sprintf('The template "%s" cannot be rendered (renderer: %s).', $name, $renderer)); + } + + // decorator + if ($this->parents[$name]) + { + $this->stack[] = $this->get('content'); + $this->set('content', $content); + + $content = $this->render($this->parents[$name], $parameters); + + $this->set('content', array_pop($this->stack)); + } + + return $content; + } + + /** + * Sets a helper value. + * + * @param string $name The helper name + * @param HelperInterface $value The helper value + */ + public function setHelperSet(HelperSet $helperSet) + { + $this->helperSet = $helperSet; + + $helperSet->setEngine($this); + } + + /** + * Gets all helper values. + * + * @return array An array of all helper values + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Gets a helper value. + * + * @param string $name The helper name + * + * @return mixed The helper value + * + * @throws \InvalidArgumentException if the helper is not defined + */ + public function __get($name) + { + return $this->helperSet->get($name); + } + + /** + * Decorates the current template with another one. + * + * @param string $template The decorator logical name + */ + public function extend($template) + { + $this->parents[$this->current] = $template; + } + + /** + * Starts a new slot. + * + * This method starts an output buffer that will be + * closed when the stop() method is called. + * + * @param string $name The slot name + * + * @throws \InvalidArgumentException if a slot with the same name is already started + */ + public function start($name) + { + if (in_array($name, $this->openSlots)) + { + throw new \InvalidArgumentException(sprintf('A slot named "%s" is already started.', $name)); + } + + $this->openSlots[] = $name; + $this->slots[$name] = ''; + + ob_start(); + ob_implicit_flush(0); + } + + /** + * Stops a slot. + * + * @throws \LogicException if no slot has been started + */ + public function stop() + { + $content = ob_get_clean(); + + if (!$this->openSlots) + { + throw new \LogicException('No slot started.'); + } + + $name = array_pop($this->openSlots); + + $this->slots[$name] = $content; + } + + /** + * Returns true if the slot exists. + * + * @param string $name The slot name + */ + public function has($name) + { + return isset($this->slots[$name]); + } + + /** + * Gets the slot value. + * + * @param string $name The slot name + * @param string $default The default slot content + * + * @return string The slot content + */ + public function get($name, $default = false) + { + return isset($this->slots[$name]) ? $this->slots[$name] : $default; + } + + /** + * Sets a slot value. + * + * @param string $name The slot name + * @param string $content The slot content + */ + public function set($name, $content) + { + $this->slots[$name] = $content; + } + + /** + * Outputs a slot. + * + * @param string $name The slot name + * @param string $default The default slot content + * + * @return Boolean true if the slot is defined or if a default content has been provided, false otherwise + */ + public function output($name, $default = false) + { + if (!isset($this->slots[$name])) + { + if (false !== $default) + { + echo $default; + + return true; + } + + return false; + } + + echo $this->slots[$name]; + + return true; + } + + /** + * Escapes a string by using the current charset. + * + * @param string $value A string to escape + * + * @return string The escaped string or the original value if not a string + */ + public function escape($value) + { + return is_string($value) || (is_object($value) && method_exists($value, '__toString')) ? htmlspecialchars($value, ENT_QUOTES, $this->charset) : $value; + } + + /** + * Sets the charset to use. + * + * @param string $charset The charset + */ + public function setCharset($charset) + { + $this->charset = $charset; + } + + /** + * Gets the current charset. + * + * @return string The current charset + */ + public function getCharset() + { + return $this->charset; + } +} diff --git a/src/Symfony/Components/Templating/Helper/AssetsHelper.php b/src/Symfony/Components/Templating/Helper/AssetsHelper.php new file mode 100644 index 000000000000..57054c998d2c --- /dev/null +++ b/src/Symfony/Components/Templating/Helper/AssetsHelper.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * AssetsHelper is the base class for all helper classes that manages assets. + * + * Usage: + * + * + * + * + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class AssetsHelper extends Helper +{ + protected + $version = null, + $baseURLs = array(), + $basePath = ''; + + /** + * Constructor. + * + * @param string $basePath The base path + * @param string|array $baseURLs The domain URL or an array of domain URLs + * @param string $version The version + */ + public function __construct($basePath = null, $baseURLs = array(), $version = null) + { + $this->setBasePath($basePath); + $this->setBaseURLs($baseURLs); + $this->version = $version; + } + + /** + * Gets the version to add to public URL. + * + * @return string The current version + */ + public function getVersion() + { + return $this->version; + } + + /** + * Sets the version that is added to each public URL. + * + * @param string $id The version + */ + public function setVersion($version) + { + $this->version = $version; + } + + /** + * Gets the base path. + * + * @return string The base path + */ + public function getBasePath() + { + return $this->basePath; + } + + /** + * Sets the base path. + * + * @param string $basePath The base path + */ + public function setBasePath($basePath) + { + if (strlen($basePath) && '/' != $basePath[0]) + { + $basePath = '/'.$basePath; + } + + $this->basePath = rtrim($basePath, '/').'/'; + } + + /** + * Gets the base URL. + * + * @param string $path The path + * + * @return string The base URL + */ + public function getBaseURL($path) + { + $count = count($this->baseURLs); + + if (0 === $count) + { + return ''; + } + + if (1 === $count) + { + return $this->baseURLs[0]; + } + + return $this->baseURLs[fmod(hexdec(substr(md5($path), 0, 10)), $count)]; + + } + + /** + * Gets the base URLs. + * + * @return array The base URLs + */ + public function getBaseURLs() + { + return $this->baseURLs; + } + + /** + * Sets the base URLs. + * + * If you pass an array, the getBaseURL() will return a + * random one each time it is called. + * + * @param string|array $baseURLs The base URLs + */ + public function setBaseURLs($baseURLs) + { + if (!is_array($baseURLs)) + { + $baseURLs = array($baseURLs); + } + + $this->baseURLs = array(); + foreach ($baseURLs as $URL) + { + $this->baseURLs[] = rtrim($URL, '/'); + } + } + + /** + * Returns the public path. + * + * @param string $path A public path + * + * @return string A public path which takes into account the base path and URL path + */ + public function getUrl($path) + { + if (strpos($path, '://')) + { + return $path; + } + + if (0 !== strpos($path, '/')) + { + $path = $this->basePath.$path; + } + + return $this->getBaseURL($path).$path.($this->version ? '?'.$this->version : ''); + } + + /** + * Returns the canonical name of this helper. + * + * @return string The canonical name + */ + public function getName() + { + return 'assets'; + } +} diff --git a/src/Symfony/Components/Templating/Helper/Helper.php b/src/Symfony/Components/Templating/Helper/Helper.php new file mode 100644 index 000000000000..21155be946a7 --- /dev/null +++ b/src/Symfony/Components/Templating/Helper/Helper.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Helper is the base class for all helper classes. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +abstract class Helper implements HelperInterface +{ + protected + $helperSet = null; + + /** + * Sets the helper set associated with this helper. + * + * @param HelperSet $helperSet A HelperSet instance + */ + public function setHelperSet(HelperSet $helperSet = null) + { + $this->helperSet = $helperSet; + } + + /** + * Gets the helper set associated with this helper. + * + * @return HelperSet A HelperSet instance + */ + public function getHelperSet() + { + return $this->helperSet; + } +} diff --git a/src/Symfony/Components/Templating/Helper/HelperInterface.php b/src/Symfony/Components/Templating/Helper/HelperInterface.php new file mode 100644 index 000000000000..5e48f73cd2cd --- /dev/null +++ b/src/Symfony/Components/Templating/Helper/HelperInterface.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * HelperInterface is the interface all helpers must implement. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +interface HelperInterface +{ + /** + * Returns the canonical name of this helper. + * + * @return string The canonical name + */ + function getName(); + + /** + * Sets the helper set associated with this helper. + * + * @param HelperSet $helperSet A HelperSet instance + */ + function setHelperSet(HelperSet $helperSet = null); + + /** + * Gets the helper set associated with this helper. + * + * @return HelperSet A HelperSet instance + */ + function getHelperSet(); +} diff --git a/src/Symfony/Components/Templating/Helper/HelperSet.php b/src/Symfony/Components/Templating/Helper/HelperSet.php new file mode 100644 index 000000000000..f81e64fe0146 --- /dev/null +++ b/src/Symfony/Components/Templating/Helper/HelperSet.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * HelperSet represents a set of helpers to be used with a templating engine. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class HelperSet +{ + protected + $helpers = array(), + $engine = null; + + public function __construct(array $helpers = array()) + { + foreach ($helpers as $alias => $helper) + { + $this->set($helper, is_int($alias) ? null : $alias); + } + } + + /** + * Sets a helper. + * + * @param HelperInterface $value The helper instance + * @param string $alias An alias + */ + public function set(HelperInterface $helper, $alias = null) + { + $this->helpers[$helper->getName()] = $helper; + if (null !== $alias) + { + $this->helpers[$alias] = $helper; + } + + $helper->setHelperSet($this); + } + + /** + * Returns true if the helper if defined. + * + * @param string $name The helper name + * + * @return Boolean true if the helper is defined, false otherwise + */ + public function has($name) + { + return isset($this->helpers[$name]); + } + + /** + * Gets a helper value. + * + * @param string $name The helper name + * + * @return HelperInterface The helper instance + * + * @throws \InvalidArgumentException if the helper is not defined + */ + public function get($name) + { + if (!$this->has($name)) + { + throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); + } + + return $this->helpers[$name]; + } + + /** + * Sets the template engine associated with this helper set. + * + * @param Engine $engine A Engine instance + */ + public function setEngine(Engine $engine = null) + { + $this->engine = $engine; + } + + /** + * Gets the template engine associated with this helper set. + * + * @return Engine A Engine instance + */ + public function getEngine() + { + return $this->engine; + } +} diff --git a/src/Symfony/Components/Templating/Helper/JavascriptsHelper.php b/src/Symfony/Components/Templating/Helper/JavascriptsHelper.php new file mode 100644 index 000000000000..59c95578db31 --- /dev/null +++ b/src/Symfony/Components/Templating/Helper/JavascriptsHelper.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * JavascriptsHelper is a helper that manages JavaScripts. + * + * Usage: + * + * + * $this->javascripts->add('foo.css', array('media' => 'print')); + * echo $this->javascripts; + * + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class JavascriptsHelper extends Helper +{ + protected + $javascripts = array(); + + /** + * Adds a JavaScript file. + * + * @param string $javascript A JavaScript file path + * @param array $attributes An array of attributes + */ + public function add($javascript, $attributes = array()) + { + $this->javascripts[$this->helperSet->get('assets')->getUrl($javascript)] = $attributes; + } + + /** + * Returns all JavaScript files. + * + * @param array An array of JavaScript files to include + */ + public function get() + { + return $this->javascripts; + } + + /** + * Returns a string representation of this helper as HTML. + * + * @return string The HTML representation of the JavaScripts + */ + public function __toString() + { + $html = array(); + foreach ($this->javascripts as $path => $attributes) + { + $atts = array(); + foreach ($attributes as $key => $value) + { + $atts[] = sprintf('%s="%s"', $key, $this->helperSet->getEngine()->escape($value)); + } + + $html[] = sprintf('', $path, implode(' ', $atts)); + } + + return implode("\n", $html); + } + + /** + * Returns the canonical name of this helper. + * + * @return string The canonical name + */ + public function getName() + { + return 'javascripts'; + } +} diff --git a/src/Symfony/Components/Templating/Helper/StylesheetsHelper.php b/src/Symfony/Components/Templating/Helper/StylesheetsHelper.php new file mode 100644 index 000000000000..927d9b9aea29 --- /dev/null +++ b/src/Symfony/Components/Templating/Helper/StylesheetsHelper.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * StylesheetsHelper is a helper that manages stylesheets. + * + * Usage: + * + * + * $this->stylesheets->add('foo.css', array('media' => 'print')); + * echo $this->stylesheets; + * + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class StylesheetsHelper extends Helper +{ + protected + $stylesheets = array(); + + /** + * Adds a stylesheets file. + * + * @param string $stylesheet A stylesheet file path + * @param array $attributes An array of attributes + */ + public function add($stylesheet, $attributes = array()) + { + $this->stylesheets[$this->helperSet->get('assets')->getUrl($stylesheet)] = $attributes; + } + + /** + * Returns all stylesheet files. + * + * @param array An array of stylesheet files to include + */ + public function get() + { + return $this->stylesheets; + } + + /** + * Returns a string representation of this helper as HTML. + * + * @return string The HTML representation of the stylesheets + */ + public function __toString() + { + $html = array(); + foreach ($this->stylesheets as $path => $attributes) + { + $atts = array(); + foreach ($attributes as $key => $value) + { + $atts[] = sprintf('%s="%s"', $key, $this->helperSet->getEngine()->escape($value)); + } + + $html[] = sprintf('', $path, implode(' ', $atts)); + } + + return implode("\n", $html); + } + + /** + * Returns the canonical name of this helper. + * + * @return string The canonical name + */ + public function getName() + { + return 'stylesheets'; + } +} diff --git a/src/Symfony/Components/Templating/Loader/CacheLoader.php b/src/Symfony/Components/Templating/Loader/CacheLoader.php new file mode 100644 index 000000000000..a58a2fddb808 --- /dev/null +++ b/src/Symfony/Components/Templating/Loader/CacheLoader.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * CacheLoader is a loader that caches other loaders responses + * on the filesystem. + * + * This cache only caches on disk to allow PHP accelerators to cache the opcodes. + * All other mecanism would imply the use of `eval()`. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class CacheLoader extends Loader +{ + protected + $loader = null, + $dir = ''; + + /** + * Constructor. + * + * @param Loader $loader A Loader instance + * @param string $dir The directory where to store the cache files + */ + public function __construct(Loader $loader, $dir) + { + $this->loader = $loader; + $this->dir = $dir; + } + + /** + * Loads a template. + * + * @param string $template The logical template name + * @param string $renderer The renderer to use + * + * @return Storage|Boolean false if the template cannot be loaded, a Storage instance otherwise + */ + public function load($template, $renderer = 'php') + { + $path = $this->dir.DIRECTORY_SEPARATOR.md5($template.$renderer).'.tpl'; + + if ($this->loader instanceof CompilableLoaderInterface) + { + $renderer = 'php'; + } + + if (file_exists($path)) + { + if ($this->debugger) + { + $this->debugger->log(sprintf('Fetching template "%s" from cache', $template)); + } + + return new FileStorage($path, $renderer); + } + + if (false === $content = $this->loader->load($template, $renderer)) + { + return false; + } + + if ($this->loader instanceof CompilableLoaderInterface) + { + $content = $this->loader->compile($content); + } + + file_put_contents($path, $content); + + if ($this->debugger) + { + $this->debugger->log(sprintf('Storing template "%s" in cache', $template)); + } + + return new FileStorage($path, $renderer); + } +} diff --git a/src/Symfony/Components/Templating/Loader/ChainLoader.php b/src/Symfony/Components/Templating/Loader/ChainLoader.php new file mode 100644 index 000000000000..be5ab5bfbb7e --- /dev/null +++ b/src/Symfony/Components/Templating/Loader/ChainLoader.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * ChainLoader is a loader that calls other loaders to load templates. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class ChainLoader extends Loader +{ + protected + $loaders = array(); + + /** + * Constructor. + * + * @param array $loaders An array of loader instances + */ + public function __construct(array $loaders = array()) + { + foreach ($loaders as $loader) + { + $this->addLoader($loader); + } + } + + /** + * Adds a loader instance. + * + * @param Loader $loader A Loader instance + */ + public function addLoader(Loader $loader) + { + $this->loaders[] = $loader; + } + + /** + * Loads a template. + * + * @param string $template The logical template name + * @param string $renderer The renderer to use + * + * @return Storage|Boolean false if the template cannot be loaded, a Storage instance otherwise + */ + public function load($template, $renderer = 'php') + { + foreach ($this->loaders as $loader) + { + if (false !== $ret = $loader->load($template, $renderer)) + { + return $ret; + } + } + + return false; + } +} diff --git a/src/Symfony/Components/Templating/Loader/CompilableLoaderInterface.php b/src/Symfony/Components/Templating/Loader/CompilableLoaderInterface.php new file mode 100644 index 000000000000..c3bf965fc91b --- /dev/null +++ b/src/Symfony/Components/Templating/Loader/CompilableLoaderInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * CompilableLoaderInterface is the interface a template loader must implement + * if the templates are compilable. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +interface CompilableLoaderInterface +{ + /** + * Compiles a template. + * + * @param string $template The template to compile + * + * @return string The compiled template + * + * @throws \Exception if the template is not compilable + */ + public function compile($template); +} diff --git a/src/Symfony/Components/Templating/Loader/FilesystemLoader.php b/src/Symfony/Components/Templating/Loader/FilesystemLoader.php new file mode 100644 index 000000000000..16d02d11776c --- /dev/null +++ b/src/Symfony/Components/Templating/Loader/FilesystemLoader.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * FilesystemLoader is a loader that read templates from the filesystem. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class FilesystemLoader extends Loader +{ + protected + $templatePathPatternPatterns = array(); + + /** + * Constructor. + * + * @param array $templatePathPatterns An array of path patterns to look for templates + */ + public function __construct($templatePathPatterns) + { + if (!is_array($templatePathPatterns)) + { + $templatePathPatterns = array($templatePathPatterns); + } + + $this->templatePathPatterns = $templatePathPatterns; + } + + /** + * Loads a template. + * + * @param string $template The logical template name + * @param string $renderer The renderer to use + * + * @return Storage|Boolean false if the template cannot be loaded, a Storage instance otherwise + */ + public function load($template, $renderer = 'php') + { + if (self::isAbsolutePath($template) && file_exists($template)) + { + return new FileStorage($template); + } + + foreach ($this->templatePathPatterns as $templatePathPattern) + { + if (is_file($file = strtr($templatePathPattern, array('%name%' => $template, '%renderer%' => $renderer)))) + { + if ($this->debugger) + { + $this->debugger->log(sprintf('Loaded template file "%s" (renderer: %s)', $file, $renderer)); + } + + return new FileStorage($file); + } + + if ($this->debugger) + { + $this->debugger->log(sprintf('Failed loading template file "%s" (renderer: %s)', $file, $renderer)); + } + } + + return false; + } + + /** + * Returns true if the file is an existing absolute path. + * + * @param string $file A path + * + * @return true if the path exists and is absolute, false otherwise + */ + static protected function isAbsolutePath($file) + { + if ($file[0] == '/' || $file[0] == '\\' || + (strlen($file) > 3 && ctype_alpha($file[0]) && + $file[1] == ':' && + ($file[2] == '\\' || $file[2] == '/') + ) + ) + { + return true; + } + + return false; + } +} diff --git a/src/Symfony/Components/Templating/Loader/Loader.php b/src/Symfony/Components/Templating/Loader/Loader.php new file mode 100644 index 000000000000..37098d80fbc1 --- /dev/null +++ b/src/Symfony/Components/Templating/Loader/Loader.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Loader is the base class for all template loader classes. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +abstract class Loader implements LoaderInterface +{ + protected + $debugger = null; + + /** + * Sets the debugger to use for this loader. + * + * @param DebuggerInterface $debugger A debugger instance + */ + public function setDebugger(DebuggerInterface $debugger) + { + $this->debugger = $debugger; + } +} diff --git a/src/Symfony/Components/Templating/Loader/LoaderInterface.php b/src/Symfony/Components/Templating/Loader/LoaderInterface.php new file mode 100644 index 000000000000..8c39b942e2e1 --- /dev/null +++ b/src/Symfony/Components/Templating/Loader/LoaderInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * LoaderInterface is the interface all loaders must implement. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +interface LoaderInterface +{ + /** + * Loads a template. + * + * @param string $template The logical template name + * @param string $renderer The renderer to use + * + * @return Storage|Boolean false if the template cannot be loaded, a Storage instance otherwise + */ + function load($template, $renderer = 'php'); +} diff --git a/src/Symfony/Components/Templating/Renderer/PhpRenderer.php b/src/Symfony/Components/Templating/Renderer/PhpRenderer.php new file mode 100644 index 000000000000..05a6e2ea44bf --- /dev/null +++ b/src/Symfony/Components/Templating/Renderer/PhpRenderer.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * PhpRenderer is a renderer for PHP templates. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class PhpRenderer extends Renderer +{ + /** + * Evaluates a template. + * + * @param Storage $template The template to render + * @param array $parameters An array of parameters to pass to the template + * + * @return string|false The evaluated template, or false if the renderer is unable to render the template + */ + public function evaluate(Storage $template, array $parameters = array()) + { + if ($template instanceof FileStorage) + { + extract($parameters); + ob_start(); + require $template; + + return ob_get_clean(); + } + else if ($template instanceof StringStorage) + { + extract($parameters); + ob_start(); + eval('; ?>'.$template.' + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Renderer is the base class for all template renderer. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +abstract class Renderer implements RendererInterface +{ + protected + $engine = null; + + /** + * Sets the template engine associated with this renderer. + * + * @param Engine $engine A Engine instance + */ + public function setEngine(Engine $engine) + { + $this->engine = $engine; + } + + /** + * Forwards the call to the associated template instance. + * + * @param string $method The method name + * @param array $arguments The array of arguments + * + * @return mixed The return value returned by the associated template instance method + */ + public function __call($method, $arguments) + { + return call_user_func_array(array($this->engine, $method), $arguments); + } + + /** + * Forwards the call to the associated template instance. + * + * @param string $name The property name + * + * @return mixed The value returned by the associated template instance + */ + public function __get($name) + { + return $this->engine->$name; + } +} diff --git a/src/Symfony/Components/Templating/Renderer/RendererInterface.php b/src/Symfony/Components/Templating/Renderer/RendererInterface.php new file mode 100644 index 000000000000..cff3252b4b96 --- /dev/null +++ b/src/Symfony/Components/Templating/Renderer/RendererInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * RendererInterface is the interface all renderer classes must implement. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +interface RendererInterface +{ + /** + * Evaluates a template. + * + * @param Storage $template The template to render + * @param array $parameters An array of parameters to pass to the template + * + * @return string|false The evaluated template, or false if the renderer is unable to render the template + */ + function evaluate(Storage $template, array $parameters = array()); + + /** + * Sets the template engine associated with this renderer. + * + * @param Engine $engine A Engine instance + */ + function setEngine(Engine $engine); +} diff --git a/src/Symfony/Components/Templating/Storage/FileStorage.php b/src/Symfony/Components/Templating/Storage/FileStorage.php new file mode 100644 index 000000000000..80f870ac4701 --- /dev/null +++ b/src/Symfony/Components/Templating/Storage/FileStorage.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * FileStorage represents a template stored on the filesystem. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class FileStorage extends Storage +{ +} diff --git a/src/Symfony/Components/Templating/Storage/Storage.php b/src/Symfony/Components/Templating/Storage/Storage.php new file mode 100644 index 000000000000..432f4ceb135d --- /dev/null +++ b/src/Symfony/Components/Templating/Storage/Storage.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Storage is the base class for all storage classes. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class Storage +{ + protected + $renderer = null, + $template = ''; + + /** + * Constructor. + * + * @param string $template The template name + */ + public function __construct($template, $renderer = null) + { + $this->template = $template; + $this->renderer = $renderer; + } + + /** + * Returns the object string representation. + * + * @return string The template name + */ + public function __toString() + { + return (string) $this->template; + } + + /** + * Gets the renderer. + * + * @return string|null The renderer name or null if no renderer is stored for this template + */ + public function getRenderer() + { + return $this->renderer; + } +} diff --git a/src/Symfony/Components/Templating/Storage/StringStorage.php b/src/Symfony/Components/Templating/Storage/StringStorage.php new file mode 100644 index 000000000000..12f8251b502a --- /dev/null +++ b/src/Symfony/Components/Templating/Storage/StringStorage.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * StringStorage represents a template stored in a string. + * + * @package symfony + * @subpackage templating + * @author Fabien Potencier + * @version SVN: $Id$ + */ +class StringStorage extends Storage +{ +} diff --git a/src/Symfony/Components/YAML/Dumper.php b/src/Symfony/Components/YAML/Dumper.php new file mode 100644 index 000000000000..c464f5eb3e94 --- /dev/null +++ b/src/Symfony/Components/YAML/Dumper.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Dumper dumps PHP variables to YAML strings. + * + * @package symfony + * @subpackage yaml + * @author Fabien Potencier + * @version SVN: $Id: Dumper.class.php 10575 2008-08-01 13:08:42Z nicolas $ + */ +class Dumper +{ + /** + * Dumps a PHP value to YAML. + * + * @param mixed $input The PHP value + * @param integer $inline The level where you switch to inline YAML + * @param integer $indent The level o indentation indentation (used internally) + * + * @return string The YAML representation of the PHP value + */ + public function dump($input, $inline = 0, $indent = 0) + { + $output = ''; + $prefix = $indent ? str_repeat(' ', $indent) : ''; + + if ($inline <= 0 || !is_array($input) || empty($input)) + { + $output .= $prefix.Inline::dump($input); + } + else + { + $isAHash = array_keys($input) !== range(0, count($input) - 1); + + foreach ($input as $key => $value) + { + $willBeInlined = $inline - 1 <= 0 || !is_array($value) || empty($value); + + $output .= sprintf('%s%s%s%s', + $prefix, + $isAHash ? Inline::dump($key).':' : '-', + $willBeInlined ? ' ' : "\n", + $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + 2) + ).($willBeInlined ? "\n" : ''); + } + } + + return $output; + } +} diff --git a/src/Symfony/Components/YAML/Inline.php b/src/Symfony/Components/YAML/Inline.php new file mode 100644 index 000000000000..2b403ff0e3e6 --- /dev/null +++ b/src/Symfony/Components/YAML/Inline.php @@ -0,0 +1,411 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Inline implements a YAML parser/dumper for the YAML inline syntax. + * + * @package symfony + * @subpackage yaml + * @author Fabien Potencier + * @version SVN: $Id: Inline.class.php 16177 2009-03-11 08:32:48Z fabien $ + */ +class Inline +{ + const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\']*(?:\'\'[^\']*)*)\')'; + + /** + * Convert a YAML string to a PHP array. + * + * @param string $value A YAML string + * + * @return array A PHP array representing the YAML string + */ + static public function load($value) + { + $value = trim($value); + + if (0 == strlen($value)) + { + return ''; + } + + switch ($value[0]) + { + case '[': + return self::parseSequence($value); + case '{': + return self::parseMapping($value); + default: + return self::parseScalar($value); + } + } + + /** + * Dumps a given PHP variable to a YAML string. + * + * @param mixed $value The PHP variable to convert + * + * @return string The YAML string representing the PHP array + */ + static public function dump($value) + { + $trueValues = '1.1' == YAML::getSpecVersion() ? array('true', 'on', '+', 'yes', 'y') : array('true'); + $falseValues = '1.1' == YAML::getSpecVersion() ? array('false', 'off', '-', 'no', 'n') : array('false'); + + switch (true) + { + case is_resource($value): + throw new \InvalidArgumentException('Unable to dump PHP resources in a YAML file.'); + case is_object($value): + return '!!php/object:'.serialize($value); + case is_array($value): + return self::dumpArray($value); + case null === $value: + return 'null'; + case true === $value: + return 'true'; + case false === $value: + return 'false'; + case ctype_digit($value): + return is_string($value) ? "'$value'" : (int) $value; + case is_numeric($value): + return is_infinite($value) ? str_ireplace('INF', '.Inf', strval($value)) : (is_string($value) ? "'$value'" : $value); + case false !== strpos($value, "\n") || false !== strpos($value, "\r"): + return sprintf('"%s"', str_replace(array('"', "\n", "\r"), array('\\"', '\n', '\r'), $value)); + case preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \#] | \A[ - ? | < > = ! % @ ]/x', $value): + return sprintf("'%s'", str_replace('\'', '\'\'', $value)); + case '' == $value: + return "''"; + case preg_match(self::getTimestampRegex(), $value): + return "'$value'"; + case in_array(strtolower($value), $trueValues): + return "'$value'"; + case in_array(strtolower($value), $falseValues): + return "'$value'"; + case in_array(strtolower($value), array('null', '~')): + return "'$value'"; + default: + return $value; + } + } + + /** + * Dumps a PHP array to a YAML string. + * + * @param array $value The PHP array to dump + * + * @return string The YAML string representing the PHP array + */ + static protected function dumpArray($value) + { + // array + $keys = array_keys($value); + if ( + (1 == count($keys) && '0' == $keys[0]) + || + (count($keys) > 1 && array_reduce($keys, function ($v, $w) { return (integer) $v + $w; }, 0) == count($keys) * (count($keys) - 1) / 2)) + { + $output = array(); + foreach ($value as $val) + { + $output[] = self::dump($val); + } + + return sprintf('[%s]', implode(', ', $output)); + } + + // mapping + $output = array(); + foreach ($value as $key => $val) + { + $output[] = sprintf('%s: %s', self::dump($key), self::dump($val)); + } + + return sprintf('{ %s }', implode(', ', $output)); + } + + /** + * Parses a scalar to a YAML string. + * + * @param scalar $scalar + * @param string $delimiters + * @param array $stringDelimiter + * @param integer $i + * @param boolean $evaluate + * + * @return string A YAML string + */ + static public function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true) + { + if (in_array($scalar[$i], $stringDelimiters)) + { + // quoted scalar + $output = self::parseQuotedScalar($scalar, $i); + } + else + { + // "normal" string + if (!$delimiters) + { + $output = substr($scalar, $i); + $i += strlen($output); + + // remove comments + if (false !== $strpos = strpos($output, ' #')) + { + $output = rtrim(substr($output, 0, $strpos)); + } + } + else if (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) + { + $output = $match[1]; + $i += strlen($output); + } + else + { + throw new \InvalidArgumentException(sprintf('Malformed inline YAML string (%s).', $scalar)); + } + + $output = $evaluate ? self::evaluateScalar($output) : $output; + } + + return $output; + } + + /** + * Parses a quoted scalar to YAML. + * + * @param string $scalar + * @param integer $i + * + * @return string A YAML string + */ + static protected function parseQuotedScalar($scalar, &$i) + { + if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/A', substr($scalar, $i), $match)) + { + throw new \InvalidArgumentException(sprintf('Malformed inline YAML string (%s).', substr($scalar, $i))); + } + + $output = substr($match[0], 1, strlen($match[0]) - 2); + + if ('"' == $scalar[$i]) + { + // evaluate the string + $output = str_replace(array('\\"', '\\n', '\\r'), array('"', "\n", "\r"), $output); + } + else + { + // unescape ' + $output = str_replace('\'\'', '\'', $output); + } + + $i += strlen($match[0]); + + return $output; + } + + /** + * Parses a sequence to a YAML string. + * + * @param string $sequence + * @param integer $i + * + * @return string A YAML string + */ + static protected function parseSequence($sequence, &$i = 0) + { + $output = array(); + $len = strlen($sequence); + $i += 1; + + // [foo, bar, ...] + while ($i < $len) + { + switch ($sequence[$i]) + { + case '[': + // nested sequence + $output[] = self::parseSequence($sequence, $i); + break; + case '{': + // nested mapping + $output[] = self::parseMapping($sequence, $i); + break; + case ']': + return $output; + case ',': + case ' ': + break; + default: + $isQuoted = in_array($sequence[$i], array('"', "'")); + $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i); + + if (!$isQuoted && false !== strpos($value, ': ')) + { + // embedded mapping? + try + { + $value = self::parseMapping('{'.$value.'}'); + } + catch (\InvalidArgumentException $e) + { + // no, it's not + } + } + + $output[] = $value; + + --$i; + } + + ++$i; + } + + throw new \InvalidArgumentException(sprintf('Malformed inline YAML string %s', $sequence)); + } + + /** + * Parses a mapping to a YAML string. + * + * @param string $mapping + * @param integer $i + * + * @return string A YAML string + */ + static protected function parseMapping($mapping, &$i = 0) + { + $output = array(); + $len = strlen($mapping); + $i += 1; + + // {foo: bar, bar:foo, ...} + while ($i < $len) + { + switch ($mapping[$i]) + { + case ' ': + case ',': + ++$i; + continue 2; + case '}': + return $output; + } + + // key + $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false); + + // value + $done = false; + while ($i < $len) + { + switch ($mapping[$i]) + { + case '[': + // nested sequence + $output[$key] = self::parseSequence($mapping, $i); + $done = true; + break; + case '{': + // nested mapping + $output[$key] = self::parseMapping($mapping, $i); + $done = true; + break; + case ':': + case ' ': + break; + default: + $output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i); + $done = true; + --$i; + } + + ++$i; + + if ($done) + { + continue 2; + } + } + } + + throw new \InvalidArgumentException(sprintf('Malformed inline YAML string %s', $mapping)); + } + + /** + * Evaluates scalars and replaces magic values. + * + * @param string $scalar + * + * @return string A YAML string + */ + static protected function evaluateScalar($scalar) + { + $scalar = trim($scalar); + + $trueValues = '1.1' == YAML::getSpecVersion() ? array('true', 'on', '+', 'yes', 'y') : array('true'); + $falseValues = '1.1' == YAML::getSpecVersion() ? array('false', 'off', '-', 'no', 'n') : array('false'); + + switch (true) + { + case 'null' == strtolower($scalar): + case '' == $scalar: + case '~' == $scalar: + return null; + case 0 === strpos($scalar, '!str'): + return (string) substr($scalar, 5); + case 0 === strpos($scalar, '! '): + return intval(self::parseScalar(substr($scalar, 2))); + case 0 === strpos($scalar, '!!php/object:'): + return unserialize(substr($scalar, 13)); + case ctype_digit($scalar): + $raw = $scalar; + $cast = intval($scalar); + return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); + case in_array(strtolower($scalar), $trueValues): + return true; + case in_array(strtolower($scalar), $falseValues): + return false; + case is_numeric($scalar): + return '0x' == $scalar[0].$scalar[1] ? hexdec($scalar) : floatval($scalar); + case 0 == strcasecmp($scalar, '.inf'): + case 0 == strcasecmp($scalar, '.NaN'): + return -log(0); + case 0 == strcasecmp($scalar, '-.inf'): + return log(0); + case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): + return floatval(str_replace(',', '', $scalar)); + case preg_match(self::getTimestampRegex(), $scalar): + return strtotime($scalar); + default: + return (string) $scalar; + } + } + + static protected function getTimestampRegex() + { + return <<[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)? + $~x +EOF; + } +} diff --git a/src/Symfony/Components/YAML/Parser.php b/src/Symfony/Components/YAML/Parser.php new file mode 100644 index 000000000000..a99a3e71f795 --- /dev/null +++ b/src/Symfony/Components/YAML/Parser.php @@ -0,0 +1,561 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Parser parses YAML strings to convert them to PHP arrays. + * + * @package symfony + * @subpackage yaml + * @author Fabien Potencier + * @version SVN: $Id: Parser.class.php 10832 2008-08-13 07:46:08Z fabien $ + */ +class Parser +{ + protected + $value = '', + $offset = 0, + $lines = array(), + $currentLineNb = -1, + $currentLine = '', + $refs = array(); + + /** + * Constructor + * + * @param integer $offset The offset of YAML document (used for line numbers in error messages) + */ + public function __construct($offset = 0) + { + $this->offset = $offset; + } + + /** + * Parses a YAML string to a PHP value. + * + * @param string $value A YAML string + * + * @return mixed A PHP value + * + * @throws \InvalidArgumentException If the YAML is not valid + */ + public function parse($value) + { + $this->value = $this->cleanup($value); + $this->currentLineNb = -1; + $this->currentLine = ''; + $this->lines = explode("\n", $this->value); + + $data = array(); + while ($this->moveToNextLine()) + { + if ($this->isCurrentLineEmpty()) + { + continue; + } + + // tab? + if (preg_match('#^\t+#', $this->currentLine)) + { + throw new \InvalidArgumentException(sprintf('A YAML file cannot contain tabs as indentation at line %d (%s).', $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + + $isRef = $isInPlace = $isProcessed = false; + if (preg_match('#^\-(\s+(?P.+?))?\s*$#', $this->currentLine, $values)) + { + if (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#', $values['value'], $matches)) + { + $isRef = $matches['ref']; + $values['value'] = $matches['value']; + } + + // array + if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) + { + $c = $this->getRealCurrentLineNb() + 1; + $parser = new Parser($c); + $parser->refs =& $this->refs; + $data[] = $parser->parse($this->getNextEmbedBlock()); + } + else + { + if (preg_match('/^([^ ]+)\: +({.*?)$/', $values['value'], $matches)) + { + $data[] = array($matches[1] => Inline::load($matches[2])); + } + else + { + $data[] = $this->parseValue($values['value']); + } + } + } + else if (preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ ].*?) *\:(\s+(?P.+?))?\s*$#', $this->currentLine, $values)) + { + $key = Inline::parseScalar($values['key']); + + if ('<<' === $key) + { + if (isset($values['value']) && '*' === substr($values['value'], 0, 1)) + { + $isInPlace = substr($values['value'], 1); + if (!array_key_exists($isInPlace, $this->refs)) + { + throw new \InvalidArgumentException(sprintf('Reference "%s" does not exist at line %s (%s).', $isInPlace, $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + } + else + { + if (isset($values['value']) && $values['value'] !== '') + { + $value = $values['value']; + } + else + { + $value = $this->getNextEmbedBlock(); + } + $c = $this->getRealCurrentLineNb() + 1; + $parser = new Parser($c); + $parser->refs =& $this->refs; + $parsed = $parser->parse($value); + + $merged = array(); + if (!is_array($parsed)) + { + throw new \InvalidArgumentException(sprintf("YAML merge keys used with a scalar value instead of an array at line %s (%s)", $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + else if (isset($parsed[0])) + { + // Numeric array, merge individual elements + foreach (array_reverse($parsed) as $parsedItem) + { + if (!is_array($parsedItem)) + { + throw new \InvalidArgumentException(sprintf("Merge items must be arrays at line %s (%s).", $this->getRealCurrentLineNb() + 1, $parsedItem)); + } + $merged = array_merge($parsedItem, $merged); + } + } + else + { + // Associative array, merge + $merged = array_merge($merge, $parsed); + } + + $isProcessed = $merged; + } + } + else if (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#', $values['value'], $matches)) + { + $isRef = $matches['ref']; + $values['value'] = $matches['value']; + } + + if ($isProcessed) + { + // Merge keys + $data = $isProcessed; + } + // hash + else if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) + { + // if next line is less indented or equal, then it means that the current value is null + if ($this->isNextLineIndented()) + { + $data[$key] = null; + } + else + { + $c = $this->getRealCurrentLineNb() + 1; + $parser = new Parser($c); + $parser->refs =& $this->refs; + $data[$key] = $parser->parse($this->getNextEmbedBlock()); + } + } + else + { + if ($isInPlace) + { + $data = $this->refs[$isInPlace]; + } + else + { + $data[$key] = $this->parseValue($values['value']); + } + } + } + else + { + // one liner? + if (1 == count(explode("\n", rtrim($this->value, "\n")))) + { + $value = Inline::load($this->lines[0]); + if (is_array($value)) + { + $first = reset($value); + if ('*' === substr($first, 0, 1)) + { + $data = array(); + foreach ($value as $alias) + { + $data[] = $this->refs[substr($alias, 1)]; + } + $value = $data; + } + } + + return $value; + } + + switch (preg_last_error()) + { + case PREG_INTERNAL_ERROR: + $error = 'Internal PCRE error on line'; + break; + case PREG_BACKTRACK_LIMIT_ERROR: + $error = 'pcre.backtrack_limit reached on line'; + break; + case PREG_RECURSION_LIMIT_ERROR: + $error = 'pcre.recursion_limit reached on line'; + break; + case PREG_BAD_UTF8_ERROR: + $error = 'Malformed UTF-8 data on line'; + break; + case PREG_BAD_UTF8_OFFSET_ERROR: + $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point on line'; + break; + default: + $error = 'Unable to parse line'; + } + + throw new \InvalidArgumentException(sprintf('%s %d (%s).', $error, $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + + if ($isRef) + { + $this->refs[$isRef] = end($data); + } + } + + return empty($data) ? null : $data; + } + + /** + * Returns the current line number (takes the offset into account). + * + * @return integer The current line number + */ + protected function getRealCurrentLineNb() + { + return $this->currentLineNb + $this->offset; + } + + /** + * Returns the current line indentation. + * + * @return integer The current line indentation + */ + protected function getCurrentLineIndentation() + { + return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' ')); + } + + /** + * Returns the next embed block of YAML. + * + * @return string A YAML string + */ + protected function getNextEmbedBlock() + { + $this->moveToNextLine(); + + $newIndent = $this->getCurrentLineIndentation(); + + if (!$this->isCurrentLineEmpty() && 0 == $newIndent) + { + throw new \InvalidArgumentException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + + $data = array(substr($this->currentLine, $newIndent)); + + while ($this->moveToNextLine()) + { + if ($this->isCurrentLineEmpty()) + { + if ($this->isCurrentLineBlank()) + { + $data[] = substr($this->currentLine, $newIndent); + } + + continue; + } + + $indent = $this->getCurrentLineIndentation(); + + if (preg_match('#^(?P *)$#', $this->currentLine, $match)) + { + // empty line + $data[] = $match['text']; + } + else if ($indent >= $newIndent) + { + $data[] = substr($this->currentLine, $newIndent); + } + else if (0 == $indent) + { + $this->moveToPreviousLine(); + + break; + } + else + { + throw new \InvalidArgumentException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb() + 1, $this->currentLine)); + } + } + + return implode("\n", $data); + } + + /** + * Moves the parser to the next line. + */ + protected function moveToNextLine() + { + if ($this->currentLineNb >= count($this->lines) - 1) + { + return false; + } + + $this->currentLine = $this->lines[++$this->currentLineNb]; + + return true; + } + + /** + * Moves the parser to the previous line. + */ + protected function moveToPreviousLine() + { + $this->currentLine = $this->lines[--$this->currentLineNb]; + } + + /** + * Parses a YAML value. + * + * @param string $value A YAML value + * + * @return mixed A PHP value + */ + protected function parseValue($value) + { + if ('*' === substr($value, 0, 1)) + { + if (false !== $pos = strpos($value, '#')) + { + $value = substr($value, 1, $pos - 2); + } + else + { + $value = substr($value, 1); + } + + if (!array_key_exists($value, $this->refs)) + { + throw new \InvalidArgumentException(sprintf('Reference "%s" does not exist (%s).', $value, $this->currentLine)); + } + return $this->refs[$value]; + } + + if (preg_match('/^(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?$/', $value, $matches)) + { + $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; + + return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), intval(abs($modifiers))); + } + else + { + return Inline::load($value); + } + } + + /** + * Parses a folded scalar. + * + * @param string $separator The separator that was used to begin this folded scalar (| or >) + * @param string $indicator The indicator that was used to begin this folded scalar (+ or -) + * @param integer $indentation The indentation that was used to begin this folded scalar + * + * @return string The text value + */ + protected function parseFoldedScalar($separator, $indicator = '', $indentation = 0) + { + $separator = '|' == $separator ? "\n" : ' '; + $text = ''; + + $notEOF = $this->moveToNextLine(); + + while ($notEOF && $this->isCurrentLineBlank()) + { + $text .= "\n"; + + $notEOF = $this->moveToNextLine(); + } + + if (!$notEOF) + { + return ''; + } + + if (!preg_match('#^(?P'.($indentation ? str_repeat(' ', $indentation) : ' +').')(?P.*)$#', $this->currentLine, $matches)) + { + $this->moveToPreviousLine(); + + return ''; + } + + $textIndent = $matches['indent']; + $previousIndent = 0; + + $text .= $matches['text'].$separator; + while ($this->currentLineNb + 1 < count($this->lines)) + { + $this->moveToNextLine(); + + if (preg_match('#^(?P {'.strlen($textIndent).',})(?P.+)$#', $this->currentLine, $matches)) + { + if (' ' == $separator && $previousIndent != $matches['indent']) + { + $text = substr($text, 0, -1)."\n"; + } + $previousIndent = $matches['indent']; + + $text .= str_repeat(' ', $diff = strlen($matches['indent']) - strlen($textIndent)).$matches['text'].($diff ? "\n" : $separator); + } + else if (preg_match('#^(?P *)$#', $this->currentLine, $matches)) + { + $text .= preg_replace('#^ {1,'.strlen($textIndent).'}#', '', $matches['text'])."\n"; + } + else + { + $this->moveToPreviousLine(); + + break; + } + } + + if (' ' == $separator) + { + // replace last separator by a newline + $text = preg_replace('/ (\n*)$/', "\n$1", $text); + } + + switch ($indicator) + { + case '': + $text = preg_replace('#\n+$#s', "\n", $text); + break; + case '+': + break; + case '-': + $text = preg_replace('#\n+$#s', '', $text); + break; + } + + return $text; + } + + /** + * Returns true if the next line is indented. + * + * @return Boolean Returns true if the next line is indented, false otherwise + */ + protected function isNextLineIndented() + { + $currentIndentation = $this->getCurrentLineIndentation(); + $notEOF = $this->moveToNextLine(); + + while ($notEOF && $this->isCurrentLineEmpty()) + { + $notEOF = $this->moveToNextLine(); + } + + if (false === $notEOF) + { + return false; + } + + $ret = false; + if ($this->getCurrentLineIndentation() <= $currentIndentation) + { + $ret = true; + } + + $this->moveToPreviousLine(); + + return $ret; + } + + /** + * Returns true if the current line is blank or if it is a comment line. + * + * @return Boolean Returns true if the current line is empty or if it is a comment line, false otherwise + */ + protected function isCurrentLineEmpty() + { + return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); + } + + /** + * Returns true if the current line is blank. + * + * @return Boolean Returns true if the current line is blank, false otherwise + */ + protected function isCurrentLineBlank() + { + return '' == trim($this->currentLine, ' '); + } + + /** + * Returns true if the current line is a comment line. + * + * @return Boolean Returns true if the current line is a comment line, false otherwise + */ + protected function isCurrentLineComment() + { + //checking explicitly the first char of the trim is faster than loops or strpos + $ltrimmedLine = ltrim($this->currentLine, ' '); + return $ltrimmedLine[0] === '#'; + } + + /** + * Cleanups a YAML string to be parsed. + * + * @param string $value The input YAML string + * + * @return string A cleaned up YAML string + */ + protected function cleanup($value) + { + $value = str_replace(array("\r\n", "\r"), "\n", $value); + + if (!preg_match("#\n$#", $value)) + { + $value .= "\n"; + } + + // strip YAML header + preg_replace('#^\%YAML[: ][\d\.]+.*\n#s', '', $value); + + // remove --- + $value = preg_replace('#^\-\-\-.*?\n#s', '', $value); + + return $value; + } +} diff --git a/src/Symfony/Components/YAML/YAML.php b/src/Symfony/Components/YAML/YAML.php new file mode 100644 index 000000000000..bea902af0b5a --- /dev/null +++ b/src/Symfony/Components/YAML/YAML.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Yaml offers convenience methods to load and dump YAML. + * + * @package symfony + * @subpackage yaml + * @author Fabien Potencier + * @version SVN: $Id: Yaml.class.php 8988 2008-05-15 20:24:26Z fabien $ + */ +class YAML +{ + static protected + $spec = '1.2'; + + /** + * Sets the YAML specification version to use. + * + * @param string $version The YAML specification version + */ + static public function setSpecVersion($version) + { + if (!in_array($version, array('1.1', '1.2'))) + { + throw new \InvalidArgumentException(sprintf('Version %s of the YAML specifications is not supported', $version)); + } + + self::$spec = $version; + } + + /** + * Gets the YAML specification version to use. + * + * @return string The YAML specification version + */ + static public function getSpecVersion() + { + return self::$spec; + } + + /** + * Loads YAML into a PHP array. + * + * The load method, when supplied with a YAML stream (string or file), + * will do its best to convert YAML in a file into a PHP array. + * + * Usage: + * + * $array = Yaml::load('config.yml'); + * print_r($array); + * + * + * @param string $input Path of YAML file or string containing YAML + * + * @return array The YAML converted to a PHP array + * + * @throws \InvalidArgumentException If the YAML is not valid + */ + public static function load($input) + { + $file = ''; + + // if input is a file, process it + if (strpos($input, "\n") === false && is_file($input)) + { + $file = $input; + + ob_start(); + $retval = include($input); + $content = ob_get_clean(); + + // if an array is returned by the config file assume it's in plain php form else in YAML + $input = is_array($retval) ? $retval : $content; + } + + // if an array is returned by the config file assume it's in plain php form else in YAML + if (is_array($input)) + { + return $input; + } + + $yaml = new Parser(); + + try + { + $ret = $yaml->parse($input); + } + catch (\Exception $e) + { + throw new \InvalidArgumentException(sprintf('Unable to parse %s: %s', $file ? sprintf('file "%s"', $file) : 'string', $e->getMessage())); + } + + return $ret; + } + + /** + * Dumps a PHP array to a YAML string. + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. + * + * @param array $array PHP array + * @param integer $inline The level where you switch to inline YAML + * + * @return string A YAML string representing the original PHP array + */ + public static function dump($array, $inline = 2) + { + $yaml = new Dumper(); + + return $yaml->dump($array, $inline); + } +} + +/** + * Wraps echo to automatically provide a newline. + * + * @param string $string The string to echo with new line + */ +function echoln($string) +{ + echo $string."\n"; +} diff --git a/src/Symfony/Foundation/ClassLoader.php b/src/Symfony/Foundation/ClassLoader.php new file mode 100644 index 000000000000..081776a2ec03 --- /dev/null +++ b/src/Symfony/Foundation/ClassLoader.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * ClassLoader implementation that implements the technical interoperability + * standards for PHP 5.3 namespaces and class names. + * + * Based on http://groups.google.com/group/php-standards/web/final-proposal + * + * Example usage: + * + * [php] + * $loader = new ClassLoader(); + * $loader->registerNamespace('Symfony', __DIR__.'/..'); + * $loader->register(); + * + * @author Jonathan H. Wage + * @author Roman S. Borschel + * @author Matthew Weier O'Phinney + * @author Kris Wallsmith + * @author Fabien Potencier + */ +class ClassLoader +{ + protected $namespaces = array(); + + /** + * Creates a new loader for classes of the specified namespace. + * + * @param string $namespace The namespace to use + * @param string $includePath The path to the namespace + */ + public function registerNamespace($namespace, $includePath = null) + { + if (!isset($this->namespaces[$namespace])) + { + $this->namespaces[$namespace] = $includePath; + } + else + { + if (!is_array($this->namespaces[$namespace])) + { + $this->namespaces[$namespace] = array($this->namespaces[$namespace]); + } + + $this->namespaces[$namespace][] = $includePath; + } + } + + /** + * Installs this class loader on the SPL autoload stack. + */ + public function register() + { + spl_autoload_register(array($this, 'loadClass')); + } + + /** + * Uninstalls this class loader from the SPL autoloader stack. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $className The name of the class to load + */ + public function loadClass($className) + { + $vendor = substr($className, 0, stripos($className, '\\')); + if ($vendor || isset($this->namespaces[''])) + { + $fileName = ''; + $namespace = ''; + if (false !== ($lastNsPos = strripos($className, '\\'))) + { + $namespace = substr($className, 0, $lastNsPos); + $className = substr($className, $lastNsPos + 1); + $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace).DIRECTORY_SEPARATOR; + } + $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className).'.php'; + + if (null !== $this->namespaces[$vendor]) + { + if (is_array($this->namespaces[$vendor])) + { + foreach ($this->namespaces[$vendor] as $dir) + { + if (!file_exists($dir.DIRECTORY_SEPARATOR.$fileName)) + { + continue; + } + + require $dir.DIRECTORY_SEPARATOR.$fileName; + + break; + } + } + else + { + require $this->namespaces[$vendor].DIRECTORY_SEPARATOR.$fileName; + } + } + else + { + require $fileName; + } + } + } +} diff --git a/tests/bin/prove.php b/tests/bin/prove.php new file mode 100644 index 000000000000..a9e22cb8deaf --- /dev/null +++ b/tests/bin/prove.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../lib/vendor/lime/LimeAutoloader.php'; +LimeAutoloader::register(); + +$h = new LimeTestSuite(array( + 'force_colors' => isset($argv) && in_array('--color', $argv), + 'verbose' => isset($argv) && in_array('--verbose', $argv), +)); +$h->base_dir = realpath(__DIR__.'/..'); + +foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__.'/../unit'), RecursiveIteratorIterator::LEAVES_ONLY) as $file) +{ + if (preg_match('/Test\.php$/', $file)) + { + $h->register($file->getRealPath()); + } +} + +exit($h->run() ? 0 : 1); diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/containers/container10.php b/tests/fixtures/Symfony/Components/DependencyInjection/containers/container10.php new file mode 100644 index 000000000000..247eda9b8637 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/containers/container10.php @@ -0,0 +1,14 @@ + + register('foo', 'FooClass')-> + addArgument(new Reference('bar')) +; + +return $container; diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/containers/container8.php b/tests/fixtures/Symfony/Components/DependencyInjection/containers/container8.php new file mode 100644 index 000000000000..113112e99b28 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/containers/container8.php @@ -0,0 +1,12 @@ +setParameters(array( + 'FOO' => 'bar', + 'bar' => 'foo is %foo bar', + 'values' => array(true, false, null, 0, 1000.3, 'true', 'false', 'null'), +)); + +return $container; diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/containers/container9.php b/tests/fixtures/Symfony/Components/DependencyInjection/containers/container9.php new file mode 100644 index 000000000000..aaced5a4b4b1 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/containers/container9.php @@ -0,0 +1,48 @@ + + register('foo', 'FooClass')-> + setConstructor('getInstance')-> + setArguments(array('foo', new Reference('foo.baz'), array('%foo%' => 'foo is %foo%', 'bar' => '%foo%'), true, new Reference('service_container')))-> + setFile(realpath(__DIR__.'/../includes/foo.php'))-> + setShared(false)-> + addMethodCall('setBar', array('bar'))-> + addMethodCall('initialize')-> + setConfigurator('sc_configure') +; +$container-> + register('bar', 'FooClass')-> + setArguments(array('foo', new Reference('foo.baz'), new Parameter('foo_bar')))-> + setShared(true)-> + setConfigurator(array(new Reference('foo.baz'), 'configure')) +; +$container-> + register('foo.baz', '%baz_class%')-> + setConstructor('getInstance')-> + setConfigurator(array('%baz_class%', 'configureStatic1')) +; +$container->register('foo_bar', '%foo_class%'); +$container->setParameters(array( + 'baz_class' => 'BazClass', + 'foo_class' => 'FooClass', + 'foo' => 'bar', + 'foo_bar' => new Reference('foo_bar'), +)); +$container->setAlias('alias_for_foo', 'foo'); +$container-> + register('method_call1', 'FooClass')-> + addMethodCall('setBar', array(new Reference('foo')))-> + addMethodCall('setBar', array(new Reference('foo', Container::NULL_ON_INVALID_REFERENCE)))-> + addMethodCall('setBar', array(new Reference('foo', Container::IGNORE_ON_INVALID_REFERENCE)))-> + addMethodCall('setBar', array(new Reference('foobaz', Container::IGNORE_ON_INVALID_REFERENCE))) +; + +return $container; diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services1.dot b/tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services1.dot new file mode 100644 index 000000000000..8a5df3bcc7d1 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services1.dot @@ -0,0 +1,7 @@ +digraph sc { + ratio="compress" + node [fontsize="11" fontname="Arial" shape="record"]; + edge [fontsize="9" fontname="Arial" color="grey" arrowhead="open" arrowsize="0.5"]; + + node_service_container [label="service_container\nSymfony\\Components\\DependencyInjection\\Builder\n", shape=record, fillcolor="#9999ff", style="filled"]; +} diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services10-1.dot b/tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services10-1.dot new file mode 100644 index 000000000000..cc15227c5060 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services10-1.dot @@ -0,0 +1,10 @@ +digraph sc { + ratio="normal" + node [fontsize="13" fontname="Verdana" shape="square"]; + edge [fontsize="12" fontname="Verdana" color="white" arrowhead="closed" arrowsize="1"]; + + node_foo [label="foo\nFooClass\n", shape=square, fillcolor="grey", style="filled"]; + node_service_container [label="service_container\nSymfony\\Components\\DependencyInjection\\Builder\n", shape=square, fillcolor="green", style="empty"]; + node_bar [label="bar\n\n", shape=square, fillcolor="red", style="empty"]; + node_foo -> node_bar [label="" style="filled"]; +} diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services10.dot b/tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services10.dot new file mode 100644 index 000000000000..17fdadb0af4e --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services10.dot @@ -0,0 +1,10 @@ +digraph sc { + ratio="compress" + node [fontsize="11" fontname="Arial" shape="record"]; + edge [fontsize="9" fontname="Arial" color="grey" arrowhead="open" arrowsize="0.5"]; + + node_foo [label="foo\nFooClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; + node_service_container [label="service_container\nSymfony\\Components\\DependencyInjection\\Builder\n", shape=record, fillcolor="#9999ff", style="filled"]; + node_bar [label="bar\n\n", shape=record, fillcolor="#ff9999", style="filled"]; + node_foo -> node_bar [label="" style="filled"]; +} diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services9.dot b/tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services9.dot new file mode 100644 index 000000000000..84cce5fb37ec --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/graphviz/services9.dot @@ -0,0 +1,21 @@ +digraph sc { + ratio="compress" + node [fontsize="11" fontname="Arial" shape="record"]; + edge [fontsize="9" fontname="Arial" color="grey" arrowhead="open" arrowsize="0.5"]; + + node_foo [label="foo (alias_for_foo)\nFooClass\n", shape=record, fillcolor="#eeeeee", style="dotted"]; + node_bar [label="bar\nFooClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; + node_foo_baz [label="foo.baz\nBazClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; + node_foo_bar [label="foo_bar\nFooClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; + node_method_call1 [label="method_call1\nFooClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; + node_service_container [label="service_container\nSymfony\\Components\\DependencyInjection\\Builder\n", shape=record, fillcolor="#9999ff", style="filled"]; + node_foobaz [label="foobaz\n\n", shape=record, fillcolor="#ff9999", style="filled"]; + node_foo -> node_foo_baz [label="" style="filled"]; + node_foo -> node_service_container [label="" style="filled"]; + node_bar -> node_foo_baz [label="" style="filled"]; + node_bar -> node_foo_bar [label="" style="filled"]; + node_method_call1 -> node_foo [label="setBar()" style="dashed"]; + node_method_call1 -> node_foo [label="setBar()" style="dashed"]; + node_method_call1 -> node_foo [label="setBar()" style="dashed"]; + node_method_call1 -> node_foobaz [label="setBar()" style="dashed"]; +} diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/includes/ProjectExtension.php b/tests/fixtures/Symfony/Components/DependencyInjection/includes/ProjectExtension.php new file mode 100644 index 000000000000..409d1aa003c4 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/includes/ProjectExtension.php @@ -0,0 +1,23 @@ +setDefinition('project.service.bar', new Definition('FooClass')); + $configuration->setParameter('project.parameter.bar', isset($config['foo']) ? $config['foo'] : 'foobar'); + + return $configuration; + } + + public function getNamespace() + { + return 'project'; + } +} diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/includes/classes.php b/tests/fixtures/Symfony/Components/DependencyInjection/includes/classes.php new file mode 100644 index 000000000000..764e259f622a --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/includes/classes.php @@ -0,0 +1,32 @@ +configure(); +} + +class BarClass +{ +} + +class BazClass +{ + public function configure($instance) + { + $instance->configure(); + } + + static public function getInstance() + { + return new self(); + } + + static public function configureStatic($instance) + { + $instance->configure(); + } + + static public function configureStatic1() + { + } +} diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/includes/foo.php b/tests/fixtures/Symfony/Components/DependencyInjection/includes/foo.php new file mode 100644 index 000000000000..841815155826 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/includes/foo.php @@ -0,0 +1,34 @@ +arguments = $arguments; + } + + static public function getInstance($arguments = array()) + { + $obj = new self($arguments); + $obj->called = true; + + return $obj; + } + + public function initialize() + { + $this->initialized = true; + } + + public function configure() + { + $this->configured = true; + } + + public function setBar($value = null) + { + $this->bar = $value; + } +} diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/ini/nonvalid.ini b/tests/fixtures/Symfony/Components/DependencyInjection/ini/nonvalid.ini new file mode 100644 index 000000000000..9f84a6087358 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/ini/nonvalid.ini @@ -0,0 +1,2 @@ +{NOT AN INI FILE} +{JUST A PLAIN TEXT FILE} diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/ini/parameters.ini b/tests/fixtures/Symfony/Components/DependencyInjection/ini/parameters.ini new file mode 100644 index 000000000000..df92f7527f87 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/ini/parameters.ini @@ -0,0 +1,3 @@ +[parameters] + foo = bar + bar = %foo% diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/ini/parameters1.ini b/tests/fixtures/Symfony/Components/DependencyInjection/ini/parameters1.ini new file mode 100644 index 000000000000..e50f7222be70 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/ini/parameters1.ini @@ -0,0 +1,3 @@ +[parameters] + FOO = foo + baz = baz diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/php/services1-1.php b/tests/fixtures/Symfony/Components/DependencyInjection/php/services1-1.php new file mode 100644 index 000000000000..770103b58918 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/php/services1-1.php @@ -0,0 +1,16 @@ +getDefaultParameters()); + } + + /** + * Gets the default parameters. + * + * @return array An array of the default parameters + */ + protected function getDefaultParameters() + { + return array( + 'foo' => 'bar', + 'bar' => 'foo is %foo bar', + 'values' => array( + 0 => true, + 1 => false, + 2 => NULL, + 3 => 0, + 4 => 1000.3, + 5 => 'true', + 6 => 'false', + 7 => 'null', + ), + ); + } +} diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/php/services9.php b/tests/fixtures/Symfony/Components/DependencyInjection/php/services9.php new file mode 100644 index 000000000000..60a41686ed2c --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/php/services9.php @@ -0,0 +1,154 @@ +getDefaultParameters()); + } + + /** + * Gets the 'foo' service. + * + * @return FooClass A FooClass instance. + */ + protected function getFooService() + { + require_once '%path%/foo.php'; + + $instance = call_user_func(array('FooClass', 'getInstance'), 'foo', $this->getService('foo.baz'), array($this->getParameter('foo') => 'foo is '.$this->getParameter('foo'), 'bar' => $this->getParameter('foo')), true, $this); + $instance->setBar('bar'); + $instance->initialize(); + sc_configure($instance); + + return $instance; + } + + /** + * Gets the 'bar' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return FooClass A FooClass instance. + */ + protected function getBarService() + { + if (isset($this->shared['bar'])) return $this->shared['bar']; + + $instance = new FooClass('foo', $this->getService('foo.baz'), $this->getParameter('foo_bar')); + $this->getService('foo.baz')->configure($instance); + + return $this->shared['bar'] = $instance; + } + + /** + * Gets the 'foo.baz' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return Object A %baz_class% instance. + */ + protected function getFoo_BazService() + { + if (isset($this->shared['foo.baz'])) return $this->shared['foo.baz']; + + $instance = call_user_func(array($this->getParameter('baz_class'), 'getInstance')); + call_user_func(array($this->getParameter('baz_class'), 'configureStatic1'), $instance); + + return $this->shared['foo.baz'] = $instance; + } + + /** + * Gets the 'foo_bar' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return Object A %foo_class% instance. + */ + protected function getFooBarService() + { + if (isset($this->shared['foo_bar'])) return $this->shared['foo_bar']; + + $class = $this->getParameter('foo_class'); + $instance = new $class(); + + return $this->shared['foo_bar'] = $instance; + } + + /** + * Gets the 'method_call1' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return FooClass A FooClass instance. + */ + protected function getMethodCall1Service() + { + if (isset($this->shared['method_call1'])) return $this->shared['method_call1']; + + $instance = new FooClass(); + $instance->setBar($this->getService('foo')); + $instance->setBar($this->getService('foo', Container::NULL_ON_INVALID_REFERENCE)); + if ($this->hasService('foo')) + { + $instance->setBar($this->getService('foo', Container::NULL_ON_INVALID_REFERENCE)); + } + if ($this->hasService('foobaz')) + { + $instance->setBar($this->getService('foobaz', Container::NULL_ON_INVALID_REFERENCE)); + } + + return $this->shared['method_call1'] = $instance; + } + + /** + * Gets the alias_for_foo service alias. + * + * @return FooClass An instance of the foo service + */ + protected function getAliasForFooService() + { + return $this->getService('foo'); + } + + /** + * Gets the default parameters. + * + * @return array An array of the default parameters + */ + protected function getDefaultParameters() + { + return array( + 'baz_class' => 'BazClass', + 'foo_class' => 'FooClass', + 'foo' => 'bar', + 'foo_bar' => new Reference('foo_bar'), + ); + } +} diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/xml/nonvalid.xml b/tests/fixtures/Symfony/Components/DependencyInjection/xml/nonvalid.xml new file mode 100644 index 000000000000..e7b5bc973c9e --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/xml/nonvalid.xml @@ -0,0 +1,3 @@ + + + diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/xml/services1.xml b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services1.xml new file mode 100644 index 000000000000..204c70eaa9c3 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services1.xml @@ -0,0 +1,4 @@ + + + + diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/xml/services10.xml b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services10.xml new file mode 100644 index 000000000000..b0336ba996dd --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services10.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/xml/services11.xml b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services11.xml new file mode 100644 index 000000000000..d31cfc1aeaac --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services11.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/xml/services12.xml b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services12.xml new file mode 100644 index 000000000000..97268df7d2dd --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services12.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/xml/services2.xml b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services2.xml new file mode 100644 index 000000000000..e28d7296255a --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services2.xml @@ -0,0 +1,26 @@ + + + + + a string + bar + + 0 + 4 + null + true + true + false + on + off + 1.3 + 1,000.3 + a string + + foo + bar + + + + + diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/xml/services3.xml b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services3.xml new file mode 100644 index 000000000000..e10c77246922 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services3.xml @@ -0,0 +1,11 @@ + + + + + foo + + true + false + + + diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/xml/services4.xml b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services4.xml new file mode 100644 index 000000000000..1f865938c842 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services4.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/xml/services5.xml b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services5.xml new file mode 100644 index 000000000000..c2ef9ddd0b3e --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services5.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/xml/services6.xml b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services6.xml new file mode 100644 index 000000000000..03ceaf26538c --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services6.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + %path%/foo.php + + + foo + + + true + false + + + + + + + + + + + + + + + + + foo + + + true + false + + + + + + diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/xml/services7.xml b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services7.xml new file mode 100644 index 000000000000..edda0a199344 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services7.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/xml/services8.xml b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services8.xml new file mode 100644 index 000000000000..342c7d4a23bf --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services8.xml @@ -0,0 +1,18 @@ + + + + + bar + foo is %%foo bar + + true + false + null + 0 + 1000.3 + true + false + null + + + diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/xml/services9.xml b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services9.xml new file mode 100644 index 000000000000..338a61ad28b3 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/xml/services9.xml @@ -0,0 +1,54 @@ + + + + + BazClass + FooClass + bar + + + + + %path%/foo.php + foo + + + foo is %foo% + %foo% + + true + + + bar + + + + + + foo + + %foo_bar% + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/yaml/nonvalid1.yml b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/nonvalid1.yml new file mode 100644 index 000000000000..4eddb872b850 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/nonvalid1.yml @@ -0,0 +1,2 @@ +foo: + bar diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/yaml/nonvalid2.yml b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/nonvalid2.yml new file mode 100644 index 000000000000..02e4a84d62c4 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/nonvalid2.yml @@ -0,0 +1 @@ +false \ No newline at end of file diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services1.yml b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services1.yml new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services1.yml @@ -0,0 +1 @@ + diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services10.yml b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services10.yml new file mode 100644 index 000000000000..068ab20be62e --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services10.yml @@ -0,0 +1 @@ +project.bar: ~ diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services11.yml b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services11.yml new file mode 100644 index 000000000000..1d180594cf25 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services11.yml @@ -0,0 +1 @@ +foobar.foobar: {} diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services12.yml b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services12.yml new file mode 100644 index 000000000000..b5e215d7aa10 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services12.yml @@ -0,0 +1 @@ +foobar: {} diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services2.yml b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services2.yml new file mode 100644 index 000000000000..d13959c6d4aa --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services2.yml @@ -0,0 +1,9 @@ +parameters: + FOO: bar + values: + - true + - false + - 0 + - 1000.3 + bar: foo + foo_bar: @foo_bar diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services3.yml b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services3.yml new file mode 100644 index 000000000000..8245e65fbc5a --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services3.yml @@ -0,0 +1,5 @@ +parameters: + foo: foo + values: + - true + - false diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services4.yml b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services4.yml new file mode 100644 index 000000000000..ea376bbe5c9f --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services4.yml @@ -0,0 +1,4 @@ +imports: + - { resource: services2.yml } + - { resource: services3.yml } + - { resource: "../ini/parameters.ini", class: Symfony\Components\DependencyInjection\Loader\IniFileLoader } diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services6.yml b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services6.yml new file mode 100644 index 000000000000..20f277e8ee6f --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services6.yml @@ -0,0 +1,20 @@ +services: + foo: { class: FooClass } + baz: { class: BazClass } + shared: { class: FooClass, shared: true } + non_shared: { class: FooClass, shared: false } + constructor: { class: FooClass, constructor: getInstance } + file: { class: FooClass, file: %path%/foo.php } + arguments: { class: FooClass, arguments: [foo, @foo, [true, false]] } + configurator1: { class: FooClass, configurator: sc_configure } + configurator2: { class: FooClass, configurator: [@baz, configure] } + configurator3: { class: FooClass, configurator: [BazClass, configureStatic] } + method_call1: + class: FooClass + calls: + - [ setBar, [] ] + method_call2: + class: FooClass + calls: + - [ setBar, [ foo, @foo, [true, false] ] ] + alias_for_foo: @foo diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services7.yml b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services7.yml new file mode 100644 index 000000000000..6b49652a4b63 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services7.yml @@ -0,0 +1,2 @@ +services: + foo: { class: BarClass } diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services8.yml b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services8.yml new file mode 100644 index 000000000000..c1cd7a91483f --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services8.yml @@ -0,0 +1,5 @@ +parameters: + foo: bar + bar: 'foo is %%foo bar' + values: [true, false, null, 0, 1000.3, 'true', 'false', 'null'] + diff --git a/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services9.yml b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services9.yml new file mode 100644 index 000000000000..c3a5ba0c30b2 --- /dev/null +++ b/tests/fixtures/Symfony/Components/DependencyInjection/yaml/services9.yml @@ -0,0 +1,37 @@ +parameters: + baz_class: BazClass + foo_class: FooClass + foo: bar + foo_bar: '@foo_bar' + +services: + foo: + class: FooClass + file: %path%/foo.php + constructor: getInstance + arguments: [foo, '@foo.baz', { '%foo%': 'foo is %foo%', bar: '%foo%' }, true, '@service_container'] + calls: + - [setBar, [bar]] + - [initialize, { }] + + shared: false + configurator: sc_configure + bar: + class: FooClass + arguments: [foo, '@foo.baz', '%foo_bar%'] + configurator: ['@foo.baz', configure] + foo.baz: + class: %baz_class% + constructor: getInstance + configurator: ['%baz_class%', configureStatic1] + foo_bar: + class: %foo_class% + method_call1: + class: FooClass + calls: + - [setBar, ['@foo']] + - [setBar, ['@@foo']] + - [setBar, ['@@foo']] + - [setBar, ['@@foobaz']] + + alias_for_foo: @foo diff --git a/tests/fixtures/Symfony/Components/Templating/templates/foo.php b/tests/fixtures/Symfony/Components/Templating/templates/foo.php new file mode 100644 index 000000000000..dbdaa21b52f8 --- /dev/null +++ b/tests/fixtures/Symfony/Components/Templating/templates/foo.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/fixtures/Symfony/Components/YAML/YtsAnchorAlias.yml b/tests/fixtures/Symfony/Components/YAML/YtsAnchorAlias.yml new file mode 100644 index 000000000000..5f9c94275b7d --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/YtsAnchorAlias.yml @@ -0,0 +1,31 @@ +--- %YAML:1.0 +test: Simple Alias Example +brief: > + If you need to refer to the same item of data twice, + you can give that item an alias. The alias is a plain + string, starting with an ampersand. The item may then + be referred to by the alias throughout your document + by using an asterisk before the name of the alias. + This is called an anchor. +yaml: | + - &showell Steve + - Clark + - Brian + - Oren + - *showell +php: | + array('Steve', 'Clark', 'Brian', 'Oren', 'Steve') + +--- +test: Alias of a Mapping +brief: > + An alias can be used on any item of data, including + sequences, mappings, and other complex data types. +yaml: | + - &hello + Meat: pork + Starch: potato + - banana + - *hello +php: | + array(array('Meat'=>'pork', 'Starch'=>'potato'), 'banana', array('Meat'=>'pork', 'Starch'=>'potato')) diff --git a/tests/fixtures/Symfony/Components/YAML/YtsBasicTests.yml b/tests/fixtures/Symfony/Components/YAML/YtsBasicTests.yml new file mode 100644 index 000000000000..eafee681d1f2 --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/YtsBasicTests.yml @@ -0,0 +1,178 @@ +--- %YAML:1.0 +test: Simple Sequence +brief: | + You can specify a list in YAML by placing each + member of the list on a new line with an opening + dash. These lists are called sequences. +yaml: | + - apple + - banana + - carrot +php: | + array('apple', 'banana', 'carrot') +--- +test: Nested Sequences +brief: | + You can include a sequence within another + sequence by giving the sequence an empty + dash, followed by an indented list. +yaml: | + - + - foo + - bar + - baz +php: | + array(array('foo', 'bar', 'baz')) +--- +test: Mixed Sequences +brief: | + Sequences can contain any YAML data, + including strings and other sequences. +yaml: | + - apple + - + - foo + - bar + - x123 + - banana + - carrot +php: | + array('apple', array('foo', 'bar', 'x123'), 'banana', 'carrot') +--- +test: Deeply Nested Sequences +brief: | + Sequences can be nested even deeper, with each + level of indentation representing a level of + depth. +yaml: | + - + - + - uno + - dos +php: | + array(array(array('uno', 'dos'))) +--- +test: Simple Mapping +brief: | + You can add a keyed list (also known as a dictionary or + hash) to your document by placing each member of the + list on a new line, with a colon seperating the key + from its value. In YAML, this type of list is called + a mapping. +yaml: | + foo: whatever + bar: stuff +php: | + array('foo' => 'whatever', 'bar' => 'stuff') +--- +test: Sequence in a Mapping +brief: | + A value in a mapping can be a sequence. +yaml: | + foo: whatever + bar: + - uno + - dos +php: | + array('foo' => 'whatever', 'bar' => array('uno', 'dos')) +--- +test: Nested Mappings +brief: | + A value in a mapping can be another mapping. +yaml: | + foo: whatever + bar: + fruit: apple + name: steve + sport: baseball +php: | + array( + 'foo' => 'whatever', + 'bar' => array( + 'fruit' => 'apple', + 'name' => 'steve', + 'sport' => 'baseball' + ) + ) +--- +test: Mixed Mapping +brief: | + A mapping can contain any assortment + of mappings and sequences as values. +yaml: | + foo: whatever + bar: + - + fruit: apple + name: steve + sport: baseball + - more + - + python: rocks + perl: papers + ruby: scissorses +php: | + array( + 'foo' => 'whatever', + 'bar' => array( + array( + 'fruit' => 'apple', + 'name' => 'steve', + 'sport' => 'baseball' + ), + 'more', + array( + 'python' => 'rocks', + 'perl' => 'papers', + 'ruby' => 'scissorses' + ) + ) + ) +--- +test: Mapping-in-Sequence Shortcut +todo: true +brief: | + If you are adding a mapping to a sequence, you + can place the mapping on the same line as the + dash as a shortcut. +yaml: | + - work on YAML.py: + - work on Store +php: | + array(array('work on YAML.py' => array('work on Store'))) +--- +test: Sequence-in-Mapping Shortcut +todo: true +brief: | + The dash in a sequence counts as indentation, so + you can add a sequence inside of a mapping without + needing spaces as indentation. +yaml: | + allow: + - 'localhost' + - '%.sourceforge.net' + - '%.freepan.org' +php: | + array('allow' => array('localhost', '%.sourceforge.net', '%.freepan.org')) +--- +todo: true +test: Merge key +brief: | + A merge key ('<<') can be used in a mapping to insert other mappings. If + the value associated with the merge key is a mapping, each of its key/value + pairs is inserted into the current mapping. +yaml: | + mapping: + name: Joe + job: Accountant + <<: + age: 38 +php: | + array( + 'mapping' => + array( + 'name' => 'Joe', + 'job' => 'Accountant', + 'age' => 38 + ) + ) diff --git a/tests/fixtures/Symfony/Components/YAML/YtsBlockMapping.yml b/tests/fixtures/Symfony/Components/YAML/YtsBlockMapping.yml new file mode 100644 index 000000000000..3a2d410c9de4 --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/YtsBlockMapping.yml @@ -0,0 +1,52 @@ +--- +test: One Element Mapping +brief: | + A mapping with one key/value pair +yaml: | + foo: bar +php: | + array('foo' => 'bar') +--- +test: Multi Element Mapping +brief: | + More than one key/value pair +yaml: | + red: baron + white: walls + blue: berries +php: | + array( + 'red' => 'baron', + 'white' => 'walls', + 'blue' => 'berries', + ) +--- +test: Values aligned +brief: | + Often times human editors of documents will align the values even + though YAML emitters generally don't. +yaml: | + red: baron + white: walls + blue: berries +php: | + array( + 'red' => 'baron', + 'white' => 'walls', + 'blue' => 'berries', + ) +--- +test: Colons aligned +brief: | + Spaces can come before the ': ' key/value separator. +yaml: | + red : baron + white : walls + blue : berries +php: | + array( + 'red' => 'baron', + 'white' => 'walls', + 'blue' => 'berries', + ) + \ No newline at end of file diff --git a/tests/fixtures/Symfony/Components/YAML/YtsDocumentSeparator.yml b/tests/fixtures/Symfony/Components/YAML/YtsDocumentSeparator.yml new file mode 100644 index 000000000000..d0e3877f97f8 --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/YtsDocumentSeparator.yml @@ -0,0 +1,85 @@ +--- %YAML:1.0 +test: Trailing Document Separator +todo: true +brief: > + You can separate YAML documents + with a string of three dashes. +yaml: | + - foo: 1 + bar: 2 + --- + more: stuff +python: | + [ + [ { 'foo': 1, 'bar': 2 } ], + { 'more': 'stuff' } + ] +ruby: | + [ { 'foo' => 1, 'bar' => 2 } ] + +--- +test: Leading Document Separator +todo: true +brief: > + You can explicity give an opening + document separator to your YAML stream. +yaml: | + --- + - foo: 1 + bar: 2 + --- + more: stuff +python: | + [ + [ {'foo': 1, 'bar': 2}], + {'more': 'stuff'} + ] +ruby: | + [ { 'foo' => 1, 'bar' => 2 } ] + +--- +test: YAML Header +todo: true +brief: > + The opening separator can contain directives + to the YAML parser, such as the version + number. +yaml: | + --- %YAML:1.0 + foo: 1 + bar: 2 +php: | + array('foo' => 1, 'bar' => 2) +documents: 1 + +--- +test: Red Herring Document Separator +brief: > + Separators included in blocks or strings + are treated as blocks or strings, as the + document separator should have no indentation + preceding it. +yaml: | + foo: | + --- +php: | + array('foo' => "---\n") + +--- +test: Multiple Document Separators in Block +brief: > + This technique allows you to embed other YAML + documents within literal blocks. +yaml: | + foo: | + --- + foo: bar + --- + yo: baz + bar: | + fooness +php: | + array( + 'foo' => "---\nfoo: bar\n---\nyo: baz\n", + 'bar' => "fooness\n" + ) diff --git a/tests/fixtures/Symfony/Components/YAML/YtsErrorTests.yml b/tests/fixtures/Symfony/Components/YAML/YtsErrorTests.yml new file mode 100644 index 000000000000..c0d6d6d3bb27 --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/YtsErrorTests.yml @@ -0,0 +1,26 @@ +--- +test: Missing value for hash item +todo: true +brief: | + Third item in this hash doesn't have a value +yaml: | + okay: value + also okay: ~ + causes error because no value specified + last key: value okay here too +python-error: causes error because no value specified + +--- +test: Not indenting enough +brief: | + There was a bug in PyYaml where it was off by one + in the indentation check. It was allowing the YAML + below. +# This is actually valid YAML now. Someone should tell showell. +yaml: | + foo: + firstline: 1 + secondline: 2 +php: | + array('foo' => null, 'firstline' => 1, 'secondline' => 2) + diff --git a/tests/fixtures/Symfony/Components/YAML/YtsFlowCollections.yml b/tests/fixtures/Symfony/Components/YAML/YtsFlowCollections.yml new file mode 100644 index 000000000000..56bf6b87f496 --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/YtsFlowCollections.yml @@ -0,0 +1,60 @@ +--- +test: Simple Inline Array +brief: > + Sequences can be contained on a + single line, using the inline syntax. + Separate each entry with commas and + enclose in square brackets. +yaml: | + seq: [ a, b, c ] +php: | + array('seq' => array('a', 'b', 'c')) +--- +test: Simple Inline Hash +brief: > + Mapping can also be contained on + a single line, using the inline + syntax. Each key-value pair is + separated by a colon, with a comma + between each entry in the mapping. + Enclose with curly braces. +yaml: | + hash: { name: Steve, foo: bar } +php: | + array('hash' => array('name' => 'Steve', 'foo' => 'bar')) +--- +test: Multi-line Inline Collections +todo: true +brief: > + Both inline sequences and inline mappings + can span multiple lines, provided that you + indent the additional lines. +yaml: | + languages: [ Ruby, + Perl, + Python ] + websites: { YAML: yaml.org, + Ruby: ruby-lang.org, + Python: python.org, + Perl: use.perl.org } +php: | + array( + 'languages' => array('Ruby', 'Perl', 'Python'), + 'websites' => array( + 'YAML' => 'yaml.org', + 'Ruby' => 'ruby-lang.org', + 'Python' => 'python.org', + 'Perl' => 'use.perl.org' + ) + ) +--- +test: Commas in Values (not in the spec!) +todo: true +brief: > + List items in collections are delimited by commas, but + there must be a space after each comma. This allows you + to add numbers without quoting. +yaml: | + attendances: [ 45,123, 70,000, 17,222 ] +php: | + array('attendances' => array(45123, 70000, 17222)) diff --git a/tests/fixtures/Symfony/Components/YAML/YtsFoldedScalars.yml b/tests/fixtures/Symfony/Components/YAML/YtsFoldedScalars.yml new file mode 100644 index 000000000000..d1432f63b961 --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/YtsFoldedScalars.yml @@ -0,0 +1,176 @@ +--- %YAML:1.0 +test: Single ending newline +brief: > + A pipe character, followed by an indented + block of text is treated as a literal + block, in which newlines are preserved + throughout the block, including the final + newline. +yaml: | + --- + this: | + Foo + Bar +php: | + array('this' => "Foo\nBar\n") +--- +test: The '+' indicator +brief: > + The '+' indicator says to keep newlines at the end of text + blocks. +yaml: | + normal: | + extra new lines not kept + + preserving: |+ + extra new lines are kept + + + dummy: value +php: | + array( + 'normal' => "extra new lines not kept\n", + 'preserving' => "extra new lines are kept\n\n\n", + 'dummy' => 'value' + ) +--- +test: Three trailing newlines in literals +brief: > + To give you more control over how space + is preserved in text blocks, YAML has + the keep '+' and chomp '-' indicators. + The keep indicator will preserve all + ending newlines, while the chomp indicator + will strip all ending newlines. +yaml: | + clipped: | + This has one newline. + + + + same as "clipped" above: "This has one newline.\n" + + stripped: |- + This has no newline. + + + + same as "stripped" above: "This has no newline." + + kept: |+ + This has four newlines. + + + + same as "kept" above: "This has four newlines.\n\n\n\n" +php: | + array( + 'clipped' => "This has one newline.\n", + 'same as "clipped" above' => "This has one newline.\n", + 'stripped' => 'This has no newline.', + 'same as "stripped" above' => 'This has no newline.', + 'kept' => "This has four newlines.\n\n\n\n", + 'same as "kept" above' => "This has four newlines.\n\n\n\n" + ) +--- +test: Extra trailing newlines with spaces +todo: true +brief: > + Normally, only a single newline is kept + from the end of a literal block, unless the + keep '+' character is used in combination + with the pipe. The following example + will preserve all ending whitespace + since the last line of both literal blocks + contains spaces which extend past the indentation + level. +yaml: | + --- + this: | + Foo + + + kept: |+ + Foo + + +php: | + array('this' => "Foo\n\n \n", + 'kept' => "Foo\n\n \n" ) + +--- +test: Folded Block in a Sequence +brief: > + A greater-then character, followed by an indented + block of text is treated as a folded block, in + which lines of text separated by a single newline + are concatenated as a single line. +yaml: | + --- + - apple + - banana + - > + can't you see + the beauty of yaml? + hmm + - dog +php: | + array( + 'apple', + 'banana', + "can't you see the beauty of yaml? hmm\n", + 'dog' + ) +--- +test: Folded Block as a Mapping Value +brief: > + Both literal and folded blocks can be + used in collections, as values in a + sequence or a mapping. +yaml: | + --- + quote: > + Mark McGwire's + year was crippled + by a knee injury. + source: espn +php: | + array( + 'quote' => "Mark McGwire's year was crippled by a knee injury.\n", + 'source' => 'espn' + ) +--- +test: Three trailing newlines in folded blocks +brief: > + The keep and chomp indicators can also + be applied to folded blocks. +yaml: | + clipped: > + This has one newline. + + + + same as "clipped" above: "This has one newline.\n" + + stripped: >- + This has no newline. + + + + same as "stripped" above: "This has no newline." + + kept: >+ + This has four newlines. + + + + same as "kept" above: "This has four newlines.\n\n\n\n" +php: | + array( + 'clipped' => "This has one newline.\n", + 'same as "clipped" above' => "This has one newline.\n", + 'stripped' => 'This has no newline.', + 'same as "stripped" above' => 'This has no newline.', + 'kept' => "This has four newlines.\n\n\n\n", + 'same as "kept" above' => "This has four newlines.\n\n\n\n" + ) diff --git a/tests/fixtures/Symfony/Components/YAML/YtsNullsAndEmpties.yml b/tests/fixtures/Symfony/Components/YAML/YtsNullsAndEmpties.yml new file mode 100644 index 000000000000..4e0c8dbf1cd4 --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/YtsNullsAndEmpties.yml @@ -0,0 +1,45 @@ +--- %YAML:1.0 +test: Empty Sequence +brief: > + You can represent the empty sequence + with an empty inline sequence. +yaml: | + empty: [] +php: | + array('empty' => array()) +--- +test: Empty Mapping +brief: > + You can represent the empty mapping + with an empty inline mapping. +yaml: | + empty: {} +php: | + array('empty' => array()) +--- +test: Empty Sequence as Entire Document +yaml: | + [] +php: | + array() +--- +test: Empty Mapping as Entire Document +yaml: | + {} +php: | + array() +--- +test: Null as Document +yaml: | + ~ +php: | + null +--- +test: Empty String +brief: > + You can represent an empty string + with a pair of quotes. +yaml: | + '' +php: | + '' diff --git a/tests/fixtures/Symfony/Components/YAML/YtsSpecificationExamples.yml b/tests/fixtures/Symfony/Components/YAML/YtsSpecificationExamples.yml new file mode 100644 index 000000000000..c975fe5892f1 --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/YtsSpecificationExamples.yml @@ -0,0 +1,1681 @@ +--- %YAML:1.0 +test: Sequence of scalars +spec: 2.1 +yaml: | + - Mark McGwire + - Sammy Sosa + - Ken Griffey +php: | + array('Mark McGwire', 'Sammy Sosa', 'Ken Griffey') +--- +test: Mapping of scalars to scalars +spec: 2.2 +yaml: | + hr: 65 + avg: 0.278 + rbi: 147 +php: | + array('hr' => 65, 'avg' => 0.278, 'rbi' => 147) +--- +test: Mapping of scalars to sequences +spec: 2.3 +yaml: | + american: + - Boston Red Sox + - Detroit Tigers + - New York Yankees + national: + - New York Mets + - Chicago Cubs + - Atlanta Braves +php: | + array('american' => + array( 'Boston Red Sox', 'Detroit Tigers', + 'New York Yankees' ), + 'national' => + array( 'New York Mets', 'Chicago Cubs', + 'Atlanta Braves' ) + ) +--- +test: Sequence of mappings +spec: 2.4 +yaml: | + - + name: Mark McGwire + hr: 65 + avg: 0.278 + - + name: Sammy Sosa + hr: 63 + avg: 0.288 +php: | + array( + array('name' => 'Mark McGwire', 'hr' => 65, 'avg' => 0.278), + array('name' => 'Sammy Sosa', 'hr' => 63, 'avg' => 0.288) + ) +--- +test: Legacy A5 +todo: true +spec: legacy_A5 +yaml: | + ? + - New York Yankees + - Atlanta Braves + : + - 2001-07-02 + - 2001-08-12 + - 2001-08-14 + ? + - Detroit Tigers + - Chicago Cubs + : + - 2001-07-23 +perl-busted: > + YAML.pm will be able to emulate this behavior soon. In this regard + it may be somewhat more correct than Python's native behaviour which + can only use tuples as mapping keys. PyYAML will also need to figure + out some clever way to roundtrip structured keys. +python: | + [ + { + ('New York Yankees', 'Atlanta Braves'): + [yaml.timestamp('2001-07-02'), + yaml.timestamp('2001-08-12'), + yaml.timestamp('2001-08-14')], + ('Detroit Tigers', 'Chicago Cubs'): + [yaml.timestamp('2001-07-23')] + } + ] +ruby: | + { + [ 'New York Yankees', 'Atlanta Braves' ] => + [ Date.new( 2001, 7, 2 ), Date.new( 2001, 8, 12 ), Date.new( 2001, 8, 14 ) ], + [ 'Detroit Tigers', 'Chicago Cubs' ] => + [ Date.new( 2001, 7, 23 ) ] + } +syck: | + struct test_node seq1[] = { + { T_STR, 0, "New York Yankees" }, + { T_STR, 0, "Atlanta Braves" }, + end_node + }; + struct test_node seq2[] = { + { T_STR, 0, "2001-07-02" }, + { T_STR, 0, "2001-08-12" }, + { T_STR, 0, "2001-08-14" }, + end_node + }; + struct test_node seq3[] = { + { T_STR, 0, "Detroit Tigers" }, + { T_STR, 0, "Chicago Cubs" }, + end_node + }; + struct test_node seq4[] = { + { T_STR, 0, "2001-07-23" }, + end_node + }; + struct test_node map[] = { + { T_SEQ, 0, 0, seq1 }, + { T_SEQ, 0, 0, seq2 }, + { T_SEQ, 0, 0, seq3 }, + { T_SEQ, 0, 0, seq4 }, + end_node + }; + struct test_node stream[] = { + { T_MAP, 0, 0, map }, + end_node + }; + +--- +test: Sequence of sequences +spec: 2.5 +yaml: | + - [ name , hr , avg ] + - [ Mark McGwire , 65 , 0.278 ] + - [ Sammy Sosa , 63 , 0.288 ] +php: | + array( + array( 'name', 'hr', 'avg' ), + array( 'Mark McGwire', 65, 0.278 ), + array( 'Sammy Sosa', 63, 0.288 ) + ) +--- +test: Mapping of mappings +todo: true +spec: 2.6 +yaml: | + Mark McGwire: {hr: 65, avg: 0.278} + Sammy Sosa: { + hr: 63, + avg: 0.288 + } +ruby: | + array( + 'Mark McGwire' => + array( 'hr' => 65, 'avg' => 0.278 ), + 'Sammy Sosa' => + array( 'hr' => 63, 'avg' => 0.288 ) + ) +--- +test: Two documents in a stream each with a leading comment +todo: true +spec: 2.7 +yaml: | + # Ranking of 1998 home runs + --- + - Mark McGwire + - Sammy Sosa + - Ken Griffey + + # Team ranking + --- + - Chicago Cubs + - St Louis Cardinals +ruby: | + y = YAML::Stream.new + y.add( [ 'Mark McGwire', 'Sammy Sosa', 'Ken Griffey' ] ) + y.add( [ 'Chicago Cubs', 'St Louis Cardinals' ] ) +documents: 2 + +--- +test: Play by play feed from a game +todo: true +spec: 2.8 +yaml: | + --- + time: 20:03:20 + player: Sammy Sosa + action: strike (miss) + ... + --- + time: 20:03:47 + player: Sammy Sosa + action: grand slam + ... +perl: | + [ 'Mark McGwire', 'Sammy Sosa', 'Ken Griffey' ] +documents: 2 + +--- +test: Single document with two comments +spec: 2.9 +yaml: | + hr: # 1998 hr ranking + - Mark McGwire + - Sammy Sosa + rbi: + # 1998 rbi ranking + - Sammy Sosa + - Ken Griffey +php: | + array( + 'hr' => array( 'Mark McGwire', 'Sammy Sosa' ), + 'rbi' => array( 'Sammy Sosa', 'Ken Griffey' ) + ) +--- +test: Node for Sammy Sosa appears twice in this document +spec: 2.10 +yaml: | + --- + hr: + - Mark McGwire + # Following node labeled SS + - &SS Sammy Sosa + rbi: + - *SS # Subsequent occurance + - Ken Griffey +php: | + array( + 'hr' => + array('Mark McGwire', 'Sammy Sosa'), + 'rbi' => + array('Sammy Sosa', 'Ken Griffey') + ) +--- +test: Mapping between sequences +todo: true +spec: 2.11 +yaml: | + ? # PLAY SCHEDULE + - Detroit Tigers + - Chicago Cubs + : + - 2001-07-23 + + ? [ New York Yankees, + Atlanta Braves ] + : [ 2001-07-02, 2001-08-12, + 2001-08-14 ] +ruby: | + { + [ 'Detroit Tigers', 'Chicago Cubs' ] => [ Date.new( 2001, 7, 23 ) ], + [ 'New York Yankees', 'Atlanta Braves' ] => [ Date.new( 2001, 7, 2 ), Date.new( 2001, 8, 12 ), Date.new( 2001, 8, 14 ) ] + } +syck: | + struct test_node seq1[] = { + { T_STR, 0, "New York Yankees" }, + { T_STR, 0, "Atlanta Braves" }, + end_node + }; + struct test_node seq2[] = { + { T_STR, 0, "2001-07-02" }, + { T_STR, 0, "2001-08-12" }, + { T_STR, 0, "2001-08-14" }, + end_node + }; + struct test_node seq3[] = { + { T_STR, 0, "Detroit Tigers" }, + { T_STR, 0, "Chicago Cubs" }, + end_node + }; + struct test_node seq4[] = { + { T_STR, 0, "2001-07-23" }, + end_node + }; + struct test_node map[] = { + { T_SEQ, 0, 0, seq3 }, + { T_SEQ, 0, 0, seq4 }, + { T_SEQ, 0, 0, seq1 }, + { T_SEQ, 0, 0, seq2 }, + end_node + }; + struct test_node stream[] = { + { T_MAP, 0, 0, map }, + end_node + }; + +--- +test: Sequence key shortcut +todo: true +spec: 2.12 +yaml: | + --- + # products purchased + - item : Super Hoop + quantity: 1 + - item : Basketball + quantity: 4 + - item : Big Shoes + quantity: 1 +perl: | + [ + { item => 'Super Hoop', quantity => 1 }, + { item => 'Basketball', quantity => 4 }, + { item => 'Big Shoes', quantity => 1 } + ] + +ruby: | + [ + { 'item' => 'Super Hoop', 'quantity' => 1 }, + { 'item' => 'Basketball', 'quantity' => 4 }, + { 'item' => 'Big Shoes', 'quantity' => 1 } + ] +python: | + [ + { 'item': 'Super Hoop', 'quantity': 1 }, + { 'item': 'Basketball', 'quantity': 4 }, + { 'item': 'Big Shoes', 'quantity': 1 } + ] +syck: | + struct test_node map1[] = { + { T_STR, 0, "item" }, + { T_STR, 0, "Super Hoop" }, + { T_STR, 0, "quantity" }, + { T_STR, 0, "1" }, + end_node + }; + struct test_node map2[] = { + { T_STR, 0, "item" }, + { T_STR, 0, "Basketball" }, + { T_STR, 0, "quantity" }, + { T_STR, 0, "4" }, + end_node + }; + struct test_node map3[] = { + { T_STR, 0, "item" }, + { T_STR, 0, "Big Shoes" }, + { T_STR, 0, "quantity" }, + { T_STR, 0, "1" }, + end_node + }; + struct test_node seq[] = { + { T_MAP, 0, 0, map1 }, + { T_MAP, 0, 0, map2 }, + { T_MAP, 0, 0, map3 }, + end_node + }; + struct test_node stream[] = { + { T_SEQ, 0, 0, seq }, + end_node + }; + + +--- +test: Literal perserves newlines +todo: true +spec: 2.13 +yaml: | + # ASCII Art + --- | + \//||\/|| + // || ||_ +perl: | + "\\//||\\/||\n// || ||_\n" +ruby: | + "\\//||\\/||\n// || ||_\n" +python: | + [ + flushLeft( + """ + \//||\/|| + // || ||_ + """ + ) + ] +syck: | + struct test_node stream[] = { + { T_STR, 0, "\\//||\\/||\n// || ||_\n" }, + end_node + }; + +--- +test: Folded treats newlines as a space +todo: true +spec: 2.14 +yaml: | + --- + Mark McGwire's + year was crippled + by a knee injury. +perl: | + "Mark McGwire's year was crippled by a knee injury." +ruby: | + "Mark McGwire's year was crippled by a knee injury." +python: | + [ "Mark McGwire's year was crippled by a knee injury." ] +syck: | + struct test_node stream[] = { + { T_STR, 0, "Mark McGwire's year was crippled by a knee injury." }, + end_node + }; + +--- +test: Newlines preserved for indented and blank lines +todo: true +spec: 2.15 +yaml: | + --- > + Sammy Sosa completed another + fine season with great stats. + + 63 Home Runs + 0.288 Batting Average + + What a year! +perl: | + "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n" +ruby: | + "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n" +python: | + [ + flushLeft( + """ + Sammy Sosa completed another fine season with great stats. + + 63 Home Runs + 0.288 Batting Average + + What a year! + """ + ) + ] +syck: | + struct test_node stream[] = { + { T_STR, 0, "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n" }, + end_node + }; + + +--- +test: Indentation determines scope +spec: 2.16 +yaml: | + name: Mark McGwire + accomplishment: > + Mark set a major league + home run record in 1998. + stats: | + 65 Home Runs + 0.278 Batting Average +php: | + array( + 'name' => 'Mark McGwire', + 'accomplishment' => "Mark set a major league home run record in 1998.\n", + 'stats' => "65 Home Runs\n0.278 Batting Average\n" + ) +--- +test: Quoted scalars +todo: true +spec: 2.17 +yaml: | + unicode: "Sosa did fine.\u263A" + control: "\b1998\t1999\t2000\n" + hexesc: "\x0D\x0A is \r\n" + + single: '"Howdy!" he cried.' + quoted: ' # not a ''comment''.' + tie-fighter: '|\-*-/|' +ruby: | + { + "tie-fighter" => "|\\-*-/|", + "control"=>"\0101998\t1999\t2000\n", + "unicode"=>"Sosa did fine." + ["263A".hex ].pack('U*'), + "quoted"=>" # not a 'comment'.", + "single"=>"\"Howdy!\" he cried.", + "hexesc"=>"\r\n is \r\n" + } +--- +test: Multiline flow scalars +todo: true +spec: 2.18 +yaml: | + plain: + This unquoted scalar + spans many lines. + + quoted: "So does this + quoted scalar.\n" +ruby: | + { + 'plain' => 'This unquoted scalar spans many lines.', + 'quoted' => "So does this quoted scalar.\n" + } +--- +test: Integers +spec: 2.19 +yaml: | + canonical: 12345 + decimal: +12,345 + octal: 014 + hexadecimal: 0xC +php: | + array( + 'canonical' => 12345, + 'decimal' => 12345, + 'octal' => 014, + 'hexadecimal' => 0xC + ) +--- +# FIX: spec shows parens around -inf and NaN +test: Floating point +spec: 2.20 +yaml: | + canonical: 1.23015e+3 + exponential: 12.3015e+02 + fixed: 1,230.15 + negative infinity: -.inf + not a number: .NaN +php: | + array( + 'canonical' => 1230.15, + 'exponential' => 1230.15, + 'fixed' => 1230.15, + 'negative infinity' => log(0), + 'not a number' => -log(0), + ) +--- +test: Miscellaneous +spec: 2.21 +yaml: | + null: ~ + true: y + false: n + string: '12345' +php: | + array( + '' => null, + 1 => true, + 0 => false, + 'string' => '12345' + ) +--- +test: Timestamps +todo: true +spec: 2.22 +yaml: | + canonical: 2001-12-15T02:59:43.1Z + iso8601: 2001-12-14t21:59:43.10-05:00 + spaced: 2001-12-14 21:59:43.10 -05:00 + date: 2002-12-14 # Time is noon UTC +php: | + array( + 'canonical' => YAML::mktime( 2001, 12, 15, 2, 59, 43, 0.10 ), + 'iso8601' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ), + 'spaced' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ), + 'date' => Date.new( 2002, 12, 14 ) + ) +--- +test: legacy Timestamps test +todo: true +spec: legacy D4 +yaml: | + canonical: 2001-12-15T02:59:43.00Z + iso8601: 2001-02-28t21:59:43.00-05:00 + spaced: 2001-12-14 21:59:43.00 -05:00 + date: 2002-12-14 +php: | + array( + 'canonical' => Time::utc( 2001, 12, 15, 2, 59, 43, 0 ), + 'iso8601' => YAML::mktime( 2001, 2, 28, 21, 59, 43, 0, "-05:00" ), + 'spaced' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0, "-05:00" ), + 'date' => Date.new( 2002, 12, 14 ) + ) +--- +test: Various explicit families +todo: true +spec: 2.23 +yaml: | + not-date: !str 2002-04-28 + picture: !binary | + R0lGODlhDAAMAIQAAP//9/X + 17unp5WZmZgAAAOfn515eXv + Pz7Y6OjuDg4J+fn5OTk6enp + 56enmleECcgggoBADs= + + application specific tag: !!something | + The semantics of the tag + above may be different for + different documents. + +ruby-setup: | + YAML.add_private_type( "something" ) do |type, val| + "SOMETHING: #{val}" + end +ruby: | + { + 'not-date' => '2002-04-28', + 'picture' => "GIF89a\f\000\f\000\204\000\000\377\377\367\365\365\356\351\351\345fff\000\000\000\347\347\347^^^\363\363\355\216\216\216\340\340\340\237\237\237\223\223\223\247\247\247\236\236\236i^\020' \202\n\001\000;", + 'application specific tag' => "SOMETHING: The semantics of the tag\nabove may be different for\ndifferent documents.\n" + } +--- +test: Application specific family +todo: true +spec: 2.24 +yaml: | + # Establish a tag prefix + --- !clarkevans.com,2002/graph/^shape + # Use the prefix: shorthand for + # !clarkevans.com,2002/graph/circle + - !^circle + center: &ORIGIN {x: 73, 'y': 129} + radius: 7 + - !^line # !clarkevans.com,2002/graph/line + start: *ORIGIN + finish: { x: 89, 'y': 102 } + - !^label + start: *ORIGIN + color: 0xFFEEBB + value: Pretty vector drawing. +ruby-setup: | + YAML.add_domain_type( "clarkevans.com,2002", 'graph/shape' ) { |type, val| + if Array === val + val << "Shape Container" + val + else + raise YAML::Error, "Invalid graph of class #{ val.class }: " + val.inspect + end + } + one_shape_proc = Proc.new { |type, val| + scheme, domain, type = type.split( /:/, 3 ) + if val.is_a? ::Hash + val['TYPE'] = "Shape: #{type}" + val + else + raise YAML::Error, "Invalid graph of class #{ val.class }: " + val.inspect + end + } + YAML.add_domain_type( "clarkevans.com,2002", 'graph/circle', &one_shape_proc ) + YAML.add_domain_type( "clarkevans.com,2002", 'graph/line', &one_shape_proc ) + YAML.add_domain_type( "clarkevans.com,2002", 'graph/label', &one_shape_proc ) +ruby: | + [ + { + "radius" => 7, + "center"=> + { + "x" => 73, + "y" => 129 + }, + "TYPE" => "Shape: graph/circle" + }, { + "finish" => + { + "x" => 89, + "y" => 102 + }, + "TYPE" => "Shape: graph/line", + "start" => + { + "x" => 73, + "y" => 129 + } + }, { + "TYPE" => "Shape: graph/label", + "value" => "Pretty vector drawing.", + "start" => + { + "x" => 73, + "y" => 129 + }, + "color" => 16772795 + }, + "Shape Container" + ] +# --- +# test: Unordered set +# spec: 2.25 +# yaml: | +# # sets are represented as a +# # mapping where each key is +# # associated with the empty string +# --- !set +# ? Mark McGwire +# ? Sammy Sosa +# ? Ken Griff +--- +test: Ordered mappings +todo: true +spec: 2.26 +yaml: | + # ordered maps are represented as + # a sequence of mappings, with + # each mapping having one key + --- !omap + - Mark McGwire: 65 + - Sammy Sosa: 63 + - Ken Griffy: 58 +ruby: | + YAML::Omap[ + 'Mark McGwire', 65, + 'Sammy Sosa', 63, + 'Ken Griffy', 58 + ] +--- +test: Invoice +dump_skip: true +spec: 2.27 +yaml: | + --- !clarkevans.com,2002/^invoice + invoice: 34843 + date : 2001-01-23 + bill-to: &id001 + given : Chris + family : Dumars + address: + lines: | + 458 Walkman Dr. + Suite #292 + city : Royal Oak + state : MI + postal : 48046 + ship-to: *id001 + product: + - + sku : BL394D + quantity : 4 + description : Basketball + price : 450.00 + - + sku : BL4438H + quantity : 1 + description : Super Hoop + price : 2392.00 + tax : 251.42 + total: 4443.52 + comments: > + Late afternoon is best. + Backup contact is Nancy + Billsmer @ 338-4338. +php: | + array( + 'invoice' => 34843, 'date' => mktime(0, 0, 0, 1, 23, 2001), + 'bill-to' => + array( 'given' => 'Chris', 'family' => 'Dumars', 'address' => array( 'lines' => "458 Walkman Dr.\nSuite #292\n", 'city' => 'Royal Oak', 'state' => 'MI', 'postal' => 48046 ) ) + , 'ship-to' => + array( 'given' => 'Chris', 'family' => 'Dumars', 'address' => array( 'lines' => "458 Walkman Dr.\nSuite #292\n", 'city' => 'Royal Oak', 'state' => 'MI', 'postal' => 48046 ) ) + , 'product' => + array( + array( 'sku' => 'BL394D', 'quantity' => 4, 'description' => 'Basketball', 'price' => 450.00 ), + array( 'sku' => 'BL4438H', 'quantity' => 1, 'description' => 'Super Hoop', 'price' => 2392.00 ) + ), + 'tax' => 251.42, 'total' => 4443.52, + 'comments' => "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.\n" + ) +--- +test: Log file +todo: true +spec: 2.28 +yaml: | + --- + Time: 2001-11-23 15:01:42 -05:00 + User: ed + Warning: > + This is an error message + for the log file + --- + Time: 2001-11-23 15:02:31 -05:00 + User: ed + Warning: > + A slightly different error + message. + --- + Date: 2001-11-23 15:03:17 -05:00 + User: ed + Fatal: > + Unknown variable "bar" + Stack: + - file: TopClass.py + line: 23 + code: | + x = MoreObject("345\n") + - file: MoreClass.py + line: 58 + code: |- + foo = bar +ruby: | + y = YAML::Stream.new + y.add( { 'Time' => YAML::mktime( 2001, 11, 23, 15, 01, 42, 00, "-05:00" ), + 'User' => 'ed', 'Warning' => "This is an error message for the log file\n" } ) + y.add( { 'Time' => YAML::mktime( 2001, 11, 23, 15, 02, 31, 00, "-05:00" ), + 'User' => 'ed', 'Warning' => "A slightly different error message.\n" } ) + y.add( { 'Date' => YAML::mktime( 2001, 11, 23, 15, 03, 17, 00, "-05:00" ), + 'User' => 'ed', 'Fatal' => "Unknown variable \"bar\"\n", + 'Stack' => [ + { 'file' => 'TopClass.py', 'line' => 23, 'code' => "x = MoreObject(\"345\\n\")\n" }, + { 'file' => 'MoreClass.py', 'line' => 58, 'code' => "foo = bar" } ] } ) +documents: 3 + +--- +test: Throwaway comments +yaml: | + ### These are four throwaway comment ### + + ### lines (the second line is empty). ### + this: | # Comments may trail lines. + contains three lines of text. + The third one starts with a + # character. This isn't a comment. + + # These are three throwaway comment + # lines (the first line is empty). +php: | + array( + 'this' => "contains three lines of text.\nThe third one starts with a\n# character. This isn't a comment.\n" + ) +--- +test: Document with a single value +todo: true +yaml: | + --- > + This YAML stream contains a single text value. + The next stream is a log file - a sequence of + log entries. Adding an entry to the log is a + simple matter of appending it at the end. +ruby: | + "This YAML stream contains a single text value. The next stream is a log file - a sequence of log entries. Adding an entry to the log is a simple matter of appending it at the end.\n" +--- +test: Document stream +todo: true +yaml: | + --- + at: 2001-08-12 09:25:00.00 Z + type: GET + HTTP: '1.0' + url: '/index.html' + --- + at: 2001-08-12 09:25:10.00 Z + type: GET + HTTP: '1.0' + url: '/toc.html' +ruby: | + y = YAML::Stream.new + y.add( { + 'at' => Time::utc( 2001, 8, 12, 9, 25, 00 ), + 'type' => 'GET', + 'HTTP' => '1.0', + 'url' => '/index.html' + } ) + y.add( { + 'at' => Time::utc( 2001, 8, 12, 9, 25, 10 ), + 'type' => 'GET', + 'HTTP' => '1.0', + 'url' => '/toc.html' + } ) +documents: 2 + +--- +test: Top level mapping +yaml: | + # This stream is an example of a top-level mapping. + invoice : 34843 + date : 2001-01-23 + total : 4443.52 +php: | + array( + 'invoice' => 34843, + 'date' => mktime(0, 0, 0, 1, 23, 2001), + 'total' => 4443.52 + ) +--- +test: Single-line documents +todo: true +yaml: | + # The following is a sequence of three documents. + # The first contains an empty mapping, the second + # an empty sequence, and the last an empty string. + --- {} + --- [ ] + --- '' +ruby: | + y = YAML::Stream.new + y.add( {} ) + y.add( [] ) + y.add( '' ) +documents: 3 + +--- +test: Document with pause +todo: true +yaml: | + # A communication channel based on a YAML stream. + --- + sent at: 2002-06-06 11:46:25.10 Z + payload: Whatever + # Receiver can process this as soon as the following is sent: + ... + # Even if the next message is sent long after: + --- + sent at: 2002-06-06 12:05:53.47 Z + payload: Whatever + ... +ruby: | + y = YAML::Stream.new + y.add( + { 'sent at' => YAML::mktime( 2002, 6, 6, 11, 46, 25, 0.10 ), + 'payload' => 'Whatever' } + ) + y.add( + { "payload" => "Whatever", "sent at" => YAML::mktime( 2002, 6, 6, 12, 5, 53, 0.47 ) } + ) +documents: 2 + +--- +test: Explicit typing +yaml: | + integer: 12 + also int: ! "12" + string: !str 12 +php: | + array( 'integer' => 12, 'also int' => 12, 'string' => '12' ) +--- +test: Private types +todo: true +yaml: | + # Both examples below make use of the 'x-private:ball' + # type family URI, but with different semantics. + --- + pool: !!ball + number: 8 + color: black + --- + bearing: !!ball + material: steel +ruby: | + y = YAML::Stream.new + y.add( { 'pool' => + YAML::PrivateType.new( 'ball', + { 'number' => 8, 'color' => 'black' } ) } + ) + y.add( { 'bearing' => + YAML::PrivateType.new( 'ball', + { 'material' => 'steel' } ) } + ) +documents: 2 + +--- +test: Type family under yaml.org +yaml: | + # The URI is 'tag:yaml.org,2002:str' + - !str a Unicode string +php: | + array( 'a Unicode string' ) +--- +test: Type family under perl.yaml.org +todo: true +yaml: | + # The URI is 'tag:perl.yaml.org,2002:Text::Tabs' + - !perl/Text::Tabs {} +ruby: | + [ YAML::DomainType.new( 'perl.yaml.org,2002', 'Text::Tabs', {} ) ] +--- +test: Type family under clarkevans.com +todo: true +yaml: | + # The URI is 'tag:clarkevans.com,2003-02:timesheet' + - !clarkevans.com,2003-02/timesheet {} +ruby: | + [ YAML::DomainType.new( 'clarkevans.com,2003-02', 'timesheet', {} ) ] +--- +test: URI Escaping +todo: true +yaml: | + same: + - !domain.tld,2002/type\x30 value + - !domain.tld,2002/type0 value + different: # As far as the YAML parser is concerned + - !domain.tld,2002/type%30 value + - !domain.tld,2002/type0 value +ruby-setup: | + YAML.add_domain_type( "domain.tld,2002", "type0" ) { |type, val| + "ONE: #{val}" + } + YAML.add_domain_type( "domain.tld,2002", "type%30" ) { |type, val| + "TWO: #{val}" + } +ruby: | + { 'same' => [ 'ONE: value', 'ONE: value' ], 'different' => [ 'TWO: value', 'ONE: value' ] } +--- +test: URI Prefixing +todo: true +yaml: | + # 'tag:domain.tld,2002:invoice' is some type family. + invoice: !domain.tld,2002/^invoice + # 'seq' is shorthand for 'tag:yaml.org,2002:seq'. + # This does not effect '^customer' below + # because it is does not specify a prefix. + customers: !seq + # '^customer' is shorthand for the full + # notation 'tag:domain.tld,2002:customer'. + - !^customer + given : Chris + family : Dumars +ruby-setup: | + YAML.add_domain_type( "domain.tld,2002", /(invoice|customer)/ ) { |type, val| + if val.is_a? ::Hash + scheme, domain, type = type.split( /:/, 3 ) + val['type'] = "domain #{type}" + val + else + raise YAML::Error, "Not a Hash in domain.tld/invoice: " + val.inspect + end + } +ruby: | + { "invoice"=> { "customers"=> [ { "given"=>"Chris", "type"=>"domain customer", "family"=>"Dumars" } ], "type"=>"domain invoice" } } + +--- +test: Overriding anchors +yaml: | + anchor : &A001 This scalar has an anchor. + override : &A001 > + The alias node below is a + repeated use of this value. + alias : *A001 +php: | + array( 'anchor' => 'This scalar has an anchor.', + 'override' => "The alias node below is a repeated use of this value.\n", + 'alias' => "The alias node below is a repeated use of this value.\n" ) +--- +test: Flow and block formatting +todo: true +yaml: | + empty: [] + flow: [ one, two, three # May span lines, + , four, # indentation is + five ] # mostly ignored. + block: + - First item in top sequence + - + - Subordinate sequence entry + - > + A folded sequence entry + - Sixth item in top sequence +ruby: | + { 'empty' => [], 'flow' => [ 'one', 'two', 'three', 'four', 'five' ], + 'block' => [ 'First item in top sequence', [ 'Subordinate sequence entry' ], + "A folded sequence entry\n", 'Sixth item in top sequence' ] } +--- +test: Complete mapping test +todo: true +yaml: | + empty: {} + flow: { one: 1, two: 2 } + spanning: { one: 1, + two: 2 } + block: + first : First entry + second: + key: Subordinate mapping + third: + - Subordinate sequence + - { } + - Previous mapping is empty. + - A key: value pair in a sequence. + A second: key:value pair. + - The previous entry is equal to the following one. + - + A key: value pair in a sequence. + A second: key:value pair. + !float 12 : This key is a float. + ? > + ? + : This key had to be protected. + "\a" : This key had to be escaped. + ? > + This is a + multi-line + folded key + : Whose value is + also multi-line. + ? this also works as a key + : with a value at the next line. + ? + - This key + - is a sequence + : + - With a sequence value. + ? + This: key + is a: mapping + : + with a: mapping value. +ruby: | + { 'empty' => {}, 'flow' => { 'one' => 1, 'two' => 2 }, + 'spanning' => { 'one' => 1, 'two' => 2 }, + 'block' => { 'first' => 'First entry', 'second' => + { 'key' => 'Subordinate mapping' }, 'third' => + [ 'Subordinate sequence', {}, 'Previous mapping is empty.', + { 'A key' => 'value pair in a sequence.', 'A second' => 'key:value pair.' }, + 'The previous entry is equal to the following one.', + { 'A key' => 'value pair in a sequence.', 'A second' => 'key:value pair.' } ], + 12.0 => 'This key is a float.', "?\n" => 'This key had to be protected.', + "\a" => 'This key had to be escaped.', + "This is a multi-line folded key\n" => "Whose value is also multi-line.", + 'this also works as a key' => 'with a value at the next line.', + [ 'This key', 'is a sequence' ] => [ 'With a sequence value.' ] } } + # Couldn't recreate map exactly, so we'll do a detailed check to be sure it's entact + obj_y['block'].keys.each { |k| + if Hash === k + v = obj_y['block'][k] + if k['This'] == 'key' and k['is a'] == 'mapping' and v['with a'] == 'mapping value.' + obj_r['block'][k] = v + end + end + } +--- +test: Literal explicit indentation +yaml: | + # Explicit indentation must + # be given in all the three + # following cases. + leading spaces: |2 + This value starts with four spaces. + + leading line break: |2 + + This value starts with a line break. + + leading comment indicator: |2 + # first line starts with a + # character. + + # Explicit indentation may + # also be given when it is + # not required. + redundant: |2 + This value is indented 2 spaces. +php: | + array( + 'leading spaces' => " This value starts with four spaces.\n", + 'leading line break' => "\nThis value starts with a line break.\n", + 'leading comment indicator' => "# first line starts with a\n# character.\n", + 'redundant' => "This value is indented 2 spaces.\n" + ) +--- +test: Chomping and keep modifiers +yaml: | + clipped: | + This has one newline. + + same as "clipped" above: "This has one newline.\n" + + stripped: |- + This has no newline. + + same as "stripped" above: "This has no newline." + + kept: |+ + This has two newlines. + + same as "kept" above: "This has two newlines.\n\n" +php: | + array( + 'clipped' => "This has one newline.\n", + 'same as "clipped" above' => "This has one newline.\n", + 'stripped' => 'This has no newline.', + 'same as "stripped" above' => 'This has no newline.', + 'kept' => "This has two newlines.\n\n", + 'same as "kept" above' => "This has two newlines.\n\n" + ) +--- +test: Literal combinations +todo: true +yaml: | + empty: | + + literal: | + The \ ' " characters may be + freely used. Leading white + space is significant. + + Line breaks are significant. + Thus this value contains one + empty line and ends with a + single line break, but does + not start with one. + + is equal to: "The \\ ' \" characters may \ + be\nfreely used. Leading white\n space \ + is significant.\n\nLine breaks are \ + significant.\nThus this value contains \ + one\nempty line and ends with a\nsingle \ + line break, but does\nnot start with one.\n" + + # Comments may follow a block + # scalar value. They must be + # less indented. + + # Modifiers may be combined in any order. + indented and chomped: |2- + This has no newline. + + also written as: |-2 + This has no newline. + + both are equal to: " This has no newline." +php: | + array( + 'empty' => '', + 'literal' => "The \\ ' \" characters may be\nfreely used. Leading white\n space " + + "is significant.\n\nLine breaks are significant.\nThus this value contains one\n" + + "empty line and ends with a\nsingle line break, but does\nnot start with one.\n", + 'is equal to' => "The \\ ' \" characters may be\nfreely used. Leading white\n space " + + "is significant.\n\nLine breaks are significant.\nThus this value contains one\n" + + "empty line and ends with a\nsingle line break, but does\nnot start with one.\n", + 'indented and chomped' => ' This has no newline.', + 'also written as' => ' This has no newline.', + 'both are equal to' => ' This has no newline.' + ) +--- +test: Folded combinations +todo: true +yaml: | + empty: > + + one paragraph: > + Line feeds are converted + to spaces, so this value + contains no line breaks + except for the final one. + + multiple paragraphs: >2 + + An empty line, either + at the start or in + the value: + + Is interpreted as a + line break. Thus this + value contains three + line breaks. + + indented text: > + This is a folded + paragraph followed + by a list: + * first entry + * second entry + Followed by another + folded paragraph, + another list: + + * first entry + + * second entry + + And a final folded + paragraph. + + above is equal to: | + This is a folded paragraph followed by a list: + * first entry + * second entry + Followed by another folded paragraph, another list: + + * first entry + + * second entry + + And a final folded paragraph. + + # Explicit comments may follow + # but must be less indented. +php: | + array( + 'empty' => '', + 'one paragraph' => 'Line feeds are converted to spaces, so this value'. + " contains no line breaks except for the final one.\n", + 'multiple paragraphs' => "\nAn empty line, either at the start or in the value:\n". + "Is interpreted as a line break. Thus this value contains three line breaks.\n", + 'indented text' => "This is a folded paragraph followed by a list:\n". + " * first entry\n * second entry\nFollowed by another folded paragraph, ". + "another list:\n\n * first entry\n\n * second entry\n\nAnd a final folded paragraph.\n", + 'above is equal to' => "This is a folded paragraph followed by a list:\n". + " * first entry\n * second entry\nFollowed by another folded paragraph, ". + "another list:\n\n * first entry\n\n * second entry\n\nAnd a final folded paragraph.\n" + ) +--- +test: Single quotes +todo: true +yaml: | + empty: '' + second: '! : \ etc. can be used freely.' + third: 'a single quote '' must be escaped.' + span: 'this contains + six spaces + + and one + line break' + is same as: "this contains six spaces\nand one line break" +php: | + array( + 'empty' => '', + 'second' => '! : \\ etc. can be used freely.', + 'third' => "a single quote ' must be escaped.", + 'span' => "this contains six spaces\nand one line break", + 'is same as' => "this contains six spaces\nand one line break" + ) +--- +test: Double quotes +todo: true +yaml: | + empty: "" + second: "! : etc. can be used freely." + third: "a \" or a \\ must be escaped." + fourth: "this value ends with an LF.\n" + span: "this contains + four \ + spaces" + is equal to: "this contains four spaces" +php: | + array( + 'empty' => '', + 'second' => '! : etc. can be used freely.', + 'third' => 'a " or a \\ must be escaped.', + 'fourth' => "this value ends with an LF.\n", + 'span' => "this contains four spaces", + 'is equal to' => "this contains four spaces" + ) +--- +test: Unquoted strings +todo: true +yaml: | + first: There is no unquoted empty string. + + second: 12 ## This is an integer. + + third: !str 12 ## This is a string. + + span: this contains + six spaces + + and one + line break + + indicators: this has no comments. + #:foo and bar# are + both text. + + flow: [ can span + lines, # comment + like + this ] + + note: { one-line keys: but multi-line values } + +php: | + array( + 'first' => 'There is no unquoted empty string.', + 'second' => 12, + 'third' => '12', + 'span' => "this contains six spaces\nand one line break", + 'indicators' => "this has no comments. #:foo and bar# are both text.", + 'flow' => [ 'can span lines', 'like this' ], + 'note' => { 'one-line keys' => 'but multi-line values' } + ) +--- +test: Spanning sequences +todo: true +yaml: | + # The following are equal seqs + # with different identities. + flow: [ one, two ] + spanning: [ one, + two ] + block: + - one + - two +php: | + array( + 'flow' => [ 'one', 'two' ], + 'spanning' => [ 'one', 'two' ], + 'block' => [ 'one', 'two' ] + ) +--- +test: Flow mappings +yaml: | + # The following are equal maps + # with different identities. + flow: { one: 1, two: 2 } + block: + one: 1 + two: 2 +php: | + array( + 'flow' => array( 'one' => 1, 'two' => 2 ), + 'block' => array( 'one' => 1, 'two' => 2 ) + ) +--- +test: Representations of 12 +todo: true +yaml: | + - 12 # An integer + # The following scalars + # are loaded to the + # string value '1' '2'. + - !str 12 + - '12' + - "12" + - "\ + 1\ + 2\ + " + # Strings containing paths and regexps can be unquoted: + - /foo/bar + - d:/foo/bar + - foo/bar + - /a.*b/ +php: | + array( 12, '12', '12', '12', '12', '/foo/bar', 'd:/foo/bar', 'foo/bar', '/a.*b/' ) +--- +test: "Null" +todo: true +yaml: | + canonical: ~ + + english: null + + # This sequence has five + # entries, two with values. + sparse: + - ~ + - 2nd entry + - Null + - 4th entry + - + + four: This mapping has five keys, + only two with values. + +php: | + array ( + 'canonical' => null, + 'english' => null, + 'sparse' => array( null, '2nd entry', null, '4th entry', null ]), + 'four' => 'This mapping has five keys, only two with values.' + ) +--- +test: Omap +todo: true +yaml: | + # Explicitly typed dictionary. + Bestiary: !omap + - aardvark: African pig-like ant eater. Ugly. + - anteater: South-American ant eater. Two species. + - anaconda: South-American constrictor snake. Scary. + # Etc. +ruby: | + { + 'Bestiary' => YAML::Omap[ + 'aardvark', 'African pig-like ant eater. Ugly.', + 'anteater', 'South-American ant eater. Two species.', + 'anaconda', 'South-American constrictor snake. Scary.' + ] + } + +--- +test: Pairs +todo: true +yaml: | + # Explicitly typed pairs. + tasks: !pairs + - meeting: with team. + - meeting: with boss. + - break: lunch. + - meeting: with client. +ruby: | + { + 'tasks' => YAML::Pairs[ + 'meeting', 'with team.', + 'meeting', 'with boss.', + 'break', 'lunch.', + 'meeting', 'with client.' + ] + } + +--- +test: Set +todo: true +yaml: | + # Explicitly typed set. + baseball players: !set + Mark McGwire: + Sammy Sosa: + Ken Griffey: +ruby: | + { + 'baseball players' => YAML::Set[ + 'Mark McGwire', nil, + 'Sammy Sosa', nil, + 'Ken Griffey', nil + ] + } + +--- +test: Boolean +yaml: | + false: used as key + logical: true + answer: no +php: | + array( + false => 'used as key', + 'logical' => true, + 'answer' => false + ) +--- +test: Integer +yaml: | + canonical: 12345 + decimal: +12,345 + octal: 014 + hexadecimal: 0xC +php: | + array( + 'canonical' => 12345, + 'decimal' => 12345, + 'octal' => 12, + 'hexadecimal' => 12 + ) +--- +test: Float +yaml: | + canonical: 1.23015e+3 + exponential: 12.3015e+02 + fixed: 1,230.15 + negative infinity: -.inf + not a number: .NaN +php: | + array( + 'canonical' => 1230.15, + 'exponential' => 1230.15, + 'fixed' => 1230.15, + 'negative infinity' => log(0), + 'not a number' => -log(0) + ) +--- +test: Timestamp +todo: true +yaml: | + canonical: 2001-12-15T02:59:43.1Z + valid iso8601: 2001-12-14t21:59:43.10-05:00 + space separated: 2001-12-14 21:59:43.10 -05:00 + date (noon UTC): 2002-12-14 +ruby: | + array( + 'canonical' => YAML::mktime( 2001, 12, 15, 2, 59, 43, 0.10 ), + 'valid iso8601' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ), + 'space separated' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ), + 'date (noon UTC)' => Date.new( 2002, 12, 14 ) + ) +--- +test: Binary +todo: true +yaml: | + canonical: !binary "\ + R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5\ + OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+\ + +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC\ + AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=" + base64: !binary | + R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5 + OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+ + +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC + AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs= + description: > + The binary value above is a tiny arrow + encoded as a gif image. +ruby-setup: | + arrow_gif = "GIF89a\f\000\f\000\204\000\000\377\377\367\365\365\356\351\351\345fff\000\000\000\347\347\347^^^\363\363\355\216\216\216\340\340\340\237\237\237\223\223\223\247\247\247\236\236\236iiiccc\243\243\243\204\204\204\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371!\376\016Made with GIMP\000,\000\000\000\000\f\000\f\000\000\005, \216\2010\236\343@\024\350i\020\304\321\212\010\034\317\200M$z\357\3770\205p\270\2601f\r\e\316\001\303\001\036\020' \202\n\001\000;" +ruby: | + { + 'canonical' => arrow_gif, + 'base64' => arrow_gif, + 'description' => "The binary value above is a tiny arrow encoded as a gif image.\n" + } + +--- +test: Merge key +todo: true +yaml: | + --- + - &CENTER { x: 1, y: 2 } + - &LEFT { x: 0, y: 2 } + - &BIG { r: 10 } + - &SMALL { r: 1 } + + # All the following maps are equal: + + - # Explicit keys + x: 1 + y: 2 + r: 10 + label: center/big + + - # Merge one map + << : *CENTER + r: 10 + label: center/big + + - # Merge multiple maps + << : [ *CENTER, *BIG ] + label: center/big + + - # Override + << : [ *BIG, *LEFT, *SMALL ] + x: 1 + label: center/big + +ruby-setup: | + center = { 'x' => 1, 'y' => 2 } + left = { 'x' => 0, 'y' => 2 } + big = { 'r' => 10 } + small = { 'r' => 1 } + node1 = { 'x' => 1, 'y' => 2, 'r' => 10, 'label' => 'center/big' } + node2 = center.dup + node2.update( { 'r' => 10, 'label' => 'center/big' } ) + node3 = big.dup + node3.update( center ) + node3.update( { 'label' => 'center/big' } ) + node4 = small.dup + node4.update( left ) + node4.update( big ) + node4.update( { 'x' => 1, 'label' => 'center/big' } ) + +ruby: | + [ + center, left, big, small, node1, node2, node3, node4 + ] + +--- +test: Default key +todo: true +yaml: | + --- # Old schema + link with: + - library1.dll + - library2.dll + --- # New schema + link with: + - = : library1.dll + version: 1.2 + - = : library2.dll + version: 2.3 +ruby: | + y = YAML::Stream.new + y.add( { 'link with' => [ 'library1.dll', 'library2.dll' ] } ) + obj_h = Hash[ 'version' => 1.2 ] + obj_h.default = 'library1.dll' + obj_h2 = Hash[ 'version' => 2.3 ] + obj_h2.default = 'library2.dll' + y.add( { 'link with' => [ obj_h, obj_h2 ] } ) +documents: 2 + +--- +test: Special keys +todo: true +yaml: | + "!": These three keys + "&": had to be quoted + "=": and are normal strings. + # NOTE: the following node should NOT be serialized this way. + encoded node : + !special '!' : '!type' + !special|canonical '&' : 12 + = : value + # The proper way to serialize the above node is as follows: + node : !!type &12 value +ruby: | + { '!' => 'These three keys', '&' => 'had to be quoted', + '=' => 'and are normal strings.', + 'encoded node' => YAML::PrivateType.new( 'type', 'value' ), + 'node' => YAML::PrivateType.new( 'type', 'value' ) } diff --git a/tests/fixtures/Symfony/Components/YAML/YtsTypeTransfers.yml b/tests/fixtures/Symfony/Components/YAML/YtsTypeTransfers.yml new file mode 100644 index 000000000000..398fa0eb8903 --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/YtsTypeTransfers.yml @@ -0,0 +1,244 @@ +--- %YAML:1.0 +test: Strings +brief: > + Any group of characters beginning with an + alphabetic or numeric character is a string, + unless it belongs to one of the groups below + (such as an Integer or Time). +yaml: | + String +php: | + 'String' +--- +test: String characters +brief: > + A string can contain any alphabetic or + numeric character, along with many + punctuation characters, including the + period, dash, space, quotes, exclamation, and + question mark. +yaml: | + - What's Yaml? + - It's for writing data structures in plain text. + - And? + - And what? That's not good enough for you? + - No, I mean, "And what about Yaml?" + - Oh, oh yeah. Uh.. Yaml for Ruby. +php: | + array( + "What's Yaml?", + "It's for writing data structures in plain text.", + "And?", + "And what? That's not good enough for you?", + "No, I mean, \"And what about Yaml?\"", + "Oh, oh yeah. Uh.. Yaml for Ruby." + ) +--- +test: Indicators in Strings +brief: > + Be careful using indicators in strings. In particular, + the comma, colon, and pound sign must be used carefully. +yaml: | + the colon followed by space is an indicator: but is a string:right here + same for the pound sign: here we have it#in a string + the comma can, honestly, be used in most cases: [ but not in, inline collections ] +php: | + array( + 'the colon followed by space is an indicator' => 'but is a string:right here', + 'same for the pound sign' => 'here we have it#in a string', + 'the comma can, honestly, be used in most cases' => array('but not in', 'inline collections') + ) +--- +test: Forcing Strings +brief: > + Any YAML type can be forced into a string using the + explicit !str method. +yaml: | + date string: !str 2001-08-01 + number string: !str 192 +php: | + array( + 'date string' => '2001-08-01', + 'number string' => '192' + ) +--- +test: Single-quoted Strings +brief: > + You can also enclose your strings within single quotes, + which allows use of slashes, colons, and other indicators + freely. Inside single quotes, you can represent a single + quote in your string by using two single quotes next to + each other. +yaml: | + all my favorite symbols: '#:!/%.)' + a few i hate: '&(*' + why do i hate them?: 'it''s very hard to explain' + entities: '£ me' +php: | + array( + 'all my favorite symbols' => '#:!/%.)', + 'a few i hate' => '&(*', + 'why do i hate them?' => 'it\'s very hard to explain', + 'entities' => '£ me' + ) +--- +test: Double-quoted Strings +brief: > + Enclosing strings in double quotes allows you + to use escapings to represent ASCII and + Unicode characters. +yaml: | + i know where i want my line breaks: "one here\nand another here\n" +php: | + array( + 'i know where i want my line breaks' => "one here\nand another here\n" + ) +--- +test: Multi-line Quoted Strings +todo: true +brief: > + Both single- and double-quoted strings may be + carried on to new lines in your YAML document. + They must be indented a step and indentation + is interpreted as a single space. +yaml: | + i want a long string: "so i'm going to + let it go on and on to other lines + until i end it with a quote." +php: | + array('i want a long string' => "so i'm going to ". + "let it go on and on to other lines ". + "until i end it with a quote." + ) + +--- +test: Plain scalars +todo: true +brief: > + Unquoted strings may also span multiple lines, if they + are free of YAML space indicators and indented. +yaml: | + - My little toe is broken in two places; + - I'm crazy to have skied this way; + - I'm not the craziest he's seen, since there was always the German guy + who skied for 3 hours on a broken shin bone (just below the kneecap); + - Nevertheless, second place is respectable, and he doesn't + recommend going for the record; + - He's going to put my foot in plaster for a month; + - This would impair my skiing ability somewhat for the + duration, as can be imagined. +php: | + array( + "My little toe is broken in two places;", + "I'm crazy to have skied this way;", + "I'm not the craziest he's seen, since there was always ". + "the German guy who skied for 3 hours on a broken shin ". + "bone (just below the kneecap);", + "Nevertheless, second place is respectable, and he doesn't ". + "recommend going for the record;", + "He's going to put my foot in plaster for a month;", + "This would impair my skiing ability somewhat for the duration, ". + "as can be imagined." + ) +--- +test: 'Null' +brief: > + You can use the tilde '~' character for a null value. +yaml: | + name: Mr. Show + hosted by: Bob and David + date of next season: ~ +php: | + array( + 'name' => 'Mr. Show', + 'hosted by' => 'Bob and David', + 'date of next season' => null + ) +--- +test: Boolean +brief: > + You can use 'true' and 'false' for boolean values. +yaml: | + Is Gus a Liar?: true + Do I rely on Gus for Sustenance?: false +php: | + array( + 'Is Gus a Liar?' => true, + 'Do I rely on Gus for Sustenance?' => false + ) +--- +test: Integers +dump_skip: true +brief: > + An integer is a series of numbers, optionally + starting with a positive or negative sign. Integers + may also contain commas for readability. +yaml: | + zero: 0 + simple: 12 + one-thousand: 1,000 + negative one-thousand: -1,000 +php: | + array( + 'zero' => 0, + 'simple' => 12, + 'one-thousand' => 1000, + 'negative one-thousand' => -1000 + ) +--- +test: Integers as Map Keys +brief: > + An integer can be used a dictionary key. +yaml: | + 1: one + 2: two + 3: three +php: | + array( + 1 => 'one', + 2 => 'two', + 3 => 'three' + ) +--- +test: Floats +dump_skip: true +brief: > + Floats are represented by numbers with decimals, + allowing for scientific notation, as well as + positive and negative infinity and "not a number." +yaml: | + a simple float: 2.00 + larger float: 1,000.09 + scientific notation: 1.00009e+3 +php: | + array( + 'a simple float' => 2.0, + 'larger float' => 1000.09, + 'scientific notation' => 1000.09 + ) +--- +test: Time +todo: true +brief: > + You can represent timestamps by using + ISO8601 format, or a variation which + allows spaces between the date, time and + time zone. +yaml: | + iso8601: 2001-12-14t21:59:43.10-05:00 + space seperated: 2001-12-14 21:59:43.10 -05:00 +php: | + array( + 'iso8601' => mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ), + 'space seperated' => mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ) + ) +--- +test: Date +todo: true +brief: > + A date can be represented by its year, + month and day in ISO8601 order. +yaml: | + 1976-07-31 +php: | + date( 1976, 7, 31 ) diff --git a/tests/fixtures/Symfony/Components/YAML/index.yml b/tests/fixtures/Symfony/Components/YAML/index.yml new file mode 100644 index 000000000000..e9735d6f4c22 --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/index.yml @@ -0,0 +1,15 @@ +- sfComments +- sfTests +- sfObjects +- sfMergeKey +- sfQuotes +- YtsAnchorAlias +- YtsBasicTests +- YtsBlockMapping +- YtsDocumentSeparator +- YtsErrorTests +- YtsFlowCollections +- YtsFoldedScalars +- YtsNullsAndEmpties +- YtsSpecificationExamples +- YtsTypeTransfers diff --git a/tests/fixtures/Symfony/Components/YAML/sfComments.yml b/tests/fixtures/Symfony/Components/YAML/sfComments.yml new file mode 100644 index 000000000000..bbc425bade7a --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/sfComments.yml @@ -0,0 +1,41 @@ +--- %YAML:1.0 +test: Comments at the end of a line +brief: > + Comments at the end of a line +yaml: | + ex1: "foo # bar" + ex2: "foo # bar" # comment + ex3: 'foo # bar' # comment + ex4: foo # comment +php: | + array('ex1' => 'foo # bar', 'ex2' => 'foo # bar', 'ex3' => 'foo # bar', 'ex4' => 'foo') +--- +test: Comments in the middle +brief: > + Comments in the middle +yaml: | + foo: + # some comment + # some comment + bar: foo + # some comment + # some comment +php: | + array('foo' => array('bar' => 'foo')) +--- +test: Comments on a hash line +brief: > + Comments on a hash line +yaml: | + foo: # a comment + foo: bar # a comment +php: | + array('foo' => array('foo' => 'bar')) +--- +test: 'Value starting with a #' +brief: > + 'Value starting with a #' +yaml: | + foo: '#bar' +php: | + array('foo' => '#bar') \ No newline at end of file diff --git a/tests/fixtures/Symfony/Components/YAML/sfMergeKey.yml b/tests/fixtures/Symfony/Components/YAML/sfMergeKey.yml new file mode 100644 index 000000000000..3eec4f877daa --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/sfMergeKey.yml @@ -0,0 +1,27 @@ +--- %YAML:1.0 +test: Simple In Place Substitution +brief: > + If you want to reuse an entire alias, only overwriting what is different + you can use a << in place substitution. This is not part of the official + YAML spec, but a widely implemented extension. See the following URL for + details: http://yaml.org/type/merge.html +yaml: | + foo: &foo + a: Steve + b: Clark + c: Brian + bar: &bar + <<: *foo + x: Oren + foo2: &foo2 + a: Ballmer + ding: &dong [ fi, fei, fo, fam] + check: + <<: + - *foo + - *dong + isit: tested + head: + <<: [ *foo , *dong , *foo2 ] +php: | + array('foo' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian'), 'bar' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'x' => 'Oren'), 'foo2' => array('a' => 'Ballmer'), 'ding' => array('fi', 'fei', 'fo', 'fam'), 'check' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam', 'isit' => 'tested'), 'head' => array('a' => 'Ballmer', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam')) diff --git a/tests/fixtures/Symfony/Components/YAML/sfObjects.yml b/tests/fixtures/Symfony/Components/YAML/sfObjects.yml new file mode 100644 index 000000000000..454ceae69bf9 --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/sfObjects.yml @@ -0,0 +1,11 @@ +--- %YAML:1.0 +test: Objects +brief: > + Comments at the end of a line +yaml: | + ex1: "foo # bar" + ex2: "foo # bar" # comment + ex3: 'foo # bar' # comment + ex4: foo # comment +php: | + array('ex1' => 'foo # bar', 'ex2' => 'foo # bar', 'ex3' => 'foo # bar', 'ex4' => 'foo') diff --git a/tests/fixtures/Symfony/Components/YAML/sfQuotes.yml b/tests/fixtures/Symfony/Components/YAML/sfQuotes.yml new file mode 100644 index 000000000000..741f1befeb8e --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/sfQuotes.yml @@ -0,0 +1,33 @@ +--- %YAML:1.0 +test: Some characters at the beginning of a string must be escaped +brief: > + Some characters at the beginning of a string must be escaped +yaml: | + foo: | bar +php: | + array('foo' => '| bar') +--- +test: A key can be a quoted string +brief: > + A key can be a quoted string +yaml: | + "foo1": bar + 'foo2': bar + "foo \" bar": bar + 'foo '' bar': bar + 'foo3: ': bar + "foo4: ": bar + foo5: { "foo \" bar: ": bar, 'foo '' bar: ': bar } +php: | + array( + 'foo1' => 'bar', + 'foo2' => 'bar', + 'foo " bar' => 'bar', + 'foo \' bar' => 'bar', + 'foo3: ' => 'bar', + 'foo4: ' => 'bar', + 'foo5' => array( + 'foo " bar: ' => 'bar', + 'foo \' bar: ' => 'bar', + ), + ) diff --git a/tests/fixtures/Symfony/Components/YAML/sfTests.yml b/tests/fixtures/Symfony/Components/YAML/sfTests.yml new file mode 100644 index 000000000000..f3dbc84cd898 --- /dev/null +++ b/tests/fixtures/Symfony/Components/YAML/sfTests.yml @@ -0,0 +1,139 @@ +--- %YAML:1.0 +test: Multiple quoted string on one line +brief: > + Multiple quoted string on one line +yaml: | + stripped_title: { name: "foo bar", help: "bar foo" } +php: | + array('stripped_title' => array('name' => 'foo bar', 'help' => 'bar foo')) +--- +test: Empty sequence +yaml: | + foo: [ ] +php: | + array('foo' => array()) +--- +test: Inline string parsing +brief: > + Inline string parsing +yaml: | + test: ['complex: string', 'another [string]'] +php: | + array('test' => array('complex: string', 'another [string]')) +--- +test: Boolean +brief: > + Boolean +yaml: | + - false + - - + - off + - no + - true + - + + - on + - yes + - null + - ~ + - 'false' + - '-' + - 'off' + - 'no' + - 'true' + - '+' + - 'on' + - 'yes' + - 'null' + - '~' +php: | + array( + false, + false, + false, + false, + true, + true, + true, + true, + null, + null, + 'false', + '-', + 'off', + 'no', + 'true', + '+', + 'on', + 'yes', + 'null', + '~', + ) +--- +test: Empty lines in folded blocks +brief: > + Empty lines in folded blocks +yaml: | + foo: + bar: | + foo + + + + bar +php: | + array('foo' => array('bar' => "foo\n\n\n \nbar\n")) +--- +test: IP addresses +brief: > + IP addresses +yaml: | + foo: 10.0.0.2 +php: | + array('foo' => '10.0.0.2') +--- +test: A sequence with an embedded mapping +brief: > + A sequence with an embedded mapping +yaml: | + - foo + - bar: { bar: foo } +php: | + array('foo', array('bar' => array('bar' => 'foo'))) +--- +test: A sequence with an unordered array +brief: > + A sequence with an unordered array +yaml: | + 1: foo + 0: bar +php: | + array(1 => 'foo', 0 => 'bar') +--- +test: Octal +brief: as in spec example 2.19, octal value is converted +yaml: | + foo: 0123 +php: | + array('foo' => 83) +--- +test: Octal strings +brief: Octal notation in a string must remain a string +yaml: | + foo: "0123" +php: | + array('foo' => '0123') +--- +test: Octal strings +brief: Octal notation in a string must remain a string +yaml: | + foo: '0123' +php: | + array('foo' => '0123') +--- +test: Octal strings +brief: Octal notation in a string must remain a string +yaml: | + foo: | + 0123 +php: | + array('foo' => "0123\n") diff --git a/tests/lib/SymfonyTests/Components/Templating/ProjectTemplateDebugger.php b/tests/lib/SymfonyTests/Components/Templating/ProjectTemplateDebugger.php new file mode 100644 index 000000000000..3927ff2ac1c4 --- /dev/null +++ b/tests/lib/SymfonyTests/Components/Templating/ProjectTemplateDebugger.php @@ -0,0 +1,31 @@ +messages[] = $message; + } + + public function hasMessage($regex) + { + foreach ($this->messages as $message) + { + if (preg_match('#'.preg_quote($regex, '#').'#', $message)) + { + return true; + } + } + + return false; + } + + public function getMessages() + { + return $this->messages; + } +} diff --git a/tests/lib/SymfonyTests/Components/Templating/SimpleHelper.php b/tests/lib/SymfonyTests/Components/Templating/SimpleHelper.php new file mode 100644 index 000000000000..a2923494f01e --- /dev/null +++ b/tests/lib/SymfonyTests/Components/Templating/SimpleHelper.php @@ -0,0 +1,23 @@ +value = $value; + } + + public function __toString() + { + return $this->value; + } + + public function getName() + { + return 'foo'; + } +} diff --git a/tests/lib/vendor/lime/LimeAnnotationSupport.php b/tests/lib/vendor/lime/LimeAnnotationSupport.php new file mode 100644 index 000000000000..37dd8e3f33f0 --- /dev/null +++ b/tests/lib/vendor/lime/LimeAnnotationSupport.php @@ -0,0 +1,196 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Extends lime_test to support annotations in test files. + * + * With this extension of lime_test, you can write very simple test files that + * support more features than regular lime, such as code executed before + * or after each test, code executed before or after the whole test suite + * or expected exceptions. + * + * A test file can be written like this with LimeTest: + * + * + * setValue('Bumblebee'); + * $t->is($r->getValue(), 'Bumblebee', 'The setter works'); + * + * // @Test + * $t->is($r->getValue(), 'Foobar', 'The value is "Foobar" by default'); + * + * + * The available annotations are: + * + * * @BeforeAll Executed before the whole test suite + * * @Before Executed before each test + * * @After Executed after each test + * * @AfterAll Executed after the whole test suite + * * @Test A test case + * + * You can add comments to the annotations that will be printed in the console: + * + * + * // @Test: The record supports setValue() + * $r->setValue('Bumblebee') + * // etc. + * + * + * You can also automatically test that certain exceptions are thrown from + * within a test. To do that, you must call the method ->expect() on the + * LimeTest object '''before''' executing the test that should throw + * an exception. + * + * + * // @Test + * $r->expect('RuntimeException'); + * throw new RuntimeException(); + * + * // results in a passed test + * + * + * @package lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeAnnotationSupport.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeAnnotationSupport +{ + protected static + $enabled = false; + + protected + $originalPath = null, + $path = null, + $test = null, + $lexer = null; + + /** + * Enables annotation support in a script file. + */ + public static function enable() + { + // make sure that annotations are not replaced twice at the same time + if (!self::$enabled) + { + self::$enabled = true; + + $support = new LimeAnnotationSupport(self::getScriptPath()); + $support->execute(); + + exit; + } + } + + /** + * Returns the file path of the executed test script + * + * @return string The file path + */ + protected static function getScriptPath() + { + $traces = debug_backtrace(); + $file = $traces[count($traces)-1]['file']; + + if (!is_file($file)) + { + throw new RuntimeException('The script name from the traces is not valid: '.$file); + } + + return $file; + } + + /** + * Constructor. + * + * Creates a backup of the given file with the extension .bak. + */ + protected function __construct($path) + { + $this->originalPath = $path; + $this->path = dirname($path).'/@'.basename($path); + + register_shutdown_function(array($this, 'cleanup')); + } + + /** + * Removes the transformed script file. + */ + public function cleanup() + { + if (file_exists($this->path)) + { + unlink($this->path); + } + } + + /** + * Transforms the annotations in the script file and executes the resulting + * script. + */ + protected function execute() + { + if (file_exists($this->path)) + { + unlink($this->path); + } + + $this->lexer = new LimeLexerTransformAnnotations($this->path); + $callbacks = $this->lexer->parse($this->originalPath); + + $this->includeTestFile(); + + $testRunner = new LimeTestRunner($this->test ? $this->test->getOutput() : null); + + foreach ($callbacks as $annotation => $callbacks) + { + $addMethod = 'add'.$annotation; + foreach ($callbacks as $list) + { + list ($callback, $comment) = $list; + $testRunner->$addMethod($callback, $comment); + } + } + + if ($this->test instanceof LimeTest) + { + $testRunner->addExceptionHandler(array($this->test, 'handleException')); + $testRunner->addAfter(array($this->test, 'verifyException')); + } + + $testRunner->run(); + } + + /** + * Includes the test file in a separate scope. + * + * @param string $testVariable + */ + protected function includeTestFile() + { +// var_dump(file_get_contents($this->path)); + include $this->path; + + if (!is_null($this->lexer->getTestVariable())) + { + eval(sprintf('$this->test = %s;', $this->lexer->getTestVariable())); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/LimeAssertionFailedException.php b/tests/lib/vendor/lime/LimeAssertionFailedException.php new file mode 100644 index 000000000000..5097847a3aa6 --- /dev/null +++ b/tests/lib/vendor/lime/LimeAssertionFailedException.php @@ -0,0 +1,62 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeAssertionFailedException extends Exception +{ + private + $actual = '', + $expected = ''; + + public function __construct($actual, $expected) + { + parent::__construct(sprintf('Got: %s, Expected: %s', $actual, $expected)); + + $this->actual = (string)$actual; + $this->expected = (string)$expected; + } + + public function getActual($indentation = 0) + { + if ($indentation > 0) + { + return $this->indent($this->actual, $indentation); + } + else + { + return $this->actual; + } + } + + public function getExpected($indentation = 0) + { + if ($indentation > 0) + { + return $this->indent($this->expected, $indentation); + } + else + { + return $this->expected; + } + } + + protected function indent($lines, $indentation = 2) + { + $lines = explode("\n", $lines); + + foreach ($lines as $key => $line) + { + $lines[$key] = str_repeat(' ', $indentation).$line; + } + + return trim(implode("\n", $lines)); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/LimeAutoloader.php b/tests/lib/vendor/lime/LimeAutoloader.php new file mode 100644 index 000000000000..2c1b4c3ae178 --- /dev/null +++ b/tests/lib/vendor/lime/LimeAutoloader.php @@ -0,0 +1,238 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +// PHP_VERSION_ID is available as of PHP 5.2.7, if our +// version is lower than that, then emulate it +if(!defined('PHP_VERSION_ID')) +{ + $version = explode('.',PHP_VERSION); + + define('PHP_VERSION_ID', ($version[0] * 10000 + $version[1] * 100 + $version[2])); +} + +/** + * LimeAutoloader is an autoloader for the lime test framework classes. + * + * Use the method register() to activate autoloading for all classes of this + * component. + * + * + * include 'path/to/LimeAutoloader.php'; + * LimeAutoloader::register(); + * + * + * Bundled with this component comes a backwards compatibility layer that + * offers class and method signatures of lime 1.0 (lime_test, lime_harness etc.). + * To activate this layer, call the method LimeAutoloader::enableLegacyMode() + * anytime before using any of the old class names in your code. + * + * + * include 'path/to/LimeAutoloader.php'; + * LimeAutoloader::register(); + * LimeAutoloader::enableLegacyMode(); + * + * + * @package symfony + * @subpackage lime + * @author Fabien Potencier + * @author Bernhard Schussek + * @version SVN: $Id: LimeAutoloader.php 24189 2009-11-20 11:29:03Z bschussek $ + */ +class LimeAutoloader +{ + static protected + $isLegacyMode = false, + $isRegistered = false; + + /** + * Enables a backwards compatibility layer to allow use of old class names + * such as lime_test, lime_output etc. + */ + static public function enableLegacyMode() + { + self::$isLegacyMode = true; + } + + /** + * Registers LimeAutoloader as an SPL autoloader. + */ + static public function register() + { + if (!self::$isRegistered) + { + ini_set('unserialize_callback_func', 'spl_autoload_call'); + spl_autoload_register(array(new self, 'autoload')); + + self::$isRegistered = true; + } + } + + /** + * Handles autoloading of classes. + * + * @param string $class A class name. + * + * @return boolean Returns true if the class has been loaded + */ + public function autoload($class) + { + // backwards compatibility + if (0 === strpos($class, 'lime_') && self::$isLegacyMode) + { + require_once dirname(__FILE__).'/lime.php'; + + return true; + } + + if (0 === strpos($class, 'Lime')) + { + $file = dirname(__FILE__).'/'; + + if (0 === strpos($class, 'LimeExpectation')) + { + $file .= 'expectation/'; + } + else if (0 === strpos($class, 'LimeLexer')) + { + $file .= 'lexer/'; + } + else if (0 === strpos($class, 'LimeParser')) + { + $file .= 'parser/'; + } + else if (0 === strpos($class, 'LimeOutput')) + { + $file .= 'output/'; + } + else if (0 === strpos($class, 'LimeMockInvocationMatcher')) + { + $file .= 'mock/matcher/'; + } + else if (0 === strpos($class, 'LimeMock')) + { + $file .= 'mock/'; + } + else if (0 === strpos($class, 'LimeTester')) + { + $file .= 'tester/'; + } + else if (0 === strpos($class, 'LimeShell')) + { + $file .= 'shell/'; + } + else if (0 === strpos($class, 'LimeConstraint')) + { + $file .= 'constraint/'; + } + + $file .= $class.'.php'; + + if (file_exists($file)) + { + require_once $file; + + return true; + } + } + + return false; + } +} + +/** + * Prints the given value to the error stream in a nicely formatted way. + * + * @param mixed $value + */ +function lime_debug($value) +{ + $result = ""; + + if (is_object($value) || is_array($value)) + { + $result = is_object($value) ? sprintf("object(%s) (\n", get_class($value)) : "array ("; + + if (is_object($value)) + { + $value = LimeTesterObject::toArray($value); + } + + foreach ($value as $key => $val) + { + if (is_object($val) || is_array($val)) + { + $output = is_object($val) ? sprintf("object(%s) (", get_class($val)) : "array ("; + + if (is_object($val)) + { + $val = LimeTesterObject::toArray($val); + } + + if (count($val) > 0) + { + $output .= "\n ...\n "; + } + + $output .= ")"; + } + else + { + if (is_string($val) && strlen($val) > 60) + { + $val = substr($val, 0, 57).'...'; + } + + $output = lime_colorize($val); + } + + $result .= sprintf(" %s => %s,\n", var_export($key, true), $output); + } + + $result .= ")"; + } + else + { + $result = lime_colorize($value); + } + + fwrite(STDERR, $result."\n"); +} + +/** + * Returns a colorized export of the given value depending on its type. + * + * @param mixed $value + * @return string + */ +function lime_colorize($value) +{ + static $colorizer = null; + + if (is_null($colorizer) && LimeColorizer::isSupported()) + { + $colorizer = new LimeColorizer(); + $colorizer->setStyle('string', array('fg' => 'cyan')); + $colorizer->setStyle('integer', array('fg' => 'green')); + $colorizer->setStyle('double', array('fg' => 'green')); + $colorizer->setStyle('boolean', array('fg' => 'red')); + } + + $type = gettype($value); + $value = var_export($value, true); + + if (!is_null($colorizer) && in_array($type, array('string', 'integer', 'double', 'boolean'))) + { + $value = $colorizer->colorize($value, $type); + } + + return $value; +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/LimeColorizer.php b/tests/lib/vendor/lime/LimeColorizer.php new file mode 100644 index 000000000000..e85e21eaf399 --- /dev/null +++ b/tests/lib/vendor/lime/LimeColorizer.php @@ -0,0 +1,196 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Colorizes text strings for output in a console. + * + * You can colorize text by calling the method colorize() with a given text + * string: + * + * + * $colorizer = new LimeColorizer(); + * $text = $colorizer->colorize('Hello World', array( + * 'bold' => true, + * 'fg' => 'white', + * 'bg' => 'blue', + * )); + * + * + * You can also predefine styles using the static method setStyle(). + * + * Use the static method isSupported() to find out whether colorization is + * supported by the current OS and console. + * + * @package lime + * @author Fabien Potencier + * @author Bernhard Schussek + * @version SVN: $Id: LimeColorizer.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeColorizer +{ + static protected + $fontStyles = array( + 'bold' => 1, + 'underscore' => 4, + 'blink' => 5, + 'reverse' => 7, + 'conceal' => 8, + ), + $foregroundColors = array( + 'black' => 30, + 'red' => 31, + 'green' => 32, + 'yellow' => 33, + 'blue' => 34, + 'magenta' => 35, + 'cyan' => 36, + 'white' => 37, + ), + $backgroundColors = array( + 'black' => 40, + 'red' => 41, + 'green' => 42, + 'yellow' => 43, + 'blue' => 44, + 'magenta' => 45, + 'cyan' => 46, + 'white' => 47, + ); + + protected + $styles = array(); + + /** + * Returns whether colorization is supported by the current OS and console. + * + * This method returns true if either the current operating system is + * Windows or the console is not a tty console. + * + * @return boolean + */ + public static function isSupported() + { + return DIRECTORY_SEPARATOR != '\\' && function_exists('posix_isatty') && @posix_isatty(STDOUT); + } + + /** + * Registers a color style under a given name. + * + * The options array may contain the following entries: + * + * + * + * + * + * + * + * + * + * + *
Option Value Type Description
bold boolean true for bold text
underscoreboolean true for underscored text
blink boolean true for blinking text
reverse boolean true for text with inverted foreground/background color
conceal boolean true for invisible text
fg color The color of the text
bg color The color of the background
+ * + * The following color values are supported: + * + * * black + * * red + * * green + * * yellow + * * blue + * * magenta + * * cyan + * * white + * + * Example: + * + * + * $colorizer = new LimeColorizer(); + * $colorizer->setStyle('myStyle', array( + * 'bold' => true, + * 'fg' => 'white', + * 'bg' => 'blue', + * )); + * + * + * @param string $name + * @param array $options + */ + public function setStyle($name, array $options = array()) + { + $this->styles[$name] = $options; + } + + /** + * Colorizes a given text. + * + * The second parameter can either be the name of a style predefined with + * setStyle() or an array of style options. For more information about the + * possible style options, see the description of setStyle(). + * + * The returned string contains special codes that are interpreted by the + * shell to format the output. + * + * Example (with options): + * + * + * $colorizer = new LimeColorizer(); + * $text = $colorizer->colorize('Hello World', array( + * 'bold' => true, + * 'fg' => 'white', + * 'bg' => 'blue', + * )); + * + * + * Example (with style name): + * + * + * $colorizer = new LimeColorizer(); + * $colorizer->setStyle('myStyle', array( + * 'bold' => true, + * 'fg' => 'white', + * 'bg' => 'blue', + * )); + * $text = $colorizer->colorize('Hello World', 'myStyle'); + * + * + * @param string $text The text to colorize + * @param string|array $parameters The style name or style options + * + * @return string The colorized text + */ + public function colorize($text = '', $parameters = array()) + { + if (!is_array($parameters) && isset(self::$this->styles[$parameters])) + { + $parameters = $this->styles[$parameters]; + } + + $codes = array(); + if (isset($parameters['fg'])) + { + $codes[] = self::$foregroundColors[$parameters['fg']]; + } + if (isset($parameters['bg'])) + { + $codes[] = self::$backgroundColors[$parameters['bg']]; + } + + foreach (self::$fontStyles as $fontStyle => $code) + { + if (isset($parameters[$fontStyle]) && $parameters[$fontStyle]) + { + $codes[] = $code; + } + } + + return "\033[".implode(';', $codes).'m'.$text."\033[0m"; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/LimeCoverage.php b/tests/lib/vendor/lime/LimeCoverage.php new file mode 100644 index 000000000000..2e3743e9a9e6 --- /dev/null +++ b/tests/lib/vendor/lime/LimeCoverage.php @@ -0,0 +1,250 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeCoverage extends LimeRegistration +{ + const + COVERED = 1, + UNCOVERED = -1; + + protected + $options = array(), + $files = array(), + $suite = null, + $coverage = array(), + $coveredLines = 0, + $uncoveredLines = 0, + $coveredCode = array(), + $uncoveredCode = array(), + $actualCode = array(); + + public function __construct(LimeTestSuite $suite, array $options = array()) + { + $this->suite = $suite; + $this->options = array_merge(array( + 'base_dir' => null, + 'extension' => '.php', + 'verbose' => false, + ), $options); + + // temporary solution, LimeRegistration needs to be modified + $this->setBaseDir($this->options['base_dir']); + $this->setExtension($this->options['extension']); + + if (!function_exists('xdebug_start_code_coverage')) + { + throw new Exception('You must install and enable xdebug before using lime coverage.'); + } + + if (!ini_get('xdebug.extended_info')) + { + throw new Exception('You must set xdebug.extended_info to 1 in your php.ini to use lime coverage.'); + } + } + + public function setFiles($files) + { + if (!is_array($files)) + { + $files = array($files); + } + + $this->files = $files; + } + + public function run() + { + if (!count($this->suite->files)) + { + throw new Exception('You must register some test files before running coverage!'); + } + + if (!count($this->files)) + { + throw new Exception('You must register some files to cover!'); + } + + $this->coverage = array(); + + $this->process($this->suite->files); + $this->parseCoverage($this->coverage); + + $this->render(); + } + + protected function process(array $files) + { + $this->output = new LimeOutput(); + + foreach ($files as $file) + { + $command = new LimeShellCommand($file, array('coverage' => true)); + $command->execute(); + + // script failed + if ($command->getStatus() != LimeShell::SUCCESS) + { + $this->output->echoln(sprintf('Warning: %s returned status %d, results may be inaccurate', $file, $command->getStatus()), LimeOutput::ERROR); + } + + // script succeeded, coverage not readable + if (false === $coverage = @unserialize($command->getOutput())) + { + if ($command->getStatus() == LimeShell::SUCCESS) + { + throw new Exception(sprintf('Unable to unserialize coverage for file "%s"', $file)); + } + } + else + { + foreach ($coverage as $file => $lines) + { + if (!isset($this->coverage[$file])) + { + $this->coverage[$file] = $lines; + } + else + { + foreach ($lines as $line => $flag) + { + if ($flag == self::COVERED) + { + $this->coverage[$file][$line] = 1; + } + } + } + } + } + } + } + + protected function parseCoverage(array $coverage) + { + $this->coveredLines = 0; + $this->uncoveredLines = 0; + + ksort($coverage); + + foreach ($coverage as $file => $lines) + { + $this->coveredCode[$file] = array(); + $this->uncoveredCode[$file] = array(); + + foreach ($lines as $line => $flag) + { + if ($flag == self::COVERED) + { + $this->coveredCode[$file][] = $line; + $this->coveredLines++; + } + else + { + $this->uncoveredCode[$file][] = $line; + $this->uncoveredLines++; + } + } + } + + $lexer = new LimeLexerCodeLines(); + + foreach ($this->files as $file) + { + if (!array_key_exists($file, $this->coveredCode)) + { + $this->coveredCode[$file] = array(); + $this->uncoveredCode[$file] = $lexer->parse($file); + $this->uncoveredLines += count($this->uncoveredCode[$file]); + } + } + } + + protected function render() + { + $printer = new LimePrinter(new LimeColorizer()); + + foreach ($this->files as $file) + { + $totalLines = count($this->coveredCode[$file]) + count($this->uncoveredCode[$file]); + $percent = count($this->coveredCode[$file]) * 100 / max($totalLines, 1); + + $relativeFile = $this->getRelativeFile($file); + + if ($percent == 100) + { + $style = LimePrinter::OK; + } + else if ($percent >= 90) + { + $style = LimePrinter::INFO; + } + else if ($percent <= 20) + { + $style = LimePrinter::NOT_OK; + } + else + { + $style = null; + } + + $printer->printLine(sprintf("%-76s%3.0f%%", $relativeFile, $percent), $style); + + if ($this->options['verbose'] && $percent > 0 && $percent < 100) + { + $printer->printLine(sprintf("missing: %s", $this->formatRange($this->uncoveredCode[$file])), LimePrinter::COMMENT); + } + } + + $totalLines = $this->coveredLines + $this->uncoveredLines; + $percent = $this->coveredLines * 100 / max($totalLines, 1); + + if ($percent <= 20) + { + $style = LimePrinter::NOT_OK; + } + else + { + $style = LimePrinter::HAPPY; + } + + $printer->printLine(str_pad(sprintf(" Total Coverage: %3.0f%%", $percent), 80), $style); + } + + protected function formatRange(array $lines) + { + sort($lines); + $formatted = ''; + $first = -1; + $last = -1; + foreach ($lines as $line) + { + if ($last + 1 != $line) + { + if ($first != -1) + { + $formatted .= $first == $last ? "$first " : "[$first - $last] "; + } + $first = $line; + $last = $line; + } + else + { + $last = $line; + } + } + if ($first != -1) + { + $formatted .= $first == $last ? "$first " : "[$first - $last] "; + } + + return $formatted; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/LimeError.php b/tests/lib/vendor/lime/LimeError.php new file mode 100644 index 000000000000..4c5d54452bef --- /dev/null +++ b/tests/lib/vendor/lime/LimeError.php @@ -0,0 +1,162 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Stores an error and optionally its trace. + * + * This class is similar to PHP's native Exception class, but is guaranteed + * to be serializable. The native Exception class is not serializable if the + * traces contain circular references between objects. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeError.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeError implements Serializable +{ + private + $type = null, + $message = null, + $file = null, + $line = null, + $trace = null; + + /** + * Creates a new instance and copies the data from an exception. + * + * @param Exception $exception + * @return LimeError + */ + public static function fromException(Exception $exception) + { + return new self( + $exception->getMessage(), + $exception->getFile(), + $exception->getLine(), + get_class($exception), + $exception->getTrace() + ); + } + + /** + * Constructor. + * + * @param string $message The error message + * @param string $file The file where the error occurred + * @param integer $line The line where the error occurred + * @param string $type The error type, f.i. "Fatal Error" + * @param array $trace The traces of the error + */ + public function __construct($message, $file, $line, $type = 'Error', array $trace = array()) + { + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->type = $type; + $this->trace = $trace; + } + + /** + * Returns the error type. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Returns the error message. + * + * @return string + */ + public function getMessage() + { + return $this->message; + } + + /** + * Returns the file where the error occurred. + * + * @return string + */ + public function getFile() + { + return $this->file; + } + + /** + * Returns the line where the error occurred. + * + * @return integer + */ + public function getLine() + { + return $this->line; + } + + /** + * Returns the trace of the error. + * + * @return array + */ + public function getTrace() + { + return $this->trace; + } + + /** + * Serializes the error. + * + * @see Serializable#serialize() + * @return string The serialized error content + */ + public function serialize() + { + $traces = $this->trace; + + foreach ($traces as &$trace) + { + if (array_key_exists('args', $trace)) + { + foreach ($trace['args'] as &$value) + { + // TODO: This should be improved. Maybe we can check for recursions + // and only exclude duplicate objects from the trace + if (is_object($value)) + { + // replace object by class name + $value = sprintf('object (%s) (...)', get_class($value)); + } + else if (is_array($value)) + { + $value = 'array(...)'; + } + } + } + } + + return serialize(array($this->file, $this->line, $this->message, $traces, $this->type)); + } + + /** + * Unserializes an error. + * + * @see Serializable#unserialize() + * @param string $data The serialized error content + */ + public function unserialize($data) + { + list($this->file, $this->line, $this->message, $this->trace, $this->type) = unserialize($data); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/LimeExceptionExpectation.php b/tests/lib/vendor/lime/LimeExceptionExpectation.php new file mode 100644 index 000000000000..26d93a0c9001 --- /dev/null +++ b/tests/lib/vendor/lime/LimeExceptionExpectation.php @@ -0,0 +1,41 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeExceptionExpectation +{ + private + $exception = null, + $file = null, + $line = null; + + public function __construct($exception, $file, $line) + { + $this->exception = $exception; + $this->file = $file; + $this->line = $line; + } + + public function getException() + { + return $this->exception; + } + + public function getFile() + { + return $this->file; + } + + public function getLine() + { + return $this->line; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/LimePrinter.php b/tests/lib/vendor/lime/LimePrinter.php new file mode 100644 index 000000000000..c2275c145932 --- /dev/null +++ b/tests/lib/vendor/lime/LimePrinter.php @@ -0,0 +1,127 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimePrinter +{ + const + OK = 0, + NOT_OK = 1, + COMMENT = 2, + SKIP = 3, + WARNING = 4, + ERROR = 5, + HAPPY = 6, + STRING = 7, + NUMBER = 8, + BOOLEAN = 9, + INFO = 10, + TRACE = 11, + TODO = 12; + + protected + $colorizer = null; + + public function __construct(LimeColorizer $colorizer = null) + { + if (!is_null($colorizer)) + { + $colorizer->setStyle(self::OK, array('fg' => 'green', 'bold' => true)); + $colorizer->setStyle(self::NOT_OK, array('bg' => 'red', 'fg' => 'white', 'bold' => true)); + $colorizer->setStyle(self::COMMENT, array('fg' => 'yellow')); + $colorizer->setStyle(self::SKIP, array('fg' => 'yellow', 'bold' => true)); + $colorizer->setStyle(self::TODO, array('fg' => 'yellow', 'bold' => true)); + $colorizer->setStyle(self::WARNING, array('fg' => 'white', 'bg' => 'yellow', 'bold' => true)); + $colorizer->setStyle(self::ERROR, array('bg' => 'red', 'fg' => 'white', 'bold' => true)); + $colorizer->setStyle(self::HAPPY, array('fg' => 'white', 'bg' => 'green', 'bold' => true)); + $colorizer->setStyle(self::STRING, array('fg' => 'cyan')); + $colorizer->setStyle(self::NUMBER, array()); + $colorizer->setStyle(self::BOOLEAN, array('fg' => 'cyan')); + $colorizer->setStyle(self::INFO, array('fg' => 'cyan', 'bold' => true)); + $colorizer->setStyle(self::TRACE, array('fg' => 'green', 'bold' => true)); + } + + $this->colorizer = $colorizer; + } + + public function printText($text, $style = null) + { + print $this->colorize($text, $style); + } + + public function printLine($text, $style = null) + { + print $this->colorize($text, $style)."\n"; + } + + public function printBox($text, $style = null) + { + print $this->colorize(str_pad($text, 80, ' '), $style)."\n"; + } + + public function printLargeBox($text, $style = null) + { + $space = $this->colorize(str_repeat(' ', 80), $style)."\n"; + $text = trim($text); + $text = wordwrap($text, 75, "\n"); + + print "\n".$space; + foreach (explode("\n", $text) as $line) + { + print $this->colorize(str_pad(' '.$line, 80, ' '), $style)."\n"; + } + print $space."\n"; + } + + protected function colorize($text, $style) + { + if (is_null($this->colorizer)) + { + return $text; + } + else + { + if (is_null($style)) + { + return preg_replace_callback('/("[^"]*"|(?|::)?\w+\([^\)]*\)*/.')/', array($this, 'autoColorize'), $text); + } + else + { + return $this->colorizer->colorize($text, $style); + } + } + } + + public function autoColorize($text) + { + $text = $text[0]; + + if (is_null($this->colorizer)) + { + return $text; + } + else + { + if ($text{0} == '"') + { + return $this->colorizer->colorize($text, self::STRING); + } + else if (in_array(strtolower($text), array('true', 'false'))) + { + return $this->colorizer->colorize($text, self::BOOLEAN); + } + else + { + return $this->colorizer->colorize($text, self::NUMBER); + } + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/LimeRegistration.php b/tests/lib/vendor/lime/LimeRegistration.php new file mode 100644 index 000000000000..8c730223f5b0 --- /dev/null +++ b/tests/lib/vendor/lime/LimeRegistration.php @@ -0,0 +1,96 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeRegistration +{ + protected + $files = array(), + $extension = '.php', + $baseDir = ''; + + public function setBaseDir($baseDir) + { + $this->baseDir = $baseDir; + } + + public function setExtension($extension) + { + $this->extension = $extension; + } + + public function getFiles() + { + return $this->files; + } + + public function register($filesOrDirectories) + { + foreach ((array) $filesOrDirectories as $fileOrDirectory) + { + if (is_file($fileOrDirectory)) + { + $this->files[] = realpath($fileOrDirectory); + } + elseif (is_dir($fileOrDirectory)) + { + $this->registerDir($fileOrDirectory); + } + else + { + throw new Exception(sprintf('The file or directory "%s" does not exist.', $fileOrDirectory)); + } + } + } + + public function registerGlob($glob) + { + if ($dirs = glob($glob)) + { + foreach ($dirs as $file) + { + $this->files[] = realpath($file); + } + } + } + + public function registerDir($directory) + { + if (!is_dir($directory)) + { + throw new Exception(sprintf('The directory "%s" does not exist.', $directory)); + } + + $files = array(); + + $currentDir = opendir($directory); + while ($entry = readdir($currentDir)) + { + if ($entry == '.' || $entry == '..') continue; + + if (is_dir($entry)) + { + $this->registerDir($entry); + } + elseif (preg_match('#'.$this->extension.'$#', $entry)) + { + $files[] = realpath($directory.DIRECTORY_SEPARATOR.$entry); + } + } + + $this->files = array_merge($this->files, $files); + } + + protected function getRelativeFile($file) + { + return str_replace(DIRECTORY_SEPARATOR, '/', str_replace(array(realpath($this->baseDir).DIRECTORY_SEPARATOR, $this->extension), '', $file)); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/LimeTest.php b/tests/lib/vendor/lime/LimeTest.php new file mode 100644 index 000000000000..220a6d2261a7 --- /dev/null +++ b/tests/lib/vendor/lime/LimeTest.php @@ -0,0 +1,435 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Unit test library. + * + * @package lime + * @author Fabien Potencier + * @version SVN: $Id: LimeTest.php 23880 2009-11-14 10:14:34Z bschussek $ + */ +class LimeTest +{ + protected + $output = null, + $options = array(), + $errorReporting = true, + $exception = null, + $exceptionExpectation = null; + + public function __construct($plan = null, array $options = array()) + { + $this->options = array( + 'base_dir' => null, + 'output' => 'tap', + 'force_colors' => false, + 'verbose' => false, + 'serialize' => false, + 'coverage' => false, + ); + + foreach (LimeShell::parseArguments($GLOBALS['argv']) as $argument => $value) + { + $this->options[str_replace('-', '_', $argument)] = $value; + } + + $this->options = array_merge($this->options, $options); + + $this->options['base_dir'] = realpath($this->options['base_dir']); + + list ($file, $line) = LimeTrace::findCaller('LimeTest'); + + if ($this->options['coverage']) + { + $this->output = new LimeOutputCoverage(); + } + elseif (is_string($this->options['output'])) + { + $factory = new LimeOutputFactory($this->options); + + $this->output = $factory->create($this->options['output']); + } + else + { + $this->output = $this->options['output']; + } + + $this->output->focus($file); + + if (!is_null($plan)) + { + $this->output->plan($plan); + } + + set_error_handler(array($this, 'handleError')); + + // make sure that exceptions that are not caught by the test runner are + // caught and formatted in an appropriate way + set_exception_handler(array($this, 'handleException')); + } + + public function setErrorReporting($enabled) + { + $this->errorReporting = $enabled; + } + + public function __destruct() + { + $this->output->close(); + $this->output->flush(); + + restore_error_handler(); + restore_exception_handler(); + } + + public function getOutput() + { + return $this->output; + } + + private function test(LimeConstraintInterface $constraint, $value, $message) + { + try + { + $constraint->evaluate($value); + + return $this->pass($message); + } + catch (LimeConstraintException $e) + { + return $this->fail($message, $e->getMessage()); + } + } + + /** + * Tests a condition and passes if it is true + * + * @param mixed $exp condition to test + * @param string $message display output message when the test passes + * + * @return boolean + */ + public function ok($exp, $message = '') + { + if ((boolean)$exp) + { + return $this->pass($message); + } + else + { + return $this->fail($message); + } + } + + /** + * Compares two values and passes if they are equal (==) + * + * @param mixed $exp1 left value + * @param mixed $exp2 right value + * @param string $message display output message when the test passes + * + * @return boolean + */ + public function is($exp1, $exp2, $message = '') + { + return $this->test(new LimeConstraintIs($exp2), $exp1, $message); + } + + /** + * Compares two values and passes if they are identical (===) + * + * @param mixed $exp1 left value + * @param mixed $exp2 right value + * @param string $message display output message when the test passes + * + * @return boolean + */ + public function same($exp1, $exp2, $message = '') + { + return $this->test(new LimeConstraintSame($exp2), $exp1, $message); + } + + /** + * Compares two values and passes if they are not equal + * + * @param mixed $exp1 left value + * @param mixed $exp2 right value + * @param string $message display output message when the test passes + * + * @return boolean + */ + public function isnt($exp1, $exp2, $message = '') + { + return $this->test(new LimeConstraintIsNot($exp2), $exp1, $message); + } + + /** + * Compares two values and passes if they are not identical (!==) + * + * @param mixed $exp1 left value + * @param mixed $exp2 right value + * @param string $message display output message when the test passes + * + * @return boolean + */ + public function isntSame($exp1, $exp2, $message = '') + { + return $this->test(new LimeConstraintNotSame($exp2), $exp1, $message); + } + + /** + * Tests a string against a regular expression + * + * @param string $exp value to test + * @param string $regex the pattern to search for, as a string + * @param string $message display output message when the test passes + * + * @return boolean + */ + public function like($exp1, $exp2, $message = '') + { + return $this->test(new LimeConstraintLike($exp2), $exp1, $message); + } + + /** + * Checks that a string doesn't match a regular expression + * + * @param string $exp value to test + * @param string $regex the pattern to search for, as a string + * @param string $message display output message when the test passes + * + * @return boolean + */ + public function unlike($exp1, $exp2, $message = '') + { + return $this->test(new LimeConstraintUnlike($exp2), $exp1, $message); + } + + public function greaterThan($exp1, $exp2, $message = '') + { + return $this->test(new LimeConstraintGreaterThan($exp2), $exp1, $message); + } + + public function greaterThanEqual($exp1, $exp2, $message = '') + { + return $this->test(new LimeConstraintGreaterThanEqual($exp2), $exp1, $message); + } + + public function lessThan($exp1, $exp2, $message = '') + { + return $this->test(new LimeConstraintLessThan($exp2), $exp1, $message); + } + + public function lessThanEqual($exp1, $exp2, $message = '') + { + return $this->test(new LimeConstraintLessThanEqual($exp2), $exp1, $message); + } + + public function contains($exp1, $exp2, $message = '') + { + return $this->test(new LimeConstraintContains($exp2), $exp1, $message); + } + + public function containsNot($exp1, $exp2, $message = '') + { + return $this->test(new LimeConstraintContainsNot($exp2), $exp1, $message); + } + + /** + * Always passes--useful for testing exceptions + * + * @param string $message display output message + * + * @return true + */ + public function pass($message = '') + { + list ($file, $line) = LimeTrace::findCaller('LimeTest'); + + $this->output->pass($message, $file, $line); + + return true; + } + + /** + * Always fails--useful for testing exceptions + * + * @param string $message display output message + * + * @return false + */ + public function fail($message = '', $error = null) + { + list ($file, $line) = LimeTrace::findCaller('LimeTest'); + + $this->output->fail($message, $file, $line, $error); + + return false; + } + + /** + * Outputs a diag message but runs no test + * + * @param string $message display output message + * + * @return void + */ + public function diag($message) + { + $this->output->comment($message); + } + + /** + * Counts as $nbTests tests--useful for conditional tests + * + * @param string $message display output message + * @param integer $nbTests number of tests to skip + * + * @return void + */ + public function skip($message = '', $nbTests = 1) + { + list ($file, $line) = LimeTrace::findCaller('LimeTest'); + + for ($i = 0; $i < $nbTests; $i++) + { + $this->output->skip($message, $file, $line); + } + } + + /** + * Counts as a test--useful for tests yet to be written + * + * @param string $message display output message + * + * @return void + */ + public function todo($message = '') + { + list ($file, $line) = LimeTrace::findCaller('LimeTest'); + + $this->output->todo($message, $file, $line); + } + + public function comment($message) + { + $this->output->comment($message); + } + + public function mock($class, array $options = array()) + { + return LimeMock::create($class, $this->output, $options); + } + + public function stub($class, array $options = array()) + { + $options = array_merge(array( + 'nice' => true, + 'no_exceptions' => true, + ), $options); + + return LimeMock::create($class, new LimeOutputNone(), $options); + } + + public function extendMock($class, array $options = array()) + { + $options['stub_methods'] = false; + + return $this->mock($class, $options); + } + + public function extendStub($class, array $options = array()) + { + $options['stub_methods'] = false; + + return $this->stub($class, $options); + } + + public function expect($exception, $code = null) + { + list ($file, $line) = LimeTrace::findCaller('LimeTest'); + + $this->exceptionExpectation = new LimeExceptionExpectation($exception, $file, $line); + $this->exception = null; + } + + public function handleError($code, $message, $file, $line, $context) + { + if (!$this->errorReporting || ($code & error_reporting()) == 0) + { + return false; + } + + switch ($code) + { + case E_WARNING: + $message = 'Warning: '.$message; + break; + case E_NOTICE: + $message = 'Notice: '.$message; + break; + } + + $this->output->warning($message, $file, $line); + } + + public function handleException(Exception $exception) + { + if (!is_null($this->exceptionExpectation)) + { + $this->exception = $exception; + } + else + { + $this->output->error(LimeError::fromException($exception)); + } + + return true; + } + + public function verifyException() + { + if (!is_null($this->exceptionExpectation)) + { + $expected = $this->exceptionExpectation->getException(); + $file = $this->exceptionExpectation->getFile(); + $line = $this->exceptionExpectation->getLine(); + + if (is_string($expected)) + { + $actual = is_object($this->exception) ? get_class($this->exception) : 'none'; + $message = sprintf('A "%s" was thrown', $expected); + } + else + { + $actual = $this->exception; + $message = sprintf('A "%s" was thrown', get_class($expected)); + } + + // can't use ->is() here because the custom file and line need to be + // passed to the output + try + { + $constraint = new LimeConstraintIs($expected); + $constraint->evaluate($actual); + + $this->output->pass($message, $file, $line); + } + catch (LimeConstraintException $e) + { + $this->output->fail($message, $file, $line, $e->getMessage()); + } + } + + $this->exceptionExpectation = null; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/LimeTestAnalyzer.php b/tests/lib/vendor/lime/LimeTestAnalyzer.php new file mode 100644 index 000000000000..ad807cd6b918 --- /dev/null +++ b/tests/lib/vendor/lime/LimeTestAnalyzer.php @@ -0,0 +1,97 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeTestAnalyzer +{ + protected + $suppressedMethods = array(), + $output = null, + $errors = '', + $file = null, + $process = null, + $done = true, + $parser = null; + + public function __construct(LimeOutputInterface $output, array $suppressedMethods = array()) + { + $this->suppressedMethods = $suppressedMethods; + $this->output = $output; + } + + public function getConnectedFile() + { + return $this->file; + } + + public function connect($file, array $arguments = array()) + { + $arguments['output'] = 'raw'; + + $this->file = $file; + $this->done = false; + $this->parser = null; + $this->process = new LimeShellProcess($file, $arguments); + $this->process->execute(); + } + + public function proceed() + { + $data = $this->process->getOutput(); + + if (is_null($this->parser)) + { + if (substr($data, 0, 5) == "\0raw\0") + { + $this->parser = new LimeParserRaw($this->output, $this->suppressedMethods); + $data = substr($data, 5); + } + else + { + $this->parser = new LimeParserTap($this->output); + } + } + + $this->parser->parse($data); + + $this->errors .= $this->process->getErrors(); + + while (preg_match('/^(.+)\n/', $this->errors, $matches)) + { + $this->output->warning($matches[1], $this->file, 0); + $this->errors = substr($this->errors, strlen($matches[0])); + } + + if ($this->process->isClosed()) + { + if (!$this->parser->done()) + { + // FIXME: Should be handled in a better way + $buffer = substr($this->parser->buffer, 0, strpos($this->parser->buffer, "\n")); + $this->output->warning(sprintf('Could not parse test output: "%s"', $buffer), $this->file, 1); + } + + // if the last error was not followed by \n, it is still in the buffer + if (!empty($this->errors)) + { + $this->output->warning($this->errors, $this->file, 0); + $this->errors = ''; + } + + $this->done = true; + } + } + + public function done() + { + return $this->done; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/LimeTestCase.php b/tests/lib/vendor/lime/LimeTestCase.php new file mode 100644 index 000000000000..207df13e56d4 --- /dev/null +++ b/tests/lib/vendor/lime/LimeTestCase.php @@ -0,0 +1,59 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeTestCase extends LimeTest +{ + protected + $testRunner = null; + + public function __construct($plan = null, array $options = array()) + { + parent::__construct($plan, $options); + + $this->testRunner = new LimeTestRunner($this->getOutput()); + $this->testRunner->addBefore(array($this, 'setUp')); + $this->testRunner->addAfter(array($this, 'tearDown')); + + // attention: the following lines are not tested + $this->testRunner->addExceptionHandler(array($this, 'handleException')); + $this->testRunner->addAfter(array($this, 'verifyException')); + + foreach (get_class_methods($this) as $method) + { + if (strpos($method, 'test') === 0 && strlen($method) > 4) + { + $this->testRunner->addTest(array($this, $method), $this->humanize($method)); + } + } + } + + public function setUp() {} + + public function tearDown() {} + + public function run() + { + $this->testRunner->run(); + } + + protected function humanize($method) + { + if (substr($method, 0, 4) == 'test') + { + $method = substr($method, 4); + } + + $method = preg_replace('/([a-z])([A-Z])/', '$1 $2', $method); + + return ucfirst(strtolower($method)); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/LimeTestRunner.php b/tests/lib/vendor/lime/LimeTestRunner.php new file mode 100644 index 000000000000..d07c406fa8cc --- /dev/null +++ b/tests/lib/vendor/lime/LimeTestRunner.php @@ -0,0 +1,217 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Runs a set of test methods. + * + * You can add different types of callbacks to a test runner. The most important + * type is the "test" callback. These callbacks are added by calling addTest(). + * All "test" callbacks are executed upon calling run(). + * + * The other callback types are called before or after the "test" callbacks: + * + * - "before all": Called once before all tests + * - "after all": Called once after all tests + * - "before": Called before each test + * - "after": Called after each test + * + * These callbacks are added by calling addBeforeAll(), addAfterAll(), + * addBefore() and addAfter(). You can add multiple callbacks for each type. + * Callbacks are called in the same order in which they are added. + * + * @package lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeTestRunner.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeTestRunner +{ + protected + $output = null, + $beforeAllCallbacks = array(), + $afterAllCallbacks = array(), + $beforeCallbacks = array(), + $afterCallbacks = array(), + $testCallbacks = array(), + $testComments = array(), + $errorCallbacks = array(), + $exceptionCallbacks = array(); + + /** + * Constructor. + * + * @param LimeOutputInterface $output + */ + public function __construct(LimeOutputInterface $output = null) + { + if (is_null($output)) + { + $output = new LimeOutputNone(); + } + + $this->output = $output; + } + + /** + * Runs all registered callbacks. + */ + public function run() + { + foreach ($this->beforeAllCallbacks as $callback) + { + call_user_func($callback); + } + + foreach ($this->testCallbacks as $key => $testCallback) + { + if (!empty($this->testComments[$key])) + { + $this->output->comment($this->testComments[$key]); + } + + foreach ($this->beforeCallbacks as $callback) + { + call_user_func($callback); + } + + try + { + call_user_func($testCallback); + } + catch (Exception $e) + { + $this->handleException($e); + } + + foreach ($this->afterCallbacks as $callback) + { + call_user_func($callback); + } + } + + foreach ($this->afterAllCallbacks as $callback) + { + call_user_func($callback); + } + } + + /** + * Adds a callable that is called once before all tests. + * + * @param callable $callback + * @throws InvalidArgumentException If the argument is no callbale + */ + public function addBeforeAll($callback) + { + $this->assertIsCallable($callback); + $this->beforeAllCallbacks[] = $callback; + } + + /** + * Adds a callable that is called once after all tests. + * + * @param callable $callback + * @throws InvalidArgumentException If the argument is no callbale + */ + public function addAfterAll($callback) + { + $this->assertIsCallable($callback); + $this->afterAllCallbacks[] = $callback; + } + + /** + * Adds a callable that is called before each test. + * + * @param callable $callback + * @throws InvalidArgumentException If the argument is no callbale + */ + public function addBefore($callback) + { + $this->assertIsCallable($callback); + $this->beforeCallbacks[] = $callback; + } + + /** + * Adds a callable that is called after each test. + * + * @param callable $callback + * @throws InvalidArgumentException If the argument is no callbale + */ + public function addAfter($callback) + { + $this->assertIsCallable($callback); + $this->afterCallbacks[] = $callback; + } + + /** + * Adds a test callable. + * + * @param callable $callback + * @throws InvalidArgumentException If the argument is no callbale + */ + public function addTest($callback, $comment = '') + { + $this->assertIsCallable($callback); + $this->testCallbacks[] = $callback; + $this->testComments[] = $comment; + } + + /** + * Adds a callback that is called when an exception is thrown in a test. + * + * The callback retrieves the exception as first argument. It + * should return TRUE if it was able to handle the exception successfully and + * FALSE otherwise. In the latter case, the exception is thrown globally. + * + * @param callable $callback + * @throws InvalidArgumentException If the argument is no callbale + */ + public function addExceptionHandler($callback) + { + $this->assertIsCallable($callback); + $this->exceptionCallbacks[] = $callback; + } + + /** + * Calls all registered exception callbacks. + * + * The exception is passed to the callbacks as first argument. + * + * @param Exception $exception + */ + protected function handleException(Exception $exception) + { + foreach ($this->exceptionCallbacks as $callback) + { + if (true === call_user_func($callback, $exception)) + { + return; + } + } + + throw $exception; + } + + /** + * Asserts that the given argument is a callable. + * + * @param mixed $callable + * @throws InvalidArgumentException If the argument is no callbale + */ + private function assertIsCallable($callable) + { + if (!is_callable($callable)) + { + throw new InvalidArgumentException('The given Argument must be a callable.'); + } + } + +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/LimeTestSuite.php b/tests/lib/vendor/lime/LimeTestSuite.php new file mode 100644 index 000000000000..a2d5e421abc4 --- /dev/null +++ b/tests/lib/vendor/lime/LimeTestSuite.php @@ -0,0 +1,120 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeTestSuite extends LimeRegistration +{ + protected + $options = array(), + $executable = null, + $output = null; + + public function __construct(array $options = array()) + { + $this->options = array_merge(array( + 'base_dir' => null, + 'executable' => null, + 'output' => 'summary', + 'force_colors' => false, + 'verbose' => false, + 'serialize' => false, + 'processes' => 1, + ), $options); + + foreach (LimeShell::parseArguments($GLOBALS['argv']) as $argument => $value) + { + $this->options[str_replace('-', '_', $argument)] = $value; + } + + $this->options['base_dir'] = realpath($this->options['base_dir']); + + if (is_string($this->options['output'])) + { + $factory = new LimeOutputFactory($this->options); + + $type = $this->options['output']; + $output = $factory->create($type); + } + else + { + $output = $this->options['output']; + $type = get_class($output); + } + + if ($this->options['processes'] > 1 && !$output->supportsThreading()) + { + throw new LogicException(sprintf('The output "%s" does not support multi-processing', $type)); + } + + $this->output = new LimeOutputInspectable($output); + } + + public function run() + { + if (!count($this->files)) + { + throw new Exception('You must register some test files before running them!'); + } + + // sort the files to be able to predict the order + sort($this->files); + reset($this->files); + + $connectors = array(); + + for ($i = 0; $i < $this->options['processes']; ++$i) + { + $connectors[] = new LimeTestAnalyzer($this->output, array('focus', 'close', 'flush')); + } + + do + { + $done = true; + + foreach ($connectors as $connector) + { + if ($connector->done() && !is_null(key($this->files))) + { + // start and close the file explicitly in case the file contains syntax errors + $this->output->focus(current($this->files)); + $connector->connect(current($this->files)); + + next($this->files); + } + + if (!$connector->done()) + { + $this->output->focus($connector->getConnectedFile()); + + $connector->proceed(); + $done = false; + + if ($connector->done()) + { + // start and close the file explicitly in case the file contains syntax errors + $this->output->close(); + } + } + } + } + while (!$done); + + $this->output->flush(); + + $planned = $this->output->getPlanned(); + $passed = $this->output->getPassed(); + $failed = $this->output->getFailed(); + $errors = $this->output->getErrors(); + $warnings = $this->output->getWarnings(); + + return 0 == ($failed + $errors + $warnings) && $planned == $passed; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/LimeTools.php b/tests/lib/vendor/lime/LimeTools.php new file mode 100644 index 000000000000..4b2855b5bf05 --- /dev/null +++ b/tests/lib/vendor/lime/LimeTools.php @@ -0,0 +1,43 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Provides static utility methods. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeTools.php 23864 2009-11-13 18:06:20Z bschussek $ + */ +abstract class LimeTools +{ + /** + * Indents every line of the given string for the given number of spaces + * (except for the first line, which is not indented). + * + * @param string $text The input string + * @param integer $numberOfSpaces The number of spaces for indenting the + * input string + * @return string The indented string + */ + public static function indent($text, $numberOfSpaces) + { + $indentation = str_repeat(' ', $numberOfSpaces); + $lines = explode("\n", $text); + + for ($i = 0, $c = count($lines); $i < $c; ++$i) + { + $lines[$i] = $indentation.$lines[$i]; + } + + return implode("\n", $lines); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/LimeTrace.php b/tests/lib/vendor/lime/LimeTrace.php new file mode 100644 index 000000000000..7d482358eb04 --- /dev/null +++ b/tests/lib/vendor/lime/LimeTrace.php @@ -0,0 +1,43 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +abstract class LimeTrace +{ + static public function findCaller($class) + { + $traces = debug_backtrace(); + $result = array($traces[0]['file'], $traces[0]['line']); + + $t = array_reverse($traces); + foreach ($t as $trace) + { + if (isset($trace['object']) && isset($trace['file']) && isset($trace['line'])) + { + $reflection = new ReflectionClass($trace['object']); + + if ($reflection->getName() == $class || $reflection->isSubclassOf($class)) + { + $result = array($trace['file'], $trace['line']); + break; + } + } + } + + // remove .test suffix which is added in case of annotated tests + if (substr($result[0], -5) == '.test') + { + $result[0] = substr($result[0], 0, -5); + } + + return $result; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/constraint/LimeConstraint.php b/tests/lib/vendor/lime/constraint/LimeConstraint.php new file mode 100644 index 000000000000..6b32d440bbae --- /dev/null +++ b/tests/lib/vendor/lime/constraint/LimeConstraint.php @@ -0,0 +1,34 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Base class for all constraints. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeConstraint.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +abstract class LimeConstraint implements LimeConstraintInterface +{ + protected + $expected = null; + + /** + * Constructor. + * + * @param $expected The value against which the constraint should be tested + */ + public function __construct($expected) + { + $this->expected = $expected; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/constraint/LimeConstraintContains.php b/tests/lib/vendor/lime/constraint/LimeConstraintContains.php new file mode 100644 index 000000000000..295cf7430f62 --- /dev/null +++ b/tests/lib/vendor/lime/constraint/LimeConstraintContains.php @@ -0,0 +1,37 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Tests that a value contains another. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeConstraintContains.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeConstraintContains extends LimeConstraint +{ + /** + * (non-PHPdoc) + * @see constraint/LimeConstraintInterface#evaluate($value) + */ + public function evaluate($value) + { + try + { + LimeTester::create($value)->contains(LimeTester::create($this->expected)); + } + catch (LimeAssertionFailedException $e) + { + throw new LimeConstraintException(sprintf("%s\n doesn't contain\n%s", $e->getActual(), $e->getExpected())); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/constraint/LimeConstraintContainsNot.php b/tests/lib/vendor/lime/constraint/LimeConstraintContainsNot.php new file mode 100644 index 000000000000..b7e109c69daf --- /dev/null +++ b/tests/lib/vendor/lime/constraint/LimeConstraintContainsNot.php @@ -0,0 +1,37 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Tests that a value does not contain another. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeConstraintContainsNot.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeConstraintContainsNot extends LimeConstraint +{ + /** + * (non-PHPdoc) + * @see constraint/LimeConstraintInterface#evaluate($value) + */ + public function evaluate($value) + { + try + { + LimeTester::create($value)->containsNot(LimeTester::create($this->expected)); + } + catch (LimeAssertionFailedException $e) + { + throw new LimeConstraintException(sprintf("%s\n must not contain\n%s", $e->getActual(), $e->getExpected())); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/constraint/LimeConstraintException.php b/tests/lib/vendor/lime/constraint/LimeConstraintException.php new file mode 100644 index 000000000000..4bdc06616dc8 --- /dev/null +++ b/tests/lib/vendor/lime/constraint/LimeConstraintException.php @@ -0,0 +1,23 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * This exception is thrown when a constraint fails. + * + * The message of the exception contains the detailed description why the + * constraint failed. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeConstraintException.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeConstraintException extends Exception {} \ No newline at end of file diff --git a/tests/lib/vendor/lime/constraint/LimeConstraintGreaterThan.php b/tests/lib/vendor/lime/constraint/LimeConstraintGreaterThan.php new file mode 100644 index 000000000000..48e6cd1e4c5a --- /dev/null +++ b/tests/lib/vendor/lime/constraint/LimeConstraintGreaterThan.php @@ -0,0 +1,37 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Tests that a value is greater than another. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeConstraintGreaterThan.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeConstraintGreaterThan extends LimeConstraint +{ + /** + * (non-PHPdoc) + * @see constraint/LimeConstraintInterface#evaluate($value) + */ + public function evaluate($value) + { + try + { + LimeTester::create($value)->greaterThan(LimeTester::create($this->expected)); + } + catch (LimeAssertionFailedException $e) + { + throw new LimeConstraintException(sprintf(" %s\nis not > %s", $e->getActual(), $e->getExpected())); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/constraint/LimeConstraintGreaterThanEqual.php b/tests/lib/vendor/lime/constraint/LimeConstraintGreaterThanEqual.php new file mode 100644 index 000000000000..c976132736a1 --- /dev/null +++ b/tests/lib/vendor/lime/constraint/LimeConstraintGreaterThanEqual.php @@ -0,0 +1,37 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Tests that a value is greater than or equal to another. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeConstraintGreaterThanEqual.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeConstraintGreaterThanEqual extends LimeConstraint +{ + /** + * (non-PHPdoc) + * @see constraint/LimeConstraintInterface#evaluate($value) + */ + public function evaluate($value) + { + try + { + LimeTester::create($value)->greaterThanEqual(LimeTester::create($this->expected)); + } + catch (LimeAssertionFailedException $e) + { + throw new LimeConstraintException(sprintf(" %s\nis not >= %s", $e->getActual(), $e->getExpected())); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/constraint/LimeConstraintInterface.php b/tests/lib/vendor/lime/constraint/LimeConstraintInterface.php new file mode 100644 index 000000000000..35e6eda18a44 --- /dev/null +++ b/tests/lib/vendor/lime/constraint/LimeConstraintInterface.php @@ -0,0 +1,32 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Tests whether a value satisfies a constraint. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeConstraintInterface.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +interface LimeConstraintInterface +{ + /** + * Evaluates the constraint for the given value. + * + * If the evaluation fails, a LimeConstraintException with details about the + * error is thrown. + * + * @param mixed $value + * @throws LimeConstraintException If the evaluation fails + */ + public function evaluate($value); +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/constraint/LimeConstraintIs.php b/tests/lib/vendor/lime/constraint/LimeConstraintIs.php new file mode 100644 index 000000000000..a5520109f7b3 --- /dev/null +++ b/tests/lib/vendor/lime/constraint/LimeConstraintIs.php @@ -0,0 +1,37 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Tests that a value equals another. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeConstraintIs.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeConstraintIs extends LimeConstraint +{ + /** + * (non-PHPdoc) + * @see constraint/LimeConstraintInterface#evaluate($value) + */ + public function evaluate($value) + { + try + { + LimeTester::create($value)->is(LimeTester::create($this->expected)); + } + catch (LimeAssertionFailedException $e) + { + throw new LimeConstraintException(sprintf(" got: %s\nexpected: %s", $e->getActual(10), $e->getExpected(10))); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/constraint/LimeConstraintIsNot.php b/tests/lib/vendor/lime/constraint/LimeConstraintIsNot.php new file mode 100644 index 000000000000..019a7fe0461a --- /dev/null +++ b/tests/lib/vendor/lime/constraint/LimeConstraintIsNot.php @@ -0,0 +1,37 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Tests that a value does not equal another. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeConstraintIsNot.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeConstraintIsNot extends LimeConstraint +{ + /** + * (non-PHPdoc) + * @see constraint/LimeConstraintInterface#evaluate($value) + */ + public function evaluate($value) + { + try + { + LimeTester::create($value)->isnt(LimeTester::create($this->expected)); + } + catch (LimeAssertionFailedException $e) + { + throw new LimeConstraintException(sprintf("%s\n must not be\n%s", $e->getActual(), $e->getExpected())); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/constraint/LimeConstraintLessThan.php b/tests/lib/vendor/lime/constraint/LimeConstraintLessThan.php new file mode 100644 index 000000000000..b13ff923ac2c --- /dev/null +++ b/tests/lib/vendor/lime/constraint/LimeConstraintLessThan.php @@ -0,0 +1,37 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Tests that a value is less than another. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeConstraintLessThan.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeConstraintLessThan extends LimeConstraint +{ + /** + * (non-PHPdoc) + * @see constraint/LimeConstraintInterface#evaluate($value) + */ + public function evaluate($value) + { + try + { + LimeTester::create($value)->lessThan(LimeTester::create($this->expected)); + } + catch (LimeAssertionFailedException $e) + { + throw new LimeConstraintException(sprintf(" %s\nis not < %s", $e->getActual(), $e->getExpected())); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/constraint/LimeConstraintLessThanEqual.php b/tests/lib/vendor/lime/constraint/LimeConstraintLessThanEqual.php new file mode 100644 index 000000000000..1908da8dbcbd --- /dev/null +++ b/tests/lib/vendor/lime/constraint/LimeConstraintLessThanEqual.php @@ -0,0 +1,37 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Tests that a value is less than or equal to another. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeConstraintLessThanEqual.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeConstraintLessThanEqual extends LimeConstraint +{ + /** + * (non-PHPdoc) + * @see constraint/LimeConstraintInterface#evaluate($value) + */ + public function evaluate($value) + { + try + { + LimeTester::create($value)->lessThanEqual(LimeTester::create($this->expected)); + } + catch (LimeAssertionFailedException $e) + { + throw new LimeConstraintException(sprintf(" %s\nis not <= %s", $e->getActual(), $e->getExpected())); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/constraint/LimeConstraintLike.php b/tests/lib/vendor/lime/constraint/LimeConstraintLike.php new file mode 100644 index 000000000000..1d9b4a5e555a --- /dev/null +++ b/tests/lib/vendor/lime/constraint/LimeConstraintLike.php @@ -0,0 +1,37 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Tests that a value is like another. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeConstraintLike.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeConstraintLike extends LimeConstraint +{ + /** + * (non-PHPdoc) + * @see constraint/LimeConstraintInterface#evaluate($value) + */ + public function evaluate($value) + { + try + { + LimeTester::create($value)->like(LimeTester::create($this->expected)); + } + catch (LimeAssertionFailedException $e) + { + throw new LimeConstraintException(sprintf(" %s\ndoesn't match %s", $e->getActual(), $e->getExpected())); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/constraint/LimeConstraintNotSame.php b/tests/lib/vendor/lime/constraint/LimeConstraintNotSame.php new file mode 100644 index 000000000000..8c6d1d6d45b7 --- /dev/null +++ b/tests/lib/vendor/lime/constraint/LimeConstraintNotSame.php @@ -0,0 +1,37 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Tests that a value is not identical to another. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeConstraintNotSame.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeConstraintNotSame extends LimeConstraint +{ + /** + * (non-PHPdoc) + * @see constraint/LimeConstraintInterface#evaluate($value) + */ + public function evaluate($value) + { + try + { + LimeTester::create($value)->isntSame(LimeTester::create($this->expected)); + } + catch (LimeAssertionFailedException $e) + { + throw new LimeConstraintException(sprintf("%s\n must not be\n%s", $e->getActual(), $e->getExpected())); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/constraint/LimeConstraintSame.php b/tests/lib/vendor/lime/constraint/LimeConstraintSame.php new file mode 100644 index 000000000000..716f469e58a9 --- /dev/null +++ b/tests/lib/vendor/lime/constraint/LimeConstraintSame.php @@ -0,0 +1,37 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Tests that a value is identical to another. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeConstraintSame.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeConstraintSame extends LimeConstraint +{ + /** + * (non-PHPdoc) + * @see constraint/LimeConstraintInterface#evaluate($value) + */ + public function evaluate($value) + { + try + { + LimeTester::create($value)->same(LimeTester::create($this->expected)); + } + catch (LimeAssertionFailedException $e) + { + throw new LimeConstraintException(sprintf(" got: %s\nexpected: %s", $e->getActual(10), $e->getExpected(10))); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/constraint/LimeConstraintUnlike.php b/tests/lib/vendor/lime/constraint/LimeConstraintUnlike.php new file mode 100644 index 000000000000..8216880cbe9b --- /dev/null +++ b/tests/lib/vendor/lime/constraint/LimeConstraintUnlike.php @@ -0,0 +1,37 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Tests that a value is unlike another. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeConstraintUnlike.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeConstraintUnlike extends LimeConstraint +{ + /** + * (non-PHPdoc) + * @see constraint/LimeConstraintInterface#evaluate($value) + */ + public function evaluate($value) + { + try + { + LimeTester::create($value)->unlike(LimeTester::create($this->expected)); + } + catch (LimeAssertionFailedException $e) + { + throw new LimeConstraintException(sprintf(" %s\nmatches %s", $e->getActual(), $e->getExpected())); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/lexer/LimeLexer.php b/tests/lib/vendor/lime/lexer/LimeLexer.php new file mode 100644 index 000000000000..6273320530c1 --- /dev/null +++ b/tests/lib/vendor/lime/lexer/LimeLexer.php @@ -0,0 +1,365 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Analyzes PHP scripts syntactically. + * + * You can extend this class if you want to write your own lexer that parses + * a PHP file for specific information. + * + * To create your own lexer, implement the methods process() and getResult() + * in your class. process() is called for every token in the file. You can use + * the methods of this class to retrieve more information about the context of + * the token, f.i. whether the token is inside a class or function etc. + * + * The method getResult() must return the value that should be returned by + * parse(). + * + * A lexer is stateless. This means that you can analyze any number of PHP + * scripts with the same lexer instance. + * + * @package Lime + * @author Bernhard Schussek + * @author Fabien Potencier + * @version SVN: $Id: LimeLexer.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +abstract class LimeLexer +{ + private + $continue, + $currentClass, + $inClassDeclaration, + $currentFunction, + $inFunctionDeclaration, + $endOfCurrentExpr, + $currentLine; + + /** + * Analyzes the given file or PHP code. + * + * @param string $content A file path or a string with PHP code. + * + * @return mixed The result from getResult() + */ + public function parse($content) + { + if (is_readable($content)) + { + $content = file_get_contents($content); + } + + $this->continue = true; + $this->currentClass = array(); + $this->inClassDeclaration = false; + $this->currentFunction = array(); + $this->inFunctionDeclaration = false; + $this->endOfCurrentExpr = true; + $this->currentLine = 1; + + $tokens = token_get_all($content); + $openBraces = 0; + foreach ($tokens as $token) + { + if (is_string($token)) + { + switch ($token) + { + case '{': + ++$openBraces; + $this->inClassDeclaration = false; + $this->inFunctionDeclaration = false; + break; + case ';': + // abstract functions + if ($this->inFunctionDeclaration) + { + $this->inFunctionDeclaration = false; + unset($this->currentFunction[$openBraces]); + } + $this->endOfCurrentExpr = true; + break; + case '}': + $this->endOfCurrentExpr = true; + break; + } + + $this->beforeProcess($token, null); + $this->process($token, null); + $this->afterProcess($token, null); + + switch ($token) + { + case '}': + --$openBraces; + if (array_key_exists($openBraces, $this->currentClass)) + { + unset($this->currentClass[$openBraces]); + } + if (array_key_exists($openBraces, $this->currentFunction)) + { + unset($this->currentFunction[$openBraces]); + } + break; + } + } + else + { + list($id, $text) = $token; + + switch ($id) + { + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + ++$openBraces; + break; + case T_OPEN_TAG: + case T_CLOSE_TAG: + $this->endOfCurrentExpr = true; + $this->currentLine += count(explode("\n", $text)) - 1; + break; + case T_WHITESPACE: + case T_START_HEREDOC: + case T_CONSTANT_ENCAPSED_STRING: + case T_ENCAPSED_AND_WHITESPACE: + case T_COMMENT: + case T_DOC_COMMENT: + $this->currentLine += count(explode("\n", $text)) - 1; + break; + case T_ABSTRACT: + if ($this->inClass()) + { + $this->currentFunction[$openBraces] = null; + $this->inFunctionDeclaration = true; + } + else + { + $this->currentClass[$openBraces] = null; + $this->inClassDeclaration = true; + } + break; + case T_INTERFACE: + case T_CLASS: + $this->currentClass[$openBraces] = null; + $this->inClassDeclaration = true; + break; + case T_FUNCTION: + $this->currentFunction[$openBraces] = null; + $this->inFunctionDeclaration = true; + break; + case T_STRING: + if (array_key_exists($openBraces, $this->currentClass) && is_null($this->currentClass[$openBraces])) + { + $this->currentClass[$openBraces] = $text; + } + if (array_key_exists($openBraces, $this->currentFunction) && is_null($this->currentFunction[$openBraces])) + { + $this->currentFunction[$openBraces] = $text; + } + break; + case T_AND_EQUAL: + case T_BREAK: + case T_CASE: + case T_CATCH: + case T_CLONE: + case T_CONCAT_EQUAL: + case T_CONTINUE: + case T_DEC: + case T_DECLARE: + case T_DEFAULT: + case T_DIV_EQUAL: + case T_DO: + case T_ECHO: + case T_ELSEIF: + case T_EMPTY: + case T_ENDDECLARE: + case T_ENDFOR: + case T_ENDFOREACH: + case T_ENDIF: + case T_ENDSWITCH: + case T_ENDWHILE: + case T_END_HEREDOC: + case T_EVAL: + case T_EXIT: + case T_FOR: + case T_FOREACH: + case T_GLOBAL: + case T_IF: + case T_INC: + case T_INCLUDE: + case T_INCLUDE_ONCE: + case T_INSTANCEOF: + case T_ISSET: + case T_IS_EQUAL: + case T_IS_GREATER_OR_EQUAL: + case T_IS_IDENTICAL: + case T_IS_NOT_EQUAL: + case T_IS_NOT_IDENTICAL: + case T_IS_SMALLER_OR_EQUAL: + case T_LIST: + case T_LOGICAL_AND: + case T_LOGICAL_OR: + case T_LOGICAL_XOR: + case T_MINUS_EQUAL: + case T_MOD_EQUAL: + case T_MUL_EQUAL: + case T_NEW: + case T_OBJECT_OPERATOR: + case T_OR_EQUAL: + case T_PLUS_EQUAL: + case T_PRINT: + case T_REQUIRE: + case T_REQUIRE_ONCE: + case T_RETURN: + case T_SL: + case T_SL_EQUAL: + case T_SR: + case T_SR_EQUAL: + case T_SWITCH: + case T_THROW: + case T_TRY: + case T_UNSET: + case T_UNSET_CAST: + case T_USE: + case T_WHILE: + case T_XOR_EQUAL: + $this->endOfCurrentExpr = false; + break; + } + + $this->beforeProcess($text, $id); + $this->process($text, $id); + $this->afterProcess($text, $id); + } + + if (!$this->continue) + { + break; + } + } + + return $this->getResult(); + } + + protected function beforeProcess($text, $id) + { + } + + protected function afterProcess($text, $id) + { + } + + /** + * Processes a token in the PHP code. + * + * @param string $text The string representation of the token + * @param integer $id The token identifier (f.i. T_VARIABLE) or NULL, if + * the token does not have an identifier. + */ + abstract protected function process($text, $id); + + /** + * Returns the result of the lexing process. + * + * @return mixed + */ + abstract protected function getResult(); + + /** + * Returns the line number at the current position of the lexer. + * + * @return integer + */ + protected function getCurrentLine() + { + return $this->currentLine; + } + + /** + * Returns the class name at the current position of the lexer. + * + * @return string Returns NULL if the current position is not inside a class. + */ + protected function getCurrentClass() + { + return $this->inClass() ? end($this->currentClass) : null; + } + + /** + * Returns the function name at the current position of the lexer. + * + * @return string Returns NULL if the current position is not inside a function. + */ + protected function getCurrentFunction() + { + return $this->inFunction() ? end($this->currentFunction) : null; + } + + /** + * Returns whether the current position of the lexer is inside a class. + * + * @return boolean + */ + protected function inClass() + { + return count($this->currentClass) > 0; + } + + /** + * Returns whether the current position of the lexer is inside a class + * declaration (f.i. "abstract class ClassName extends BaseClass"). + * + * @return boolean + */ + protected function inClassDeclaration() + { + return $this->inClassDeclaration; + } + + /** + * Returns whether the current position of the lexer is inside a function. + * + * @return boolean + */ + protected function inFunction() + { + return count($this->currentFunction) > 0; + } + + /** + * Returns whether the current position of the lexer is inside a function + * declaration (f.i. "protected function myFunctionName()"). + * + * @return boolean + */ + protected function inFunctionDeclaration() + { + return $this->inFunctionDeclaration; + } + + /** + * Returns whether the current token marks the end of the last expression. + * + * @return boolean + */ + protected function isEndOfCurrentExpr() + { + return $this->endOfCurrentExpr; + } + + /** + * Tells the lexer to stop lexing. + */ + protected function stop() + { + $this->continue = false; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/lexer/LimeLexerAnnotationAware.php b/tests/lib/vendor/lime/lexer/LimeLexerAnnotationAware.php new file mode 100644 index 000000000000..a84cef1baa11 --- /dev/null +++ b/tests/lib/vendor/lime/lexer/LimeLexerAnnotationAware.php @@ -0,0 +1,213 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Analyzes PHP scripts taking annotations into account. + * + * Like LimeLexer, this class analyzes PHP scripts syntactically but is aware + * of annotations. Annotations are expected to be expressed using single + * line comments. Optionally, you can add a comment to the annotation, which + * needs to be separated from the annotation by any number of colons or spaces. + * + * + * // @Annotation: Optional comment + * + * + * You can extend this class if you want to write your own lexer that takes + * existing annotations into account. You have to pass a number of + * expected annotations to the constructor. Any other exception that is not + * passed in this array will result in an exception during parsing. + * + * + * $lexer = new CustomLexerAnnotationAware(array('Annotation1', 'Annotation2')); + * + * + * The following script will lead to an error when parsed: + * + * + * $i = 1; + * // @Annotation3 + * $i++; + * + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeLexerAnnotationAware.php 23701 2009-11-08 21:23:40Z bschussek $ + * @see LimeLexer + */ +abstract class LimeLexerAnnotationAware extends LimeLexer +{ + private + $allowedAnnotations, + $currentAnnotation, + $currentAnnotationComment, + $inAnnotation, + $inAnnotationDeclaration; + + /** + * Constructor. + * + * Accepts an array of expected annotation names as argument. Any annotation + * that is not listed in this array will cause an exception during parsing. + * + * @param array $allowedAnnotations The list of allowed annotations. + */ + public function __construct(array $allowedAnnotations = array()) + { + $this->allowedAnnotations = $allowedAnnotations; + } + + /** + * (non-PHPdoc) + * @see lexer/LimeLexer#parse($content) + */ + public function parse($content) + { + $this->currentAnnotation = null; + $this->currentAnnotationComment = null; + $this->inAnnotation = false; + $this->inAnnotationDeclaration = false; + + return parent::parse($content); + } + + /** + * (non-PHPdoc) + * @see lexer/LimeLexer#beforeProcess($text, $id) + */ + protected function beforeProcess($text, $id) + { + if (!$this->inClass() && !$this->inFunction() && $id = T_COMMENT && strpos($text, '//') === 0) + { + list($annotation, $comment) = $this->extractAnnotation($text); + + if (!is_null($annotation)) + { + $this->currentAnnotation = $annotation; + $this->currentAnnotationComment = $comment; + $this->inAnnotation = true; + $this->inAnnotationDeclaration = true; + } + } + else + { + $this->inAnnotationDeclaration = false; + } + } + + /** + * Returns whether the parser currently is within any annotation. + * + * All the code following an annotation declaration is considered to be + * inside this annotation's block. In annotated script, this method will thus + * only return false before the first annotation declaration. + * + * @return boolean TRUE if any annotation declaration preceded the current + * position of the lexer + */ + protected function inAnnotation() + { + return $this->inAnnotation; + } + + /** + * Returns whether the parser is currently inside an annotation declaration. + * + * An annotation declaration is any single line comment with a word that + * starts with "@" and any optional following comments. Annotations and + * comments have to be separated by one or more spaces or colons. + * + * + * // @Annotation: Optional comment + * + * + * @return boolean + */ + protected function inAnnotationDeclaration() + { + return $this->inAnnotationDeclaration; + } + + /** + * Returns the name of the currently active annotation. + * + * @return boolean + * @see inAnnotation() + */ + protected function getCurrentAnnotation() + { + return $this->currentAnnotation; + } + + /** + * Returns the comment of the currently active annotation. + * + * @return boolean + * @see inAnnotation() + */ + protected function getCurrentAnnotationComment() + { + return $this->currentAnnotationComment; + } + + /** + * Returns the array of allowed annotation names. + * + * This array can be set in the constructor. + * + * @return array + */ + protected function getAllowedAnnotations() + { + return $this->allowedAnnotations; + } + + /** + * Extracts an annotation from a single-line comment and validates it. + * + * Possible valid annotations are: + * + * // @Annotation + * // @Annotation: Some comment here + * + * + * The results for those annotations are: + * + * array('Annotation', null); + * array('Annotation', 'Some comment here'); + * + * + * @param string $text Some code + * + * @return array An array with the annotation name and the annotation + * comment. If either of both cannot be read, it is NULL. + */ + protected function extractAnnotation($text) + { + if (preg_match('/^\/\/\s*@(\w+)([:\s]+(.*))?\s*$/', $text, $matches)) + { + $annotation = $matches[1]; + $data = count($matches) > 3 ? trim($matches[3]) : null; + + if (!in_array($annotation, $this->allowedAnnotations)) + { + throw new LogicException(sprintf('The annotation "%s" is not valid', $annotation)); + } + + return array($annotation, $data); + } + else + { + return array(null, null); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/lexer/LimeLexerCodeLines.php b/tests/lib/vendor/lime/lexer/LimeLexerCodeLines.php new file mode 100644 index 000000000000..939ecb81b149 --- /dev/null +++ b/tests/lib/vendor/lime/lexer/LimeLexerCodeLines.php @@ -0,0 +1,100 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Processes a source file for the lines of code and returns the line numbers. + * + * The following rules apply for detecting LOC and are conformant with + * xdebug_get_code_coverage(): + * + * * identifier in function declaration == LOC + * * class declaration != LOC + * * method declaration != LOC + * * property declaration != LOC + * * } == LOC + * * { != LOC + * * { after class declaration == LOC + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeLexerCodeLines.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeLexerCodeLines extends LimeLexer +{ + private + $lines = array(); + + /** + * (non-PHPdoc) + * @see lexer/LimeLexer#parse($content) + */ + public function parse($content) + { + $this->lines = array(); + + return parent::parse($content); + } + + /** + * (non-PHPdoc) + * @see lexer/LimeLexer#process($text, $id) + */ + protected function process($text, $id) + { + // whitespace is ignored + if ($id == T_WHITESPACE) + { + return; + } + // PHP tags are ignored + else if ($id == T_OPEN_TAG || $id == T_CLOSE_TAG) + { + return; + } + // class declarations are ignored + else if ($this->inClassDeclaration()) + { + return; + } + // function declarations are ignored, except for the identifier + else if ($this->inFunctionDeclaration() && $id != T_STRING) + { + return; + } + // method declarations are ignored + else if ($this->inClass() && $this->inFunctionDeclaration()) + { + return; + } + // everything in classes except function body, the { and the } of the class is ignored + else if ($this->inClass() && !$this->inFunction() && $text != '{' && $text != '}') + { + return; + } + // { is ignored, except for after class declarations + else if ($text == '{' && !($this->inClass() && !$this->inFunction())) + { + return; + } + + $this->lines[$this->getCurrentLine()] = true; + } + + /** + * (non-PHPdoc) + * @see lexer/LimeLexer#getResult() + */ + protected function getResult() + { + return array_keys($this->lines); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/lexer/LimeLexerTestVariable.php b/tests/lib/vendor/lime/lexer/LimeLexerTestVariable.php new file mode 100644 index 000000000000..7f1f44b7087a --- /dev/null +++ b/tests/lib/vendor/lime/lexer/LimeLexerTestVariable.php @@ -0,0 +1,89 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Extracts the first global variable containing a reference to an instance of + * LimeTest or any subclass from a source file. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeLexerTestVariable.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeLexerTestVariable extends LimeLexer +{ + const + NORMAL = 0, + VARIABLE = 1, + ASSIGNMENT = 2, + INSTANTIATION = 3; + + protected + $lastVariable = null, + $testVariable = null, + $state = self::NORMAL; + + /** + * This method implements a turing machine for variable assignments. + * + * Once a variable name is caught, the object is set to state VARIABLE. + * When the variable is succeeded by an assignment operator "=", the state + * is set to ASSIGNMENT. If the assignment operator is succeeded by the + * keyword "new", the state is set to INSTANTIATION. If the assignment + * operator is succeeded by a class name that inherits class LimeTest, + * processing is stopped and the variable name is returned. Otherwise, + * the state is reset and processing continues. + * + * @see LimeLexer#process($text, $id) + */ + protected function process($text, $id) + { + if ($id == T_VARIABLE && !$this->inFunction()) + { + $this->lastVariable = $text; + $this->state = self::VARIABLE; + } + else if ($text == '=' && $this->state == self::VARIABLE) + { + $this->state = self::ASSIGNMENT; + } + else if ($id == T_NEW && $this->state == self::ASSIGNMENT) + { + $this->state = self::INSTANTIATION; + } + else if ($id == T_STRING && $this->state == self::INSTANTIATION) + { + if (class_exists($text)) + { + $class = new ReflectionClass($text); + if ($text == 'LimeTest' || $class->isSubclassOf('LimeTest')) + { + $this->testVariable = $this->lastVariable; + $this->stop(); + } + } + $this->state = self::NORMAL; + } + else if ($id != T_WHITESPACE) + { + $this->state = self::NORMAL; + } + } + + /** + * (non-PHPdoc) + * @see LimeLexer#getResult() + */ + protected function getResult() + { + return $this->testVariable; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/lexer/LimeLexerTransformAnnotations.php b/tests/lib/vendor/lime/lexer/LimeLexerTransformAnnotations.php new file mode 100644 index 000000000000..46b52567850c --- /dev/null +++ b/tests/lib/vendor/lime/lexer/LimeLexerTransformAnnotations.php @@ -0,0 +1,222 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Transforms annotated code in a file into functions. + * + * The created function names are returned by the function parse(), indexed + * by annotation name. + * + * + * $lexer = new LimeLexerTransformAnnotations('path/to/transformed/file.php', array('First', 'Second')); + * $functions = $lexer->parse('/path/to/original/file.php'); + * + * // => array('First' => array(...), 'Second' => array(...)) + * + * + * The annotated source file for the above code could look like this: + * + * + * $test = 'nothing'; + * + * // @First + * $test = 'First'; + * + * // @Second + * $test = 'Second'; + * + * // @First + * echo $test; + * + * + * You can include the transformed file and execute a certain subset of + * annotations: + * + * + * include 'path/to/transformed/file.php'; + * + * foreach ($functions['First'] as $function) + * { + * $function(); + * } + * + * // => First + * + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeLexerTransformAnnotations.php 23701 2009-11-08 21:23:40Z bschussek $ + * @see LimeLexerAnnotationAware + */ +class LimeLexerTransformAnnotations extends LimeLexerAnnotationAware +{ + protected static + $annotations = array('Test', 'Before', 'After', 'BeforeAll', 'AfterAll'); + + protected + $fileName, + $file, + $variables, + $functions, + $functionCount, + $initialized, + $testVariable, + $classBuffer, + $classNotLoaded, + $firstAnnotation; + + /** + * Constructor. + * + * @param string $targetFile The file where the transformed code + * will be written. + * @param array $allowedAnnotations The allowed annotations. + */ + public function __construct($targetFile) + { + parent::__construct(self::$annotations); + + $this->fileName = $targetFile; + } + + /** + * Transforms the annoated code in the given file and writes it to the + * target file. + * + * @see LimeLexer#parse($content) + */ + public function parse($content) + { + if (is_readable($content)) + { + $content = file_get_contents($content); + } + + $lexer = new LimeLexerVariables($this->getAllowedAnnotations(), array('Before')); + $this->variables = $lexer->parse($content); + + $lexer = new LimeLexerTestVariable(); + $this->testVariable = $lexer->parse($content); + + $this->initialized = false; + $this->functionCount = 0; + $this->functions = array(); + $this->classBuffer = ''; + $this->classNotLoaded = false; + $this->firstAnnotation = true; + + foreach ($this->getAllowedAnnotations() as $annotation) + { + $this->functions[$annotation] = array(); + } + + // backup the contents for the case that the path == filename + $this->file = fopen($this->fileName, 'w'); + + $result = parent::parse($content); + + if ($this->inAnnotation()) + { + fwrite($this->file, "\n}"); + } + + fclose($this->file); + + return $result; + } + + /** + * Returns the name of the first global variable that contains an instance + * of LimeTest or any subclass. + * + * If no such variable could be detected, NULL is returned. + * + * @return string + */ + public function getTestVariable() + { + return $this->testVariable; + } + + /** + * (non-PHPdoc) + * @see LimeLexer#process($text, $id) + */ + protected function process($text, $id) + { + if (!$this->inClassDeclaration()) + { + $this->classBuffer = ''; + } + + if (!$this->inClass()) + { + $this->classNotLoaded = false; + } + + // Some classes are automatically loaded when the script is opened, others + // are not. These other classes need to be left in the source code, + // otherwise they cannot be instantiated later. + // This functionality is covered in LimeAnnotationSupportTest 11+12 + if ($this->inClassDeclaration()) + { + if ($this->getCurrentClass() && !class_exists($this->getCurrentClass()) && !interface_exists($this->getCurrentClass())) + { + $this->classNotLoaded = true; + $text = $this->classBuffer.$text; + $this->classBuffer = ''; + } + else + { + $this->classBuffer .= $text; + } + } + + if ($id == T_OPEN_TAG && !$this->initialized) + { + if (count($this->variables)) + { + $text .= 'global '.implode(', ', $this->variables).';'; + } + $this->initialized = true; + } + else if ($this->inClass() && $this->classNotLoaded) + { + // just print + } + else if ($this->inClass() || $this->inFunction()) + { + $text = str_repeat("\n", count(explode("\n", $text)) - 1); + } + else if ($this->inAnnotationDeclaration()) + { + $functionName = '__lime_annotation_'.($this->functionCount++); + $this->functions[$this->getCurrentAnnotation()][] = array($functionName, $this->getCurrentAnnotationComment()); + + $text = $this->firstAnnotation ? '' : '} '; + $this->firstAnnotation = false; + $variables = count($this->variables) ? sprintf('global %s;', implode(', ', $this->variables)) : ''; + $text .= sprintf("function %s() { %s\n", $functionName, $variables); + } + + fwrite($this->file, $text); + } + + /** + * (non-PHPdoc) + * @see LimeLexer#getResult() + */ + protected function getResult() + { + return $this->functions; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/lexer/LimeLexerVariables.php b/tests/lib/vendor/lime/lexer/LimeLexerVariables.php new file mode 100644 index 000000000000..6cc980ee12dc --- /dev/null +++ b/tests/lib/vendor/lime/lexer/LimeLexerVariables.php @@ -0,0 +1,65 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Extracts all global variables from a source file. + * + * This lexer includes all global variables that are not inside annotations, + * except variables from the scope of the annotations passed to the constructor, + * which are included as well. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeLexerVariables.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeLexerVariables extends LimeLexerAnnotationAware +{ + protected + $includedAnnotations = array(), + $variables = array(); + + /** + * Constructor. + * + * @param array $allowedAnnotations The list of allowed annotation names + * @param array $includedAnnotations The list of annotation names whose + * variables are considered global + */ + public function __construct(array $allowedAnnotations = array(), array $includedAnnotations = array()) + { + parent::__construct($allowedAnnotations); + + $this->includedAnnotations = $includedAnnotations; + } + + /** + * (non-PHPdoc) + * @see LimeLexer#process($text, $id) + */ + protected function process($text, $id) + { + if ($id == T_VARIABLE && !$this->inClass() && !$this->inFunction() + && (!$this->inAnnotation() || in_array($this->getCurrentAnnotation(), $this->includedAnnotations))) + { + $this->variables[] = $text; + } + } + + /** + * (non-PHPdoc) + * @see LimeLexer#getResult() + */ + protected function getResult() + { + return array_unique($this->variables); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/lime.php b/tests/lib/vendor/lime/lime.php new file mode 100644 index 000000000000..c7932411a46c --- /dev/null +++ b/tests/lib/vendor/lime/lime.php @@ -0,0 +1,262 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +require_once dirname(__FILE__).'/LimeAutoloader.php'; + +LimeAutoloader::enableLegacyMode(); +LimeAutoloader::register(); + +class lime_test extends LimeTest +{ + public function __construct($plan = null, $options = array()) + { + // for BC + if (!is_array($options)) + { + $options = array(); // drop the old output because it is not compatible with LimeTest + } + + parent::__construct($plan, $options); + } + + static public function to_array() + { + return self::toArray(); + } + + static public function to_xml($results = null) + { + return self::toXml($results); + } + + /** + * Compares two arguments with an operator + * + * @param mixed $exp1 left value + * @param string $op operator + * @param mixed $exp2 right value + * @param string $message display output message when the test passes + * + * @return boolean + */ + public function cmp_ok($exp1, $op, $exp2, $message = '') + { + switch ($op) + { + case '===': + return $this->same($exp1, $exp2, $message); + case '!==': + return $this->isntSame($exp1, $exp2, $message); + case '==': + return $this->is($exp1, $exp2, $message); + case '!=': + return $this->isnt($exp1, $exp2, $message); + case '<': + return $this->lessThan($exp1, $exp2, $message); + case '<=': + return $this->lessThanEqual($exp1, $exp2, $message); + case '>': + return $this->greaterThan($exp1, $exp2, $message); + case '>=': + return $this->greaterThanEqual($exp1, $exp2, $message); + default: + throw new InvalidArgumentException(sprintf('Unknown operation "%s"', $op)); + } + } + + /** + * Checks the availability of a method for an object or a class + * + * @param mixed $object an object instance or a class name + * @param string|array $methods one or more method names + * @param string $message display output message when the test passes + * + * @return boolean + */ + public function can_ok($object, $methods, $message = '') + { + $result = true; + $failedMessages = array(); + foreach ((array) $methods as $method) + { + if (!method_exists($object, $method)) + { + $failedMessages[] = sprintf("method '%s' does not exist", $method); + $result = false; + } + } + + return $this->test_ok($result, $message, implode("\n", $failedMessages)); + } + + /** + * Checks the type of an argument + * + * @param mixed $var variable instance + * @param string $class class or type name + * @param string $message display output message when the test passes + * + * @return boolean + */ + public function isa_ok($var, $class, $message = '') + { + $type = is_object($var) ? get_class($var) : gettype($var); + $error = sprintf("variable isn't a '%s' it's a '%s'", $class, $type); + + return $this->test_ok($type == $class, $message, $error); + } + + public function is_deeply($exp1, $exp2, $message = '') + { + return $this->is($exp1, $exp2, $message); + } + + public function include_ok($file, $message = '') + { + return $this->includeOk($file, $message); + } + + public function error($message) + { + list($file, $line) = LimeTrace::findCaller('lime_test'); + + $this->output->error(new LimeError($message, $file, $line)); + } + + /** + * @deprecated Use comment() instead + * @param $message + * @return unknown_type + */ + public function info($message) + { + if ($this->output instanceof LimeOutputTap) + { + $this->output->info($message); + } + } + + private function test_ok($condition, $message, $error = null) + { + list ($file, $line) = LimeTrace::findCaller('LimeTest'); + + if ($result = (boolean) $condition) + { + $this->output->pass($message, $file, $line); + } + else + { + $this->output->fail($message, $file, $line, $error); + } + + return $result; + } +} + +class lime_output extends LimeOutput +{ + public function green_bar($message) + { + return $this->greenBar($message); + } + + public function red_bar($message) + { + return $this->redBar($message); + } +} + +class lime_output_color extends LimeOutput +{ +} + +class lime_colorizer extends LimeColorizer +{ + protected static + $instances = array(), + $staticStyles = array(); + + public function __construct() + { + self::$instances[] = $this; + $this->styles = self::$staticStyles; + } + + public static function style($name, $options = array()) + { + foreach (self::$instances as $instance) + { + $instance->setStyle($name, $options); + } + self::$staticStyles[$name] = $options; + } +} + +class lime_harness extends LimeTestSuite +{ + public function __construct($options = array()) + { + // for BC + if (!is_array($options)) + { + $options = array(); // drop the old output because it is not compatible with LimeTest + } + else if (array_key_exists('php_cli', $options)) + { + $options['executable'] = $options['php_cli']; + unset($options['php_cli']); + } + + parent::__construct($options); + } + + public function to_array() + { + return $this->toArray(); + } + + public function to_xml() + { + return $this->toXml(); + } + + public function get_failed_files() + { + return $this->output->getFailedFiles(); + } +} + +class lime_coverage extends LimeCoverage +{ + public static function get_php_lines($content) + { + return self::getPhpLines($content); + } + + public function format_range($lines) + { + return $this->formatRange($lines); + } +} + +class lime_registration extends LimeRegistration +{ + public function register_glob($glob) + { + return $this->registerGlob($glob); + } + + public function register_dir($directory) + { + return $this->registerDir($directory); + } +} diff --git a/tests/lib/vendor/lime/mock/LimeMock.php b/tests/lib/vendor/lime/mock/LimeMock.php new file mode 100644 index 000000000000..c0060c3ab7b4 --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMock.php @@ -0,0 +1,345 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Generates mock objects + * + * This class generates configurable mock objects based on existing interfaces, + * classes or virtual (non-existing) class names. You can use it to create + * objects of classes that you have not implemented yet, or to substitute + * an existing class in a test. + * + * A mock object is created with the create() method: + * + * + * $mock = LimeMock::create('MyClass', $output); + * + * + * Note: The LimeTest class offers an easy access to preconfigured mocks and + * stubs using the methods mock() and stub(). + * + * Initially the mock is in recording mode. In this mode you just make the + * expected method calls with the expected parameters. You can use modifiers + * to configure return values or exceptions that should be thrown. + * + * + * // method "someMethod()" returns "return value" when called with "parameter" + * $mock->someMethod('parameter')->returns('return value'); + * + * + * You can find the complete list of method modifiers in class + * LimeMockInvocationExpectation. By default, expected methods are initialized + * with the modifier once(). If the option "nice" is set, the method is + * initialized with the modifier any() instead. + * + * Once the recording is over, you must call the method replay() on the mock. + * After the call to this method, the mock is in replay mode. In this mode, it + * listens for method calls and returns the results configured before. + * + * + * $mock = LimeMock::create('MyClass', $output); + * $mock->add(1, 2)->returns(3); + * $mock->replay(); + * + * echo $mock->add(1, 2); + * // returns 3 + * + * + * You also have the possibility to find out whether all the configured + * methods have been called with the right parameters while in replay mode + * by calling verify(). + * + * + * $mock = LimeMock::create('MyClass', $output); + * $mock->add(1,2); + * $mock->replay(); + * $mock->add(1); + * $mock->verify(); + * + * // results in a failing test + * + * + * The method create() accepts several options to configure the created mock: + * + * * strict: If set to TRUE, the mock expects methods to be + * called in the same order in which they were recorded. + * Additionally, method parameters will be compared + * with strict typing. Default: FALSE + * * generate_controls: If set to FALSE, the mock's control methods + * replay(), verify() etc. will not be generated. + * Setting this option is useful when the mocked + * class contains any of these methods. You then have + * to access the control methods statically in this + * class, f.i. LimeMock::replay($mock); + * Default: TRUE + * * stub_methods: If set to FALSE, method implementations in the + * mocked class are called when a method is not + * configured to be stubbed. Default: TRUE + * * nice: See LimeMockBehaviour + * * no_exceptions: See LimeMockBehaviour + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMock.php 24994 2009-12-06 21:02:45Z bschussek $ + * @see LimeMockBehaviour + * @see LimeMockInvocationExpectation + */ +class LimeMock +{ + protected static + $methodTemplate = '%s function %s(%s) { $args = func_get_args(); return $this->__call(\'%s\', $args); }', + $parameterTemplate = '%s %s', + $parameterWithDefaultTemplate = '%s %s = %s', + + $illegalMethods = array( + '__construct', + '__call', + '__lime_replay', + '__lime_getState', + ), + + $controlMethods = array( + 'replay', + 'any', + 'reset', + 'verify', + 'setExpectNothing', + ); + + /** + * Creates a new mock object for the given class or interface name. + * + * The class/interface does not necessarily have to exist. Every generated + * object fulfills the condition ($mock instanceof $class). + * + * @param string $classOrInterface The (non-)existing class/interface + * you want to mock + * @param LimeOutputInterface $output The output for displaying the test results + * @param array $options Generation options. See the class + * description for more information. + * @return LimeMockInterface The mock object + */ + public static function create($classOrInterface, LimeOutputInterface $output, array $options = array()) + { + $options = array_merge(array( + 'strict' => false, + 'generate_controls' => true, + 'stub_methods' => true, + ), $options); + + if ($options['strict']) + { + $behaviour = new LimeMockOrderedBehaviour($options); + } + else + { + $behaviour = new LimeMockUnorderedBehaviour($options); + } + + $name = self::generateClass($classOrInterface, $options['generate_controls']); + + return new $name($classOrInterface, $behaviour, $output, $options['stub_methods']); + } + + /** + * Generates a mock class for the given class/interface name and returns + * the generated class name. + * + * @param string $classOrInterface The mocked class/interface name + * @param boolean $generateControls Whether control methods should be generated. + * @return string The generated class name + */ + protected static function generateClass($classOrInterface, $generateControls = true) + { + $methods = ''; + + if (!class_exists($classOrInterface, false) && !interface_exists($classOrInterface, false)) + { + if (($pos = strpos($classOrInterface, '\\')) !== false) + { + $namespace = substr($classOrInterface, 0, $pos); + $interface = substr($classOrInterface, $pos+1); + + eval(sprintf('namespace %s { interface %s {} }', $namespace, $interface)); + } + else + { + eval(sprintf('interface %s {}', $classOrInterface)); + } + } + + $class = new ReflectionClass($classOrInterface); + foreach ($class->getMethods() as $method) + { + /* @var $method ReflectionMethod */ + if (in_array($method->getName(), self::$controlMethods) && $generateControls) + { + throw new LogicException(sprintf('The mocked class "%s" contains the method "%s", which conflicts with the mock\'s control methods. Please set the option "generate_controls" to false.', $classOrInterface, $method->getName())); + } + + if (!in_array($method->getName(), self::$illegalMethods) && !$method->isFinal()) + { + $modifiers = Reflection::getModifierNames($method->getModifiers()); + $modifiers = array_diff($modifiers, array('abstract')); + $modifiers = implode(' ', $modifiers); + + $parameters = array(); + + foreach ($method->getParameters() as $parameter) + { + $typeHint = ''; + + /* @var $parameter ReflectionParameter */ + if ($parameter->getClass()) + { + $typeHint = $parameter->getClass()->getName(); + } + else if ($parameter->isArray()) + { + $typeHint = 'array'; + } + + $name = '$'.$parameter->getName(); + + if ($parameter->isPassedByReference()) + { + $name = '&'.$name; + } + + if ($parameter->isOptional()) + { + $default = var_export($parameter->getDefaultValue(), true); + $parameters[] = sprintf(self::$parameterWithDefaultTemplate, $typeHint, $name, $default); + } + else + { + $parameters[] = sprintf(self::$parameterTemplate, $typeHint, $name); + } + } + + $methods .= sprintf(self::$methodTemplate, $modifiers, $method->getName(), + implode(', ', $parameters), $method->getName())."\n "; + } + } + + $interfaces = array(); + + $name = self::generateName($class->getName()); + + $declaration = 'class '.$name; + + if ($class->isInterface()) + { + $interfaces[] = $class->getName(); + } + else + { + $declaration .= ' extends '.$class->getName(); + } + + $interfaces[] = 'LimeMockInterface'; + + if (count($interfaces) > 0) + { + $declaration .= ' implements '.implode(', ', $interfaces); + } + + $template = new LimeMockTemplate(dirname(__FILE__).'/template/mocked_class.tpl'); + + eval($template->render(array( + 'class_declaration' => $declaration, + 'methods' => $methods, + 'generate_controls' => $generateControls, + ))); + + return $name; + } + + /** + * Generates a mock class name for the given original class/interface name. + * + * @param string $originalName + * @return string + */ + protected static function generateName($originalName) + { + // strip namespace separators + $originalName = str_replace('\\', '_', $originalName); + + while (!isset($name) || class_exists($name, false)) + { + // inspired by PHPUnit_Framework_MockObject_Generator + $name = 'Mock_'.$originalName.'_'.substr(md5(microtime()), 0, 8); + } + + return $name; + } + + /** + * Turns the given mock into replay mode. + * + * @param LimeMockInterface $mock + */ + public static function replay(LimeMockInterface $mock) + { + return $mock->__lime_replay(); + } + + /** + * Resets the given mock. + * + * All expected invocations are removed, the mock is set to record mode again. + * + * @param LimeMockInterface $mock + */ + public static function reset(LimeMockInterface $mock) + { + return $mock->__lime_reset(); + } + + /** + * Expects the given method on the given mock to be called with any parameters. + * + * The LimeMockInvocationExpectation object is returned and allows you to + * set further modifiers on the method expectation. + * + * @param LimeMockInterface $mock + * @param string $methodName + * @return LimeMockInvocationExpectation + */ + public static function any(LimeMockInterface $mock, $methodName) + { + return $mock->__call($methodName, null); + } + + /** + * Configures the mock to expect no method call. + * + * @param LimeMockInterface $mock + */ + public static function setExpectNothing(LimeMockInterface $mock) + { + return $mock->__lime_getState()->setExpectNothing(); + } + + /** + * Verifies the given mock. + * + * @param LimeMockInterface $mock + */ + public static function verify(LimeMockInterface $mock) + { + return $mock->__lime_getState()->verify(); + } +} + + diff --git a/tests/lib/vendor/lime/mock/LimeMockBehaviour.php b/tests/lib/vendor/lime/mock/LimeMockBehaviour.php new file mode 100644 index 000000000000..279713d4707e --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMockBehaviour.php @@ -0,0 +1,140 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Provides common methods of all implemented behaviours. + * + * Behaviours accept the following options for initialization: + * + * * strict: If set to TRUE, the behaviour initializes all mocked + * methods with the modifier strict() to enable strict + * type comparison. Default: FALSE + * * nice: If set to TRUE, the behaviour will ignore unexpected + * method calls. Mocked methods will be initialized + * with the modifier any(). Default: FALSE + * * no_exceptions: If set to TRUE, throwing of exceptions is + * suppressed when unexpected methods are called. + * The methods will be reported as errors when + * verify() is called. Default: FALSE + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockBehaviour.php 23880 2009-11-14 10:14:34Z bschussek $ + * @see LimeMockBehaviourInterface + */ +abstract class LimeMockBehaviour implements LimeMockBehaviourInterface +{ + protected + $options = array(), + $verified = false, + $invocations = array(), + $expectNothing = false; + + /** + * Constructor. + * + * @param array $options The options for initializing the behaviour. + * @return unknown_type + */ + public function __construct(array $options = array()) + { + $this->options = array_merge(array( + 'strict' => false, + 'nice' => false, + 'no_exceptions' => false, + ), $options); + } + + /** + * (non-PHPdoc) + * @see mock/LimeMockBehaviourInterface#expect($invocation) + */ + public function expect(LimeMockInvocationExpectation $invocation) + { + $this->invocations[] = $invocation; + + if ($this->options['strict']) + { + $invocation->strict(); + } + + if ($this->options['nice']) + { + $invocation->any(); + } + else + { + $invocation->once(); + } + } + + /** + * (non-PHPdoc) + * @see mock/LimeMockBehaviourInterface#invoke($invocation) + */ + public function invoke(LimeMockInvocation $invocation) + { + if (!$this->options['nice'] && !$this->verified && !$this->options['no_exceptions'] && ($this->expectNothing || count($this->invocations) > 0)) + { + throw new LimeMockInvocationException($invocation, 'was not expected to be called'); + } + } + + /** + * (non-PHPdoc) + * @see mock/LimeMockBehaviourInterface#isInvokable($method) + */ + public function isInvokable(LimeMockMethod $method) + { + foreach ($this->invocations as $invocation) + { + if ($invocation->matches($method)) + { + return true; + } + } + + return false; + } + + /** + * (non-PHPdoc) + * @see mock/LimeMockBehaviourInterface#verify() + */ + public function verify() + { + foreach ($this->invocations as $invocation) + { + $invocation->verify(); + } + + $this->verified = true; + } + + /** + * (non-PHPdoc) + * @see mock/LimeMockBehaviourInterface#setExpectNothing() + */ + public function setExpectNothing() + { + $this->expectNothing = true; + } + + /** + * (non-PHPdoc) + * @see mock/LimeMockBehaviourInterface#reset() + */ + public function reset() + { + $this->invocations = array(); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/LimeMockBehaviourInterface.php b/tests/lib/vendor/lime/mock/LimeMockBehaviourInterface.php new file mode 100644 index 000000000000..fb05d2936a7a --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMockBehaviourInterface.php @@ -0,0 +1,84 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * A behaviour specifies how the mock compares expected method invocations with + * actual method invocations. + * + * The behaviour is fed with different invocation expectations by calling the + * method expect(). Later, the method invoke() is called for all actual + * invocations. The behaviour has to decide which expectations to compare + * with incoming actual invocations. One behaviour implementation may, for + * example, decide to accept invoked invocations only if they are called + * in the same order as the invocation expectations. + * + * In the end, verify() can be called on the behaviour to verify whether all + * expectations have been met. + * + * The behaviour should pass the matching of methods and method verification + * to LimeMockInvocationExpectation. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockBehaviourInterface.php 23880 2009-11-14 10:14:34Z bschussek $ + */ +interface LimeMockBehaviourInterface +{ + /** + * Adds the following invocation expectation. + * + * @param LimeMockInvocationExpectation $invocation + */ + public function expect(LimeMockInvocationExpectation $invocation); + + /** + * Invokes the given method invocation. + * + * If the method invocation is not expected, this method should throw a + * LimeMockInvocationException. Otherwise the call should be passed to + * the method invoke() of LimeMockInvocationExpectation, its return value + * should be returned. + * + * @param LimeMockInvocation $invocation The invoked method + * @return mixed The return value of + * LimeMockInvocationExpectation#invoke() + * @throws LimeMockInvocationException If the method should not have been + * invoked + */ + public function invoke(LimeMockInvocation $invocation); + + /** + * Returns whether the given method is invokable. + * + * @param LimeMockMethod $method The method + * @return boolean TRUE if the method is invokable + */ + public function isInvokable(LimeMockMethod $method); + + /** + * Verifies whether all expectations have been fulfilled. + * + * You should call LimeMockInvocationExpectation#verify() to implement this + * method. + */ + public function verify(); + + /** + * Configures the behaviour to expect no method to be invoked. + */ + public function setExpectNothing(); + + /** + * Clears all invocation expectations in the behaviour. + */ + public function reset(); +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/LimeMockException.php b/tests/lib/vendor/lime/mock/LimeMockException.php new file mode 100644 index 000000000000..6d6a8a7c165b --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMockException.php @@ -0,0 +1,26 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * An exception thrown from mock objects. + * + * This exception is generally thrown if you invoke an unexpected method on + * a mock object while in replay mode. Generally this exception should bubble + * up to notify the user about test errors. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockException.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeMockException extends Exception +{ +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/LimeMockInterface.php b/tests/lib/vendor/lime/mock/LimeMockInterface.php new file mode 100644 index 000000000000..f5ee53b24cf0 --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMockInterface.php @@ -0,0 +1,73 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * This interface is implemented by all mock objects. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockInterface.php 23701 2009-11-08 21:23:40Z bschussek $ + * @see LimeMock + */ +interface LimeMockInterface +{ + /** + * Constructor. + * + * @param string $class The name of the mocked class + * @param LimeMockBehaviourInterface $behaviour The behaviour for invocation + * comparisons. + * @param LimeOutputInterface $output The output for displaying + * the comparison results. + */ + public function __construct($class, LimeMockBehaviourInterface $behaviour, LimeOutputInterface $output); + + /** + * Invokes the given method. + * + * The mock reacts accordingly depending on whether it is in record or in + * replay mode. In record mode, mocks return an instance of + * LimeMockInvocationExpectation, which you can use to further configure + * the expected invocation. In replay mode, the configured return value + * is returned. If you configured the method to throw an exception, this + * exception will be thrown here. + * + * @param string $method The method + * @param array $parameters The method parameters + * @return LimeMockInvocationExpectation|mixed + * @throws LimeMockException If the method should not have been invoked + * @throws Exception If you configured the mock to throw an exception + */ + public function __call($method, $parameters); + + /** + * Switches the mock object into replay mode. + * + * @throws BadMethodCallException If the object already is in replay mode + */ + public function __lime_replay(); + + /** + * Resets all expected invocations in the mock and switches it into record + * mode. + * + * @see LimeMockBehaviourInterface#reset() + */ + public function __lime_reset(); + + /** + * Returns the object representing the current state of the mock. + * + * @return LimeMockStateInterface + */ + public function __lime_getState(); +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/LimeMockInvocation.php b/tests/lib/vendor/lime/mock/LimeMockInvocation.php new file mode 100644 index 000000000000..9c58d5ba3c35 --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMockInvocation.php @@ -0,0 +1,134 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Represents the invocation of a class or object method with a set of + * parameters. + * + * This class is used internally by LimeMockControl to track the method + * invocations on mock objects. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockInvocation.php 23880 2009-11-14 10:14:34Z bschussek $ + */ +class LimeMockInvocation implements LimeMockMethodInterface +{ + protected + $method = null, + $parameters = array(); + + /** + * Constructor. + * + * @param string $method The method name + * @param array $parameters The method parameters + */ + public function __construct(LimeMockMethod $method, array $parameters = array()) + { + $this->method = $method; + $this->parameters = $parameters; + } + + /** + * Returns the class name. + * + * @return string + */ + public function getClass() + { + return $this->method->getClass(); + } + + /** + * Returns the method name. + * + * @return string + */ + public function getMethod() + { + return $this->method->getMethod(); + } + + /** + * Returns the method parameters. + * + * @return array The parameter array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Returns the parameter at the given index. + * + * @param integer $index + * @return mixed + */ + public function getParameter($index) + { + if ($index >= count($this->parameters)) + { + throw new OutOfRangeException(sprintf('The parameter %s does not exist', $index)); + } + + return $this->parameters[$index]; + } + + /** + * Returns a string representation of the method call invocation. + * + * The result looks like a method call in PHP source code. + * + * Example: + * + * $invocation = new LimeMockMethodInvocation('doSomething', array(1, 'foobar')); + * print $invocation; + * + * // => "doSomething(1, 'foobar')" + * + * + * @return string + */ + public function __toString() + { + $parameters = $this->parameters; + + if (is_array($parameters)) + { + foreach ($parameters as $key => $value) + { + if (is_string($value)) + { + $value = str_replace(array("\0", "\n", "\t", "\r"), array('\0', '\n', '\t', '\r'), $value); + $value = strlen($value) > 30 ? substr($value, 0, 30).'...' : $value; + $parameters[$key] = '"'.$value.'"'; + } + else if (is_object($value)) + { + $parameters[$key] = get_class($value); + } + else if (is_array($value)) + { + $parameters[$key] = 'array'; + } + else + { + $parameters[$key] = var_export($value, true); + } + } + } + + return sprintf('%s(%s)', $this->method->getMethod(), implode(', ', (array)$parameters)); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/LimeMockInvocationException.php b/tests/lib/vendor/lime/mock/LimeMockInvocationException.php new file mode 100644 index 000000000000..7ab03061a0db --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMockInvocationException.php @@ -0,0 +1,38 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Thrown when a method invocation should not have been made. + * + * This exception is usually wrapped inside a LimeMockInvocation and should not + * bubble up. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockInvocationException.php 23864 2009-11-13 18:06:20Z bschussek $ + */ +class LimeMockInvocationException extends Exception +{ + /** + * Constructor. + * + * @param LimeMockInvocation $invocation The erroneous method invocation + * @param string $message The message describing why the + * invocation should not have been + * made. The message is appended + * at the method name. + */ + public function __construct(LimeMockInvocation $invocation, $message) + { + parent::__construct($invocation.' '.$message); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/LimeMockInvocationExceptionStack.php b/tests/lib/vendor/lime/mock/LimeMockInvocationExceptionStack.php new file mode 100644 index 000000000000..1d488a69f106 --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMockInvocationExceptionStack.php @@ -0,0 +1,67 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Collects a number of LimeMockInvocationException objects. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockInvocationExceptionStack.php 24352 2009-11-24 19:49:42Z bschussek $ + */ +class LimeMockInvocationExceptionStack extends LimeMockInvocationException +{ + protected + $exceptions = array(); + + /** + * Ignores the parent constructor. + */ + public function __construct() {} + + /** + * Adds a new exception to the stack. + * + * The stack message is updated to contain the message of the exception. + * + * @param LimeMockInvocationException $exception + */ + public function add(LimeMockInvocationException $exception) + { + $this->exceptions[] = $exception; + + if (count($this->exceptions) > 1) + { + $this->message = "One of the following errors occured:\n"; + + for ($i = 1; $i <= count($this->exceptions); ++$i) + { + $message = LimeTools::indent(wordwrap($this->exceptions[$i-1]->getMessage(), 70), strlen($i)+2); + + $this->message .= sprintf("%s) %s\n", $i, trim($message)); + } + } + else + { + $this->message = $this->exceptions[0]->getMessage(); + } + } + + /** + * Returns TRUE when the stack contains no exceptions, FALSE otherwise. + * + * @return boolean + */ + public function isEmpty() + { + return count($this->exceptions) == 0; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/LimeMockInvocationExpectation.php b/tests/lib/vendor/lime/mock/LimeMockInvocationExpectation.php new file mode 100644 index 000000000000..9e7546e1c479 --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMockInvocationExpectation.php @@ -0,0 +1,462 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Represents an expected method invocation. + * + * Instances of this class are returned when you record a new method call on + * a mock object while in record mode. You can then use this instance specify + * further modifiers, like how often the method is expected to be called, + * whether it is expected to be called with the same parameter types etc. + * + * The modifiers of this class support method chaining. + * + * + * $mock = LimeMock::create('MyClass', $output); + * $mock->doSomething(); + * // returns LimeMockInvocationExpectation + * + * // let's use the returned object to configure the invocation + * $mock->doSomething()->atLeastOnce()->returns('some value'); + * + * + * You must inform this object of an invoked method by calling invoke(). When + * that is done, you can use verify() to find out whether all the modifiers + * succeeded, i.e. whether the method was called a sufficient number of times + * etc. The results of the verification are then written to the output. + * + * Note: This class is implemented to verify a method automatically upon + * invoking. If all the method modifiers are satisfied, the success message + * is immediately printed to the output, even if you don't call verify(). If + * you want to suppress all output, you should pass an instance of LimeOutputNone + * to this class. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockInvocationExpectation.php 23880 2009-11-14 10:14:34Z bschussek $ + */ +class LimeMockInvocationExpectation +{ + protected + $invocation = null, + $matched = false, + $output = null, + $countMatcher = null, + $parameterMatchers = array(), + $parameters = array(), + $withAnyParameters = false, + $returns = false, + $returnValue = null, + $exception = null, + $callback = null, + $strict = false, + $verified = false; + + /** + * Constructor. + * + * @param LimeMockInvocation $invocation The expected method invocation + * @param LimeOutputInterface $output The output to write at when + * verification passes or fails + */ + public function __construct(LimeMockInvocation $invocation, LimeOutputInterface $output) + { + $this->invocation = $invocation; + $this->output = $output; + } + + protected function getMatchers() + { + return array_merge($this->parameterMatchers, ($this->countMatcher ? array($this->countMatcher) : array())); + } + + /** + * Returns the string representation. + * + * The string representation consists of the method name and the messages of + * all applied modifiers. + * + * Example: + * + * "doSomething() was called at least once" + * + * @return string + */ + public function __toString() + { + $string = $this->invocation.' was called'; + + foreach ($this->getMatchers() as $matcher) + { + // avoid trailing spaces if the message is empty + $string = rtrim($string.' '.$matcher->getMessage()); + } + + return $string; + } + + /** + * Notifies this object of the given method invocation. + * + * If any of the matchers decides that this method should not have been + * invoked, an exception is thrown. If all matchers are satisfied, a success + * message is printed to the output. + * + * If this object was configured to throw an exception, this exception is + * thrown now. Otherwise the method's configured return value is returned. + * + * @param LimeMockInvocation $invocation The invoked method + * @return mixed The configured return value + * See returns() + * @throws LimeMockInvocationException If the method should not have been + * invoked + * @throws Exception If this object was configured to + * throw an exception + * See throw() + */ + public function invoke(LimeMockInvocation $invocation) + { + try + { + foreach ($this->getMatchers() as $matcher) + { + $matcher->invoke($invocation); + } + } + catch (LimeMockInvocationMatcherException $e) + { + throw new LimeMockInvocationException($this->invocation, $e->getMessage()); + } + + if (!$this->verified && $this->isSatisfied()) + { + list ($file, $line) = LimeTrace::findCaller('LimeMockInterface'); + + $this->output->pass((string)$this, $file, $line); + + $this->verified = true; + } + + if (!is_null($this->callback)) + { + $result = call_user_func_array($this->callback, $invocation->getParameters()); + + return $this->returns ? $this->returnValue : $result; + } + + if (!is_null($this->exception)) + { + if (is_string($this->exception)) + { + throw new $this->exception(); + } + else + { + throw $this->exception; + } + } + + return $this->returnValue; + } + + /** + * Returns whether the method signature and parameters of this object match + * the given invocation. + * + * @param LimeMockInvocation $invocation + * @return boolean + */ + public function matches(LimeMockMethodInterface $method) + { + if ($this->invocation->getClass() != $method->getClass() || $this->invocation->getMethod() != $method->getMethod()) + { + return false; + } + else if ($method instanceof LimeMockInvocation && !$this->withAnyParameters) + { + $index = 0; + + foreach ($this->parameterMatchers as $matcher) + { + $index = max($index, $matcher->getIndex()); + } + + return count($method->getParameters()) == $index; + } + else + { + return true; + } + } + + /** + * Returns whether this object may be invoked. + * + * This method returns FALSE if the next call to invoke() would throw a + * LimeMockInvocationException. + * + * @return boolean + */ + public function isInvokable() + { + $result = true; + + foreach ($this->getMatchers() as $matcher) + { + $result = $result && $matcher->isInvokable(); + } + + return $result; + } + + /** + * Returns whether the requirements of all configured modifiers have been + * fulfilled. + * + * @return boolean + */ + public function isSatisfied() + { + $result = true; + + foreach ($this->getMatchers() as $matcher) + { + $result = $result && $matcher->isSatisfied(); + } + + return $result; + } + + /** + * Verifies whether the requirements of all configured modifiers have been + * fulfilled. + * + * Depending on the result, either a failed or a passed test is written to the + * output. A method may only be verified once. + * + * Note: Methods are verified automatically once invoke() is called and + * all matchers are satisfied. In this case verify() simply does nothing. + */ + public function verify() + { + if (!$this->verified) + { + list ($file, $line) = LimeTrace::findCaller('LimeMockInterface'); + + if ($this->isSatisfied()) + { + $this->output->pass((string)$this, $file, $line); + } + else + { + $this->output->fail((string)$this, $file, $line); + } + + $this->verified = true; + } + } + + /** + * This method is expected to be called the given number of times. + * + * @param integer $times + * @return LimeMockInvocationExpectation This object + */ + public function times($times) + { + $this->countMatcher = new LimeMockInvocationMatcherTimes($times); + + return $this; + } + + /** + * This method is expected to be called exactly once. + * + * @return LimeMockInvocationExpectation This object + */ + public function once() + { + return $this->times(1); + } + + /** + * This method is expected to be called never. + * + * @return LimeMockInvocationExpectation This object + */ + public function never() + { + return $this->times(0); + } + + /** + * This method is expected to be called zero times or more. + * + * @return LimeMockInvocationExpectation This object + */ + public function any() + { + $this->countMatcher = new LimeMockInvocationMatcherAny(); + + return $this; + } + + /** + * This method is expected to be called once or more. + * + * @return LimeMockInvocationExpectation This object + */ + public function atLeastOnce() + { + $this->countMatcher = new LimeMockInvocationMatcherAtLeastOnce(); + + return $this; + } + + /** + * This method is expected to be called any time within the given limits. + * + * The limits are inclusive. If the method is called exactly $start times, + * the requirements of this modifier are fulfilled. + * + * @param integer $start + * @param integer $end + * @return LimeMockInvocationExpectation This object + */ + public function between($start, $end) + { + $this->countMatcher = new LimeMockInvocationMatcherBetween($start, $end); + + return $this; + } + + /** + * This method will return the given value when invoked. + * + * @param mixed $value + * @return LimeMockInvocationExpectation This object + */ + public function returns($value) + { + $this->returns = true; + $this->returnValue = $value; + + return $this; + } + + /** + * This method will throw the given exception when invoked. + * + * @param string|Exception $class + * @return LimeMockInvocationExpectation This object + */ + public function throws($class) + { + $this->exception = $class; + + return $this; + } + + /** + * This method will call the given callback and return its return value when + * invoked. + * + * @param callable $callback + * @return LimeMockInvocationExpectation This object + */ + public function callback($callback) + { + if (!is_callable($callback)) + { + throw new InvalidArgumentException('The given argument is no callable'); + } + + $this->callback = $callback; + + return $this; + } + + /** + * This method must be called with the exact same parameter types. + * + * @return LimeMockInvocationExpectation This object + */ + public function strict() + { + $this->strict = true; + + if (!$this->withAnyParameters) + { + // reload matchers + $this->withParameters($this->parameters); + } + + return $this; + } + + /** + * Configures a parameter to match some constraint. + * + * The constraint can be configured on the returned matcher object. + * + * @param integer $index The index of the parameter. The first parameter has + * index 1. + * @return LimeMockInvocationMatcherParameter + */ + public function parameter($index) + { + $this->parameterMatchers[$index] = $matcher = new LimeMockInvocationMatcherParameter($index, $this); + + return $matcher; + } + + /** + * This method can be called with any parameters. + * + * @return LimeMockInvocationExpectation This object + */ + public function withAnyParameters() + { + $this->parameterMatchers = array(); + $this->withAnyParameters = true; + + return $this; + } + + /** + * This method must be called with the given parameters. + * + * @param array $parameters + * @param $strict + * @return unknown_type + */ + public function withParameters(array $parameters) + { + $this->parameters = $parameters; + $this->parameterMatchers = array(); + $this->withAnyParameters = false; + + foreach ($parameters as $index => $value) + { + if ($this->strict) + { + $this->parameter($index+1)->same($value); + } + else + { + $this->parameter($index+1)->is($value); + } + } + + return $this; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/LimeMockMethod.php b/tests/lib/vendor/lime/mock/LimeMockMethod.php new file mode 100644 index 000000000000..05940c81312b --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMockMethod.php @@ -0,0 +1,57 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Represents a method on a class. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockMethod.php 23880 2009-11-14 10:14:34Z bschussek $ + */ +class LimeMockMethod implements LimeMockMethodInterface +{ + protected + $class = null, + $method = null; + + /** + * Constructor. + * + * @param string $class The class name + * @param string $method The method name + */ + public function __construct($class, $method) + { + $this->class = $class; + $this->method = $method; + } + + /** + * Returns the class name. + * + * @return string + */ + public function getClass() + { + return $this->class; + } + + /** + * Returns the method name. + * + * @return string + */ + public function getMethod() + { + return $this->method; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/LimeMockMethodInterface.php b/tests/lib/vendor/lime/mock/LimeMockMethodInterface.php new file mode 100644 index 000000000000..9abd871b37c3 --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMockMethodInterface.php @@ -0,0 +1,8 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * A behaviour that requires methods to be invoked in the same order as they + * were expected. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockOrderedBehaviour.php 23701 2009-11-08 21:23:40Z bschussek $ + * @see LimeMockBehaviourInterface + */ +class LimeMockOrderedBehaviour extends LimeMockBehaviour +{ + protected + $cursor = 0; + + /** + * (non-PHPdoc) + * @see mock/LimeMockBehaviour#invoke($invocation) + */ + public function invoke(LimeMockInvocation $invocation) + { + if (array_key_exists($this->cursor, $this->invocations)) + { + $invocationExpectation = $this->invocations[$this->cursor]; + + if ($invocationExpectation->matches($invocation) && $invocationExpectation->isInvokable()) + { + return $invocationExpectation->invoke($invocation); + } + else if ($invocationExpectation->isSatisfied()) + { + $this->cursor++; + + return $this->invoke($invocation); + } + } + + parent::invoke($invocation); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/LimeMockRecordState.php b/tests/lib/vendor/lime/mock/LimeMockRecordState.php new file mode 100644 index 000000000000..77f3b821f18d --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMockRecordState.php @@ -0,0 +1,99 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * The state of the mock during record mode. + * + * During record mode, all methods that are called on the mock are turned into + * invocation expectations. You may set modifiers on these expectations to + * configure whether invocations should return values, throw exceptions etc. + * in replay mode. See the description of LimeMockInvocationExpectation for + * more information. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockRecordState.php 23880 2009-11-14 10:14:34Z bschussek $ + * @see LimeMockInvocationExpectation + */ +class LimeMockRecordState implements LimeMockStateInterface +{ + protected + $behaviour = null, + $output = null; + + /** + * Constructor. + * + * @param LimeMockBehaviourInterface $behaviour The behaviour on which this + * state operates + * @param LimeOutputInterface $output The output where failed and + * successful tests are written + * to. + */ + public function __construct(LimeMockBehaviourInterface $behaviour, LimeOutputInterface $output) + { + $this->behaviour = $behaviour; + $this->output = $output; + } + + /** + * (non-PHPdoc) + * @see mock/LimeMockStateInterface#invoke($method, $parameters) + */ + public function invoke(LimeMockMethod $method, array $parameters = null) + { + $invocation = new LimeMockInvocation($method, is_null($parameters) ? array() : $parameters); + $invocation = new LimeMockInvocationExpectation($invocation, $this->output); + + if (is_null($parameters)) + { + $invocation->withAnyParameters(); + } + else + { + $invocation->withParameters($parameters); + } + + $this->behaviour->expect($invocation); + + return $invocation; + } + + /** + * All method can be invoked during record mode. + * + * (non-PHPdoc) + * @see mock/LimeMockStateInterface#isInvokable($method) + */ + public function isInvokable(LimeMockMethod $method) + { + return true; + } + + /** + * (non-PHPdoc) + * @see mock/LimeMockStateInterface#setExpectNothing() + */ + public function setExpectNothing() + { + return $this->behaviour->setExpectNothing(); + } + + /** + * (non-PHPdoc) + * @see mock/LimeMockStateInterface#verify() + */ + public function verify() + { + throw new BadMethodCallException('replay() must be called before verify()'); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/LimeMockReplayState.php b/tests/lib/vendor/lime/mock/LimeMockReplayState.php new file mode 100644 index 000000000000..072c3d36e2c3 --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMockReplayState.php @@ -0,0 +1,75 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * The state of the mock during replay mode. + * + * In this state, invoked methods are verified automatically. If a method + * was expected to be called, the configured return value of the method is + * returned. If it was not expected, an exception is thrown instead. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockReplayState.php 23880 2009-11-14 10:14:34Z bschussek $ + */ +class LimeMockReplayState implements LimeMockStateInterface +{ + protected + $behaviour = null; + + /** + * Constructor. + * + * @param LimeMockBehaviourInterface $behaviour The behaviour on which this + * state operates. + */ + public function __construct(LimeMockBehaviourInterface $behaviour) + { + $this->behaviour = $behaviour; + } + + /** + * (non-PHPdoc) + * @see mock/LimeMockStateInterface#invoke($class, $method, $parameters) + */ + public function invoke(LimeMockMethod $method, array $parameters = null) + { + return $this->behaviour->invoke(new LimeMockInvocation($method, is_null($parameters) ? array() : $parameters)); + } + + /** + * (non-PHPdoc) + * @see mock/LimeMockStateInterface#isInvokable($method) + */ + public function isInvokable(LimeMockMethod $method) + { + return $this->behaviour->isInvokable($method); + } + + /** + * (non-PHPdoc) + * @see mock/LimeMockStateInterface#setExpectNothing() + */ + public function setExpectNothing() + { + throw new BadMethodCallException('setExpectNothing() must be called before replay()'); + } + + /** + * (non-PHPdoc) + * @see mock/LimeMockStateInterface#verify() + */ + public function verify() + { + return $this->behaviour->verify(); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/LimeMockStateInterface.php b/tests/lib/vendor/lime/mock/LimeMockStateInterface.php new file mode 100644 index 000000000000..e979d9164664 --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMockStateInterface.php @@ -0,0 +1,56 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Represents the current state of the mock. + * + * A mock can have different states during his lifetime. The functionality + * in these different states is implemented using the State Pattern. Each + * state should extend this interface. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockStateInterface.php 23880 2009-11-14 10:14:34Z bschussek $ + */ +interface LimeMockStateInterface +{ + /** + * Handles an invoked method on the mock. + * + * Depending on the state of the mock, invoked methods may be treated + * differently. + * + * @param LimeMockInvocation $invocation + * @return mixed + * @throws LimeMockInvocationException + * @throws Exception + */ + public function invoke(LimeMockMethod $method, array $parameters = null); + + /** + * Returns whether the given method is invokable. + * + * @param LimeMockMethod $method The method + * @return boolean TRUE if the method is invokable + */ + public function isInvokable(LimeMockMethod $method); + + /** + * Tells the state that the mock should not receive any method invocation. + */ + public function setExpectNothing(); + + /** + * Verifies the mock in the current state. + */ + public function verify(); +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/LimeMockTemplate.php b/tests/lib/vendor/lime/mock/LimeMockTemplate.php new file mode 100644 index 000000000000..0b9cc9cf09a7 --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMockTemplate.php @@ -0,0 +1,57 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Reads template files and parses them with a set of template variables. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockTemplate.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeMockTemplate +{ + private + $parameters = array(), + $filename = ''; + + /** + * Constructor. + * + * Configures this template to use the given file. + * + * @param string $filename + */ + public function __construct($filename) + { + $this->filename = $filename; + } + + /** + * Renders the file of this template with the given parameters. + * + * The parameters are made available in the template and can be accessed there + * as normal PHP variables. The template is parsed and the output of the + * template is returned. + * + * @param array $parameters + * @return string + */ + public function render(array $parameters) + { + ob_start(); + extract($parameters); + include $this->filename; + + return ob_get_clean(); + } + +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/LimeMockUnorderedBehaviour.php b/tests/lib/vendor/lime/mock/LimeMockUnorderedBehaviour.php new file mode 100644 index 000000000000..37a6d77bc820 --- /dev/null +++ b/tests/lib/vendor/lime/mock/LimeMockUnorderedBehaviour.php @@ -0,0 +1,45 @@ + + * @version SVN: $Id: LimeMockUnorderedBehaviour.php 23864 2009-11-13 18:06:20Z bschussek $ + * @see LimeMockBehaviourInterface + */ +class LimeMockUnorderedBehaviour extends LimeMockBehaviour +{ + /** + * (non-PHPdoc) + * @see mock/LimeMockBehaviour#invoke($invocation) + */ + public function invoke(LimeMockInvocation $invocation) + { + $exceptionStack = new LimeMockInvocationExceptionStack(); + + foreach ($this->invocations as $invocationExpectation) + { + try + { + if ($invocationExpectation->matches($invocation)) + { + return $invocationExpectation->invoke($invocation); + } + } + catch (LimeMockInvocationException $e) + { + // make sure to test all expectations + $exceptionStack->add($e); + } + } + + // no invocation matched and at least one exception was thrown + if (!$exceptionStack->isEmpty()) + { + throw $exceptionStack; + } + + parent::invoke($invocation); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherAny.php b/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherAny.php new file mode 100644 index 000000000000..a3780e80fbfc --- /dev/null +++ b/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherAny.php @@ -0,0 +1,59 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Requires a method call to be invoked any time. + * + * This matcher always passes. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockInvocationMatcherAny.php 23701 2009-11-08 21:23:40Z bschussek $ + * @see LimeMockInvocationMatcherInterface + */ +class LimeMockInvocationMatcherAny implements LimeMockInvocationMatcherInterface +{ + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#invoke($invocation) + */ + public function invoke(LimeMockInvocation $invocation) + { + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#isInvokable() + */ + public function isInvokable() + { + return true; + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#isSatisfied() + */ + public function isSatisfied() + { + return true; + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#getMessage() + */ + public function getMessage() + { + return ''; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherAtLeastOnce.php b/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherAtLeastOnce.php new file mode 100644 index 000000000000..1e3e92a31ab9 --- /dev/null +++ b/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherAtLeastOnce.php @@ -0,0 +1,63 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Requires a method call to be invoked once or more. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockInvocationMatcherAtLeastOnce.php 23701 2009-11-08 21:23:40Z bschussek $ + * @see LimeMockInvocationMatcherInterface + */ +class LimeMockInvocationMatcherAtLeastOnce implements LimeMockInvocationMatcherInterface +{ + private + $actual = 0; + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#invoke($invocation) + */ + public function invoke(LimeMockInvocation $invocation) + { + $this->actual++; + + return true; + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#isInvokable() + */ + public function isInvokable() + { + return true; + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#isSatisfied() + */ + public function isSatisfied() + { + return $this->actual >= 1; + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#getMessage() + */ + public function getMessage() + { + return 'at least once'; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherBetween.php b/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherBetween.php new file mode 100644 index 000000000000..9de1e5044954 --- /dev/null +++ b/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherBetween.php @@ -0,0 +1,94 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Requires a method to be called between X and Y times. + * + * The parameters X and Y are passed to the constructor. These values are + * inclusive, that means that the matcher passes if the method is called + * exactly X or Y times. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockInvocationMatcherBetween.php 23701 2009-11-08 21:23:40Z bschussek $ + * @see LimeMockInvocationMatcherInterface + */ +class LimeMockInvocationMatcherBetween implements LimeMockInvocationMatcherInterface +{ + private + $start = 0, + $end = 0, + $actual = 0; + + /** + * Constructor. + * + * @param integer $start The lower limit of accepted invokation counts + * @param integer $end The upper limit of accepted invokation counts + */ + public function __construct($start, $end) + { + if ($start > $end) + { + $this->start = $end; + $this->end = $start; + } + else + { + $this->start = $start; + $this->end = $end; + } + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#invoke($invocation) + */ + public function invoke(LimeMockInvocation $invocation) + { + if ($this->actual < $this->end) + { + $this->actual++; + } + else + { + throw new LimeMockInvocationMatcherException(sprintf('should only be called %s', $this->getMessage())); + } + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#isInvokable() + */ + public function isInvokable() + { + return $this->actual < $this->end; + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#isSatisfied() + */ + public function isSatisfied() + { + return $this->actual >= $this->start && $this->actual <= $this->end; + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#getMessage() + */ + public function getMessage() + { + return sprintf('between %s and % times', $this->start, $this->end); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherException.php b/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherException.php new file mode 100644 index 000000000000..bc7c40cb119a --- /dev/null +++ b/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherException.php @@ -0,0 +1,27 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Exception thrown by all invokation matchers. + * + * This exception is thrown by the method invoke() of all invokation matchers + * implementing LimeMockInvocationMatcherInterface if the invokation is not + * accepted. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockInvocationMatcherException.php 23701 2009-11-08 21:23:40Z bschussek $ + * @see LimeMockInvocationMatcherInterface + */ +class LimeMockInvocationMatcherException extends Exception +{ +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherInterface.php b/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherInterface.php new file mode 100644 index 000000000000..e83361862f93 --- /dev/null +++ b/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherInterface.php @@ -0,0 +1,77 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Verifies whether some criteria has been fulfilled for a method invocation. + * + * You can configure a number of matchers for a method invocation in class + * LimeExpectedInvocation. If a method is invoked and the method + * signature and parameters match the expected invocation, the method call + * is passed to the invoke() method of all associated matchers. This method + * decides whether the invocation is valid and throws a + * LimeMockInvocationMatcherException if it is not. + * + * When the mock object is verified, the method isSatisfied() is queried on all + * matchers of every method. If all matchers are satisfied, the invocation + * expectation is considered to be fulfilled. + * + * The method isInvokable() returns whether the matcher accepts any more + * invocations. For example, if a matcher only accepts 3 invocation and throws + * exceptions after that, isInvokable() should return false as soon as these + * three invocations have been made. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockInvocationMatcherInterface.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +interface LimeMockInvocationMatcherInterface +{ + /** + * Notifies the matcher of a given method invocation. + * + * If the matcher decides that the method should not have been invoked, it + * must throw an exception in this method. + * + * @param LimeMockInvocation $invocation The method invocation + * @throws LimeMockInvocationMatcherException If the invocation was not valid + */ + public function invoke(LimeMockInvocation $invocation); + + /** + * Returns whether the matcher accepts any more invokations. + * + * @return boolean + */ + public function isInvokable(); + + /** + * Returns whether the matcher's criteria is fulfilled. + * + * The matcher's criteria could be, for instance, that a method must be + * invoked at least 3 times. As soon as this is the case, isSatisfied() + * returns TRUE. + * + * @return boolean + */ + public function isSatisfied(); + + /** + * The message describing the purpose of the matcher that is appended to + * the method name in the test output. + * + * If this message returns "with any parameters", the resulting output is + * "doSomething(x) was called with any parameters". + * + * @return string + */ + public function getMessage(); +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherParameter.php b/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherParameter.php new file mode 100644 index 000000000000..9df4dd384321 --- /dev/null +++ b/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherParameter.php @@ -0,0 +1,288 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Requires a method call to satisfy a constraint for one parameter. + * + * The index of the parameter is given to the constructor. The desired + * constraint can be configured by calling any of the methods of this class. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockInvocationMatcherParameter.php 23864 2009-11-13 18:06:20Z bschussek $ + * @see LimeMockInvocationMatcherInterface + */ +class LimeMockInvocationMatcherParameter implements LimeMockInvocationMatcherInterface +{ + private + $index = null, + $parent = null, + $constraint = null; + + /** + * Constructor. + * + * @param integer $index + * @param LimeMockInvocationExpectation $parent + */ + public function __construct($index, LimeMockInvocationExpectation $parent) + { + $this->index = $index; + $this->parent = $parent; + } + + /** + * Returns the parameter index of this matcher. + * + * @return integer + */ + public function getIndex() + { + return $this->index; + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#invoke($invocation) + */ + public function invoke(LimeMockInvocation $invocation) + { + try + { + if (!is_null($this->constraint)) + { + $this->constraint->evaluate($invocation->getParameter($this->index-1)); + } + } + catch (LimeConstraintException $e) + { + $message = LimeTools::indent($e->getMessage(), 2); + + throw new LimeMockInvocationMatcherException("was called with wrong parameter $this->index\n".$message); + } + catch (OutOfRangeException $e) + { + throw new LimeMockInvocationMatcherException("was not called with $this->index or more parameters"); + } + } + + /** + * Returns whether this matcher matches the given invocation. + * + * @param LimeMockInvocation $invocation + * @return boolean + */ + public function matches(LimeMockInvocation $invocation) + { + try + { + if (!is_null($this->constraint)) + { + $this->constraint->evaluate($invocation->getParameter($this->index-1)); + } + } + catch (LimeConstraintException $e) + { + return false; + } + catch (OutOfRangeException $e) + { + return false; + } + + return true; + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#isInvokable() + */ + public function isInvokable() + { + return true; + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#isSatisfied() + */ + public function isSatisfied() + { + return true; + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#getMessage() + */ + public function getMessage() + { + return ''; + } + + /** + * Sets the constraint and returns the related invocation expectation object. + * + * @param LimeConstraintInterface $constraint + * @return LimeMockInvocationExpectation + */ + private function setConstraint(LimeConstraintInterface $constraint) + { + $this->constraint = $constraint; + + return $this->parent; + } + + /** + * Requires the parameter to be equal to the given value. + * + * @param LimeConstraintInterface $constraint + * @return LimeMockInvocationExpectation + * @see LimeConstraintIs + */ + public function is($expected) + { + return $this->setConstraint(new LimeConstraintIs($expected)); + } + + /** + * Requires the parameter to be not equal to the given value. + * + * @param LimeConstraintInterface $constraint + * @return LimeMockInvocationExpectation + * @see LimeConstraintIsNot + */ + public function isnt($expected) + { + return $this->setConstraint(new LimeConstraintIsNot($expected)); + } + + /** + * Requires the parameter to be identical to the given value. + * + * @param LimeConstraintInterface $constraint + * @return LimeMockInvocationExpectation + * @see LimeConstraintSame + */ + public function same($expected) + { + return $this->setConstraint(new LimeConstraintSame($expected)); + } + + /** + * Requires the parameter to be not identical to the given value. + * + * @param LimeConstraintInterface $constraint + * @return LimeMockInvocationExpectation + * @see LimeConstraintNotSame + */ + public function isntSame($expected) + { + return $this->setConstraint(new LimeConstraintNotSame($expected)); + } + + /** + * Requires the parameter to be like the given value. + * + * @param LimeConstraintInterface $constraint + * @return LimeMockInvocationExpectation + * @see LimeConstraintLike + */ + public function like($expected) + { + return $this->setConstraint(new LimeConstraintLike($expected)); + } + + /** + * Requires the parameter to be unlike the given value. + * + * @param LimeConstraintInterface $constraint + * @return LimeMockInvocationExpectation + * @see LimeConstraintUnlike + */ + public function unlike($expected) + { + return $this->setConstraint(new LimeConstraintUnlike($expected)); + } + + /** + * Requires the parameter to contain the given value. + * + * @param LimeConstraintInterface $constraint + * @return LimeMockInvocationExpectation + * @see LimeConstraintContains + */ + public function contains($expected) + { + return $this->setConstraint(new LimeConstraintContains($expected)); + } + + /** + * Requires the parameter to not contain the given value. + * + * @param LimeConstraintInterface $constraint + * @return LimeMockInvocationExpectation + * @see LimeConstraintContainsNot + */ + public function containsNot($expected) + { + return $this->setConstraint(new LimeConstraintContainsNot($expected)); + } + + /** + * Requires the parameter to be greater than the given value. + * + * @param LimeConstraintInterface $constraint + * @return LimeMockInvocationExpectation + * @see LimeConstraintGreaterThan + */ + public function greaterThan($expected) + { + return $this->setConstraint(new LimeConstraintGreaterThan($expected)); + } + + /** + * Requires the parameter to be greater than or equal to the given value. + * + * @param LimeConstraintInterface $constraint + * @return LimeMockInvocationExpectation + * @see LimeConstraintGreaterThanEqual + */ + public function greaterThanEqual($expected) + { + return $this->setConstraint(new LimeConstraintGreaterThanEqual($expected)); + } + + /** + * Requires the parameter to be less than the given value. + * + * @param LimeConstraintInterface $constraint + * @return LimeMockInvocationExpectation + * @see LimeConstraintLessThan + */ + public function lessThan($expected) + { + return $this->setConstraint(new LimeConstraintLessThan($expected)); + } + + /** + * Requires the parameter to be less than or equal to the given value. + * + * @param LimeConstraintInterface $constraint + * @return LimeMockInvocationExpectation + * @see LimeConstraintLessThanEqual + */ + public function lessThanEqual($expected) + { + return $this->setConstraint(new LimeConstraintLessThanEqual($expected)); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherTimes.php b/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherTimes.php new file mode 100644 index 000000000000..772683c1437d --- /dev/null +++ b/tests/lib/vendor/lime/mock/matcher/LimeMockInvocationMatcherTimes.php @@ -0,0 +1,90 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Requires a method to be invoked a specific number of times. + * + * The expected number of method invokations must be passed to the constructor. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeMockInvocationMatcherTimes.php 23701 2009-11-08 21:23:40Z bschussek $ + * @see LimeMockInvocationMatcherInterface + */ +class LimeMockInvocationMatcherTimes implements LimeMockInvocationMatcherInterface +{ + private + $expected = 0, + $actual = 0; + + /** + * Constructor. + * + * @param integer $times The expected number of method invokations0 + */ + public function __construct($times) + { + $this->expected = $times; + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#invoke($invocation) + */ + public function invoke(LimeMockInvocation $invocation) + { + if ($this->actual < $this->expected) + { + $this->actual++; + } + else + { + if ($this->expected == 0) + { + throw new LimeMockInvocationMatcherException('should not be called'); + } + else + { + $times = $this->getMessage(); + + throw new LimeMockInvocationMatcherException(sprintf('should only be called %s', $times)); + } + } + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#isInvokable() + */ + public function isInvokable() + { + return $this->actual < $this->expected; + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#isSatisfied() + */ + public function isSatisfied() + { + return $this->actual >= $this->expected; + } + + /** + * (non-PHPdoc) + * @see mock/matcher/LimeMockInvocationMatcherInterface#getMessage() + */ + public function getMessage() + { + return $this->expected == 1 ? 'once' : $this->expected.' times'; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/mock/template/mocked_class.tpl b/tests/lib/vendor/lime/mock/template/mocked_class.tpl new file mode 100644 index 000000000000..25bf0727c48e --- /dev/null +++ b/tests/lib/vendor/lime/mock/template/mocked_class.tpl @@ -0,0 +1,93 @@ +/* + * This file is part of the Lime framework. + * + * (c) Fabien Potencier + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + + +{ + private + $class = null, + $state = null, + $output = null, + $behaviour = null, + $stubMethods = true; + + public function __construct($class, LimeMockBehaviourInterface $behaviour, LimeOutputInterface $output, $stubMethods = true) + { + $this->class = $class; + $this->behaviour = $behaviour; + $this->output = $output; + $this->stubMethods = $stubMethods; + + $this->__lime_reset(); + } + + public function __call($method, $parameters) + { + try + { + $method = new LimeMockMethod($this->class, $method); + + // if $stubMethods is set to FALSE, methods that are not configured are + // passed to the real implementation + if ($this->stubMethods || $this->state->isInvokable($method)) + { + return $this->state->invoke($method, $parameters); + } + else if (method_exists($this->class, $method->getMethod())) + { + // THIS METHOD CALL WILL LEAD TO SEGFAULTS WHEN EXECUTED IN A + // WEBSERVER ENVIRONMENT!!! + + if (PHP_VERSION_ID < 50300) + { + return call_user_func_array(array($this, 'parent::'.$method->getMethod()), $parameters); + } + else + { + return call_user_func_array('parent::'.$method->getMethod(), $parameters); + } + } + } + catch (LimeMockInvocationException $e) + { + // hide the internal trace to not distract when debugging test errors + throw new LimeMockException($e->getMessage()); + } + } + + public function __lime_replay() + { + $this->state = new LimeMockReplayState($this->behaviour); + } + + public function __lime_reset() + { + $this->behaviour->reset(); + + if (!$this->state instanceof LimeMockRecordState) + { + $this->state = new LimeMockRecordState($this->behaviour, $this->output); + } + } + + public function __lime_getState() + { + return $this->state; + } + + + public function replay() { return LimeMock::replay($this); } + public function any($methodName) { return LimeMock::any($this, $methodName); } + public function reset() { return LimeMock::reset($this); } + public function verify() { return LimeMock::verify($this); } + public function setExpectNothing() { return LimeMock::setExpectNothing($this); } + + + +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/output/LimeOutput.php b/tests/lib/vendor/lime/output/LimeOutput.php new file mode 100644 index 000000000000..b8ca0425a13a --- /dev/null +++ b/tests/lib/vendor/lime/output/LimeOutput.php @@ -0,0 +1,169 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Prints text on the console in different formats. + * + * You can use the various methods in this class to print nicely formatted + * text message in the console. If the console does not support text formatting, + * text formatting is suppressed, unless you pass the argument $forceColors=TRUE + * in the constructor. + * + * @package symfony + * @subpackage lime + * @author Fabien Potencier + * @author Bernhard Schussek + * @version SVN: $Id$ + */ +class LimeOutput +{ + const + ERROR = 'ERROR', + INFO = 'INFO', + PARAMETER = 'PARAMETER', + COMMENT = 'COMMENT', + GREEN_BAR = 'GREEN_BAR', + RED_BAR = 'RED_BAR', + INFO_BAR = 'INFO_BAR'; + + protected static + $styles = array(self::ERROR, self::INFO, self::PARAMETER, self::COMMENT, self::GREEN_BAR, self::RED_BAR, self::INFO_BAR); + + protected + $colorizer = null; + + /** + * Constructor. + * + * @param boolean $forceColors If set to TRUE, colorization will be enforced + * whether or not the current console supports it + */ + public function __construct($forceColors = false) + { + if (LimeColorizer::isSupported() || $forceColors) + { + $colorizer = new LimeColorizer(); + $colorizer->setStyle(self::ERROR, array('bg' => 'red', 'fg' => 'white', 'bold' => true)); + $colorizer->setStyle(self::INFO, array('fg' => 'green', 'bold' => true)); + $colorizer->setStyle(self::PARAMETER, array('fg' => 'cyan')); + $colorizer->setStyle(self::COMMENT, array('fg' => 'yellow')); + $colorizer->setStyle(self::GREEN_BAR, array('fg' => 'white', 'bg' => 'green', 'bold' => true)); + $colorizer->setStyle(self::RED_BAR, array('fg' => 'white', 'bg' => 'red', 'bold' => true)); + $colorizer->setStyle(self::INFO_BAR, array('fg' => 'cyan', 'bold' => true)); + + $this->colorizer = $colorizer; + } + } + + /** + * Colorizes the given text with the given style. + * + * @param string $text Some text + * @param string $style One of the predefined style constants + * @return string The formatted text + */ + protected function colorize($text, $style) + { + if (!in_array($style, self::$styles)) + { + throw new InvalidArgumentException(sprintf('The style "%s" does not exist', $style)); + } + + return is_null($this->colorizer) ? $text : $this->colorizer->colorize($text, $style); + } + + /** + * ? + */ + public function diag() + { + $messages = func_get_args(); + foreach ($messages as $message) + { + echo $this->colorize('# '.join("\n# ", (array) $message), self::COMMENT)."\n"; + } + } + + /** + * Prints a comment. + * + * @param string $message + */ + public function comment($message) + { + echo $this->colorize(sprintf('# %s', $message), self::COMMENT)."\n"; + } + + /** + * Prints an informational message. + * + * @param string $message + */ + public function info($message) + { + echo $this->colorize(sprintf('> %s', $message), self::INFO_BAR)."\n"; + } + + /** + * Prints an error. + * + * @param string $message + */ + public function error($message) + { + echo $this->colorize(sprintf(' %s ', $message), self::RED_BAR)."\n"; + } + + /** + * Prints and automatically colorizes a line. + * + * You can wrap the whole line into a specific predefined style by passing + * the style constant in the second parameter. + * + * @param string $message The message to colorize + * @param string $style The desired style constant + * @param boolean $colorize Whether to automatically colorize parts of the + * line + */ + public function echoln($message, $style = null, $colorize = true) + { + if ($colorize) + { + $message = preg_replace('/(?:^|\.)((?:not ok|dubious) *\d*)\b/e', '$this->colorize(\'$1\', self::ERROR)', $message); + $message = preg_replace('/(?:^|\.)(ok *\d*)\b/e', '$this->colorize(\'$1\', self::INFO)', $message); + $message = preg_replace('/"(.+?)"/e', '$this->colorize(\'$1\', self::PARAMETER)', $message); + $message = preg_replace('/(\->|\:\:)?([a-zA-Z0-9_]+?)\(\)/e', '$this->colorize(\'$1$2()\', self::PARAMETER)', $message); + } + + echo ($style ? $this->colorize($message, $style) : $message)."\n"; + } + + /** + * Prints a message in a green box. + * + * @param string $message + */ + public function greenBar($message) + { + echo $this->colorize($message.str_repeat(' ', 71 - min(71, strlen($message))), self::GREEN_BAR)."\n"; + } + + /** + * Prints a message a in a red box. + * + * @param string $message + */ + public function redBar($message) + { + echo $this->colorize($message.str_repeat(' ', 71 - min(71, strlen($message))), self::RED_BAR)."\n"; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/output/LimeOutputArray.php b/tests/lib/vendor/lime/output/LimeOutputArray.php new file mode 100644 index 000000000000..64629f1a3d3d --- /dev/null +++ b/tests/lib/vendor/lime/output/LimeOutputArray.php @@ -0,0 +1,222 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Formats test results as multidimensional array. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeOutputArray.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeOutputArray implements LimeOutputInterface +{ + protected + $serialize = false, + $results = array(), + $currentResults = null; + + /** + * Constructor. + * + * @param boolean $serialize Whether the array should be serialized before printing + */ + public function __construct($serialize = false) + { + $this->serialize = $serialize; + } + + /** + * (non-PHPdoc) + * @see output/LimeOutputInterface#supportsThreading() + */ + public function supportsThreading() + { + return true; + } + + /** + * (non-PHPdoc) + * @see output/LimeOutputInterface#focus($file) + */ + public function focus($file) + { + $this->currentResults =& $this->getResults($file); + } + + /** + * (non-PHPdoc) + * @see output/LimeOutputInterface#close() + */ + public function close() + { + } + + /** + * (non-PHPdoc) + * @see output/LimeOutputInterface#plan($amount) + */ + public function plan($amount) + { + $this->currentResults['stats']['plan'] = $amount; + } + + /** + * (non-PHPdoc) + * @see output/LimeOutputInterface#pass($message, $file, $line) + */ + public function pass($message, $file, $line) + { + $this->currentResults['stats']['total']++; + $this->currentResults['stats']['passed'][] = $this->addTest(true, $line, $file, $message); + } + + /** + * (non-PHPdoc) + * @see output/LimeOutputInterface#fail($message, $file, $line, $error) + */ + public function fail($message, $file, $line, $error = null) + { + $index = $this->addTest(false, $line, $file, $message); + + $this->currentResults['stats']['total']++; + $this->currentResults['stats']['failed'][] = $index; + + if (!is_null($error)) + { + $this->currentResults['tests'][$index]['error'] = $error; + } + } + + /** + * (non-PHPdoc) + * @see output/LimeOutputInterface#skip($message, $file, $line) + */ + public function skip($message, $file, $line) + { + $this->currentResults['stats']['total']++; + $this->currentResults['stats']['skipped'][] = $this->addTest(true, $line, $file, $message); + } + + /** + * (non-PHPdoc) + * @see output/LimeOutputInterface#todo($message, $file, $line) + */ + public function todo($message, $file, $line) + { + } + + /** + * (non-PHPdoc) + * @see output/LimeOutputInterface#warning($message, $file, $line) + */ + public function warning($message, $file, $line) + { + } + + /** + * (non-PHPdoc) + * @see output/LimeOutputInterface#error($error) + */ + public function error(LimeError $error) + { + } + + /** + * (non-PHPdoc) + * @see output/LimeOutputInterface#comment($message) + */ + public function comment($message) + { + } + + /** + * (non-PHPdoc) + * @see output/LimeOutputInterface#flush() + */ + public function flush() + { + if ($this->serialize) + { + print serialize($this->results); + } + else + { + var_export($this->results); + } + } + + /** + * Returns the results as array. + * + * @return array + */ + public function toArray() + { + return $this->results; + } + + /** + * Returns the result array of the given test file. + * + * @param string $file + * @return array + */ + protected function &getResults($file) + { + foreach ($this->results as $key => &$fileResults) + { + if ($fileResults['file'] == $file) + { + return $fileResults; + } + } + + $newResults = array( + 'file' => $file, + 'tests' => array(), + 'stats' => array( + 'plan' => 0, + 'total' => 0, + 'failed' => array(), + 'passed' => array(), + 'skipped' => array(), + ), + ); + + $this->results[] =& $newResults; + + return $newResults; + } + + /** + * Addsthe given test to the test results. + * + * @param boolean $status + * @param integer $line + * @param string $file + * @param string $message + * @return integer + */ + protected function addTest($status, $line, $file, $message) + { + $index = count($this->currentResults['tests']) + 1; + + $this->currentResults['tests'][$index] = array( + 'line' => $line, + 'file' => $file, + 'message' => $message, + 'status' => $status, + ); + + return $index; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/output/LimeOutputConsoleSummary.php b/tests/lib/vendor/lime/output/LimeOutputConsoleSummary.php new file mode 100644 index 000000000000..69dff6f58d5f --- /dev/null +++ b/tests/lib/vendor/lime/output/LimeOutputConsoleSummary.php @@ -0,0 +1,325 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Colorizes test results and summarizes them in the console. + * + * For each test file, one line is printed in the console with a few optional + * lines in case the file contains errors or failed tests. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeOutputConsoleSummary.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +class LimeOutputConsoleSummary implements LimeOutputInterface +{ + protected + $printer = null, + $options = array(), + $startTime = 0, + $file = null, + $actualFiles = 0, + $failedFiles = 0, + $actualTests = 0, + $failedTests = 0, + $expected = array(), + $actual = array(), + $passed = array(), + $failed = array(), + $errors = array(), + $warnings = array(), + $todos = array(), + $line = array(); + + /** + * Constructor. + * + * @param LimePrinter $printer The printer for printing text to the console + * @param array $options The options of this output + */ + public function __construct(LimePrinter $printer, array $options = array()) + { + $this->printer = $printer; + $this->startTime = time(); + $this->options = array_merge(array( + 'base_dir' => null, + 'processes' => 1, + 'verbose' => false, + ), $options); + } + + public function supportsThreading() + { + return true; + } + + public function focus($file) + { + $this->file = $file; + + if (!array_key_exists($file, $this->line)) + { + $this->line[$file] = count($this->line); + $this->expected[$file] = 0; + $this->actual[$file] = 0; + $this->passed[$file] = 0; + $this->failed[$file] = array(); + $this->errors[$file] = array(); + $this->warnings[$file] = array(); + $this->todos[$file] = array(); + } + } + + public function close() + { + if (!is_null($this->file)) + { + $this->actualFiles++; + $this->actualTests += $this->getActual(); + $this->failedTests += $this->getFailed(); + + $path = $this->truncate($this->file); + + if (strlen($path) > 71) + { + $path = substr($path, -71); + } + + $this->printer->printText(str_pad($path, 73, '.')); + + $incomplete = ($this->getExpected() > 0 && $this->getActual() != $this->getExpected()); + + if ($this->getErrors() || $this->getFailed() || $incomplete) + { + $this->failedFiles++; + $this->printer->printLine("not ok", LimePrinter::NOT_OK); + } + else if ($this->getWarnings()) + { + $this->printer->printLine("warning", LimePrinter::WARNING); + } + else + { + $this->printer->printLine("ok", LimePrinter::OK); + } + + if ($this->getExpected() > 0 && $this->getActual() != $this->getExpected()) + { + $this->printer->printLine(' Plan Mismatch:', LimePrinter::COMMENT); + if ($this->getActual() > $this->getExpected()) + { + $this->printer->printLine(sprintf(' Looks like you only planned %s tests but ran %s.', $this->getExpected(), $this->getActual())); + } + else + { + $this->printer->printLine(sprintf(' Looks like you planned %s tests but only ran %s.', $this->getExpected(), $this->getActual())); + } + } + + if ($this->getFailed()) + { + $this->printer->printLine(' Failed Tests:', LimePrinter::COMMENT); + + $i = 0; + foreach ($this->failed[$this->file] as $number => $failed) + { + if (!$this->options['verbose'] && $i > 2) + { + $this->printer->printLine(sprintf(' ... and %s more', $this->getFailed()-$i)); + break; + } + + ++$i; + + $this->printer->printLine(' not ok '.$number.' - '.$failed[0]); + } + } + + if ($this->getWarnings()) + { + $this->printer->printLine(' Warnings:', LimePrinter::COMMENT); + + foreach ($this->warnings[$this->file] as $i => $warning) + { + if (!$this->options['verbose'] && $i > 2) + { + $this->printer->printLine(sprintf(' ... and %s more', $this->getWarnings()-$i)); + break; + } + + $this->printer->printLine(' '.$warning[0]); + + if ($this->options['verbose']) + { + $this->printer->printText(' (in '); + $this->printer->printText($this->truncate($warning[1]), LimePrinter::TRACE); + $this->printer->printText(' on line '); + $this->printer->printText($warning[2], LimePrinter::TRACE); + $this->printer->printLine(')'); + } + } + } + + if ($this->getErrors()) + { + $this->printer->printLine(' Errors:', LimePrinter::COMMENT); + + foreach ($this->errors[$this->file] as $i => $error) + { + if (!$this->options['verbose'] && $i > 2) + { + $this->printer->printLine(sprintf(' ... and %s more', $this->getErrors()-$i)); + break; + } + + $this->printer->printLine(' '.$error->getMessage()); + + if ($this->options['verbose']) + { + $this->printer->printText(' (in '); + $this->printer->printText($this->truncate($error->getFile()), LimePrinter::TRACE); + $this->printer->printText(' on line '); + $this->printer->printText($error->getLine(), LimePrinter::TRACE); + $this->printer->printLine(')'); + } + } + } + + if ($this->getTodos()) + { + $this->printer->printLine(' TODOs:', LimePrinter::COMMENT); + + foreach ($this->todos[$this->file] as $i => $todo) + { + if (!$this->options['verbose'] && $i > 2) + { + $this->printer->printLine(sprintf(' ... and %s more', $this->getTodos()-$i)); + break; + } + + $this->printer->printLine(' '.$todo); + } + } + } + } + + protected function getExpected() + { + return $this->expected[$this->file]; + } + + protected function getActual() + { + return $this->actual[$this->file]; + } + + protected function getPassed() + { + return $this->passed[$this->file]; + } + + protected function getFailed() + { + return count($this->failed[$this->file]); + } + + protected function getErrors() + { + return count($this->errors[$this->file]); + } + + protected function getWarnings() + { + return count($this->warnings[$this->file]); + } + + protected function getTodos() + { + return count($this->todos[$this->file]); + } + + public function plan($amount) + { + $this->expected[$this->file] = $amount; + } + + public function pass($message, $file, $line) + { + $this->passed[$this->file]++; + $this->actual[$this->file]++; + } + + public function fail($message, $file, $line, $error = null) + { + $this->actual[$this->file]++; + $this->failed[$this->file][$this->actual[$this->file]] = array($message, $file, $line, $error); + } + + public function skip($message, $file, $line) + { + $this->actual[$this->file]++; + } + + public function todo($message, $file, $line) + { + $this->actual[$this->file]++; + $this->todos[$this->file][] = $message; + } + + public function warning($message, $file, $line) + { + $this->warnings[$this->file][] = array($message, $file, $line); + } + + public function error(LimeError $error) + { + $this->errors[$this->file][] = $error; + } + + public function comment($message) {} + + public function flush() + { + if ($this->failedFiles > 0) + { + $stats = sprintf(' Failed %d/%d test scripts, %.2f%% okay. %d/%d subtests failed, %.2f%% okay.', + $this->failedFiles, $this->actualFiles, 100 - 100*$this->failedFiles/max(1,$this->actualFiles), + $this->failedTests, $this->actualTests, 100 - 100*$this->failedTests/max(1,$this->actualTests)); + + $this->printer->printBox($stats, LimePrinter::NOT_OK); + } + else + { + $time = max(1, time() - $this->startTime); + $stats = sprintf(' Files=%d, Tests=%d, Time=%02d:%02d, Processes=%d', + $this->actualFiles, $this->actualTests, floor($time/60), $time%60, $this->options['processes']); + + $this->printer->printBox(' All tests successful.', LimePrinter::HAPPY); + $this->printer->printBox($stats, LimePrinter::HAPPY); + } + } + + protected function truncate($file) + { + $extension = pathinfo($file, PATHINFO_EXTENSION); + $file = substr($file, 0, strlen($file)-strlen($extension)); + + if (!is_null($this->options['base_dir'])) + { + return str_replace($this->options['base_dir'], '', $file); + } + else + { + return $file; + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/output/LimeOutputCoverage.php b/tests/lib/vendor/lime/output/LimeOutputCoverage.php new file mode 100644 index 000000000000..9008d6d49e80 --- /dev/null +++ b/tests/lib/vendor/lime/output/LimeOutputCoverage.php @@ -0,0 +1,47 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeOutputCoverage implements LimeOutputInterface +{ + public function supportsThreading() + { + return false; + } + + public function focus($file) + { + xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); + } + + public function flush() + { + echo serialize(xdebug_get_code_coverage()); + } + + public function close() {} + + public function plan($amount) {} + + public function pass($message, $file, $line) {} + + public function fail($message, $file, $line, $error = null) {} + + public function skip($message, $file, $line) {} + + public function todo($message, $file, $line) {} + + public function warning($message, $file, $line) {} + + public function error(LimeError $error) {} + + public function comment($message) {} +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/output/LimeOutputFactory.php b/tests/lib/vendor/lime/output/LimeOutputFactory.php new file mode 100644 index 000000000000..d9848a63f2bb --- /dev/null +++ b/tests/lib/vendor/lime/output/LimeOutputFactory.php @@ -0,0 +1,47 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeOutputFactory +{ + protected + $options = array(); + + public function __construct(array $options) + { + $this->options = array_merge(array( + 'serialize' => false, + 'force_colors' => false, + 'base_dir' => null, + ), $options); + } + + public function create($name) + { + $colorizer = LimeColorizer::isSupported() || $this->options['force_colors'] ? new LimeColorizer() : null; + $printer = new LimePrinter($colorizer); + + switch ($name) + { + case 'raw': + return new LimeOutputRaw(); + case 'xml': + return new LimeOutputXml(); + case 'array': + return new LimeOutputArray($this->options['serialize']); + case 'summary': + return new LimeOutputConsoleSummary($printer, $this->options); + case 'tap': + default: + return new LimeOutputTap($printer, $this->options); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/output/LimeOutputInspectable.php b/tests/lib/vendor/lime/output/LimeOutputInspectable.php new file mode 100644 index 000000000000..a10fc0593376 --- /dev/null +++ b/tests/lib/vendor/lime/output/LimeOutputInspectable.php @@ -0,0 +1,140 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeOutputInspectable implements LimeOutputInterface +{ + private + $output = null, + $planned = 0, + $passed = 0, + $failed = 0, + $skipped = 0, + $todos = 0, + $errors = 0, + $warnings = 0, + $failedFiles = array(); + + public function __construct(LimeOutputInterface $output = null) + { + $this->output = is_null($output) ? new LimeOutputNone() : $output; + } + + public function supportsThreading() + { + return $this->output->supportsThreading(); + } + + public function getPlanned() + { + return $this->planned; + } + + public function getPassed() + { + return $this->passed; + } + + public function getFailed() + { + return $this->failed; + } + + public function getSkipped() + { + return $this->skipped; + } + + public function getTodos() + { + return $this->todos; + } + + public function getErrors() + { + return $this->errors; + } + + public function getWarnings() + { + return $this->warnings; + } + + public function getFailedFiles() + { + return $this->failedFiles; + } + + public function focus($file) + { + $this->output->focus($file); + } + + public function close() + { + $this->output->close(); + } + + public function plan($amount) + { + $this->planned += $amount; + $this->output->plan($amount); + } + + public function pass($message, $file, $line) + { + $this->passed++; + $this->output->pass($message, $file, $line); + } + + public function fail($message, $file, $line, $error = null) + { + $this->failed++; + $this->failedFiles[] = $file; + $this->output->fail($message, $file, $line, $error); + } + + public function skip($message, $file, $line) + { + $this->skipped++; + $this->output->skip($message, $file, $line); + } + + public function todo($message, $file, $line) + { + $this->todos++; + $this->output->todo($message, $file, $line); + } + + public function warning($message, $file, $line) + { + $this->warnings++; + $this->failedFiles[] = $file; + $this->output->warning($message, $file, $line); + } + + public function error(LimeError $error) + { + $this->errors++; + $this->failedFiles[] = $error->getFile(); + $this->output->error($error); + } + + public function comment($message) + { + $this->output->comment($message); + } + + public function flush() + { + $this->output->flush(); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/output/LimeOutputInterface.php b/tests/lib/vendor/lime/output/LimeOutputInterface.php new file mode 100644 index 000000000000..7a61910dd278 --- /dev/null +++ b/tests/lib/vendor/lime/output/LimeOutputInterface.php @@ -0,0 +1,131 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Specifies how the results of an executed test should be presented. + * + * The class LimeTest uses an output to communicate the results of a test to + * the user or to a different application. All outputs must implement this + * interface. + * + * One output instance may receive test results of one or many test files. + * Each time when the output switches context between one test file and + * another, the method focus() is called with the name of the new test file. + * Once the file has been processed completely, the method close() is called + * to allow the output to finalize the results for the active test script. + * + * Depending on whether the output supports threading (parallel inputs from + * different actively tested files) the method supportsThreading() should + * return TRUE or FALSE. + * + * @package Lime + * @author Bernhard Schussek + * @version SVN: $Id: LimeOutputInterface.php 23701 2009-11-08 21:23:40Z bschussek $ + */ +interface LimeOutputInterface +{ + /** + * Returns whether this output supports processing results from different tests + * simultaneously. + * + * @return boolean + */ + public function supportsThreading(); + + /** + * Focuses the output on the given test file. + * + * All inputs until the next call to focus() concern this test file. + * + * @param string$file + */ + public function focus($file); + + /** + * Closes the output for the currently focused test file. + */ + public function close(); + + /** + * Sets the plan for the currently focused test file. + * + * The plan is the expected number of tests.0 + * + * @param integer $amount + */ + public function plan($amount); + + /** + * Informs the output about a successful test. + * + * @param string $message The test message + * @param string $file The file in which the successful test occured + * @param integer $line The line of the file + */ + public function pass($message, $file, $line); + + /** + * Informs the output about a failed test with an optional failure reason. + * + * @param string $message The test message + * @param string $file The file in which the failed test occured + * @param integer $line The line of the file + * @param string $error The reason why the test failed + */ + public function fail($message, $file, $line, $error = null); + + /** + * Informs the output about a skipped test. + * + * @param string $message The test message + * @param string $file The file in which the skipped test occured + * @param integer $line The line of the file + */ + public function skip($message, $file, $line); + + /** + * Informs the output about a todo. + * + * @param string $message The todo message + * @param string $file The file in which the todo occured + * @param integer $line The line of the file + */ + public function todo($message, $file, $line); + + /** + * Informs the output about a warning. + * + * @param string $message The warning message + * @param string $file The file in which the warning occured + * @param integer $line The line of the file + */ + public function warning($message, $file, $line); + + /** + * Informs the output about an error. + * + * @param LimeError $error The error that occurred + */ + public function error(LimeError $error); + + /** + * Informs the output about a comment. + * + * @param string $message The comment message + */ + public function comment($message); + + /** + * Flushes the test outputs to the console. + */ + public function flush(); +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/output/LimeOutputNone.php b/tests/lib/vendor/lime/output/LimeOutputNone.php new file mode 100644 index 000000000000..7963eeb5cf67 --- /dev/null +++ b/tests/lib/vendor/lime/output/LimeOutputNone.php @@ -0,0 +1,41 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeOutputNone implements LimeOutputInterface +{ + public function supportsThreading() + { + return true; + } + + public function focus($file) {} + + public function close() {} + + public function plan($amount) {} + + public function pass($message, $file, $line) {} + + public function fail($message, $file, $line, $error = null) {} + + public function skip($message, $file, $line) {} + + public function todo($message, $file, $line) {} + + public function warning($message, $file, $line) {} + + public function error(LimeError $error) {} + + public function comment($message) {} + + public function flush() {} +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/output/LimeOutputRaw.php b/tests/lib/vendor/lime/output/LimeOutputRaw.php new file mode 100644 index 000000000000..4b10970a238c --- /dev/null +++ b/tests/lib/vendor/lime/output/LimeOutputRaw.php @@ -0,0 +1,96 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeOutputRaw implements LimeOutputInterface +{ + protected + $initialized = false; + + protected function printCall($method, array $arguments = array()) + { + foreach ($arguments as &$argument) + { + if (is_string($argument)) + { + $argument = str_replace(array("\n", "\r"), array('\n', '\r'), $argument); + } + } + + if (!$this->initialized) + { + $this->initialized = true; + print "\0raw\0"; + } + + print serialize(array($method, $arguments))."\n"; + } + + public function supportsThreading() + { + return true; + } + + public function focus($file) + { + $this->printCall('focus', array($file)); + } + + public function close() + { + $this->printCall('close', array()); + } + + public function plan($amount) + { + $this->printCall('plan', array($amount)); + } + + public function pass($message, $file, $line) + { + $this->printCall('pass', array($message, $file, $line)); + } + + public function fail($message, $file, $line, $error = null) + { + $this->printCall('fail', array($message, $file, $line, $error)); + } + + public function skip($message, $file, $line) + { + $this->printCall('skip', array($message, $file, $line)); + } + + public function todo($message, $file, $line) + { + $this->printCall('todo', array($message, $file, $line)); + } + + public function warning($message, $file, $line) + { + $this->printCall('warning', array($message, $file, $line)); + } + + public function error(LimeError $error) + { + $this->printCall('error', array($error)); + } + + public function comment($message) + { + $this->printCall('comment', array($message)); + } + + public function flush() + { + $this->printCall('flush'); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/output/LimeOutputTap.php b/tests/lib/vendor/lime/output/LimeOutputTap.php new file mode 100644 index 000000000000..6b86603348d4 --- /dev/null +++ b/tests/lib/vendor/lime/output/LimeOutputTap.php @@ -0,0 +1,280 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeOutputTap implements LimeOutputInterface +{ + protected + $options = array(), + $expected = null, + $passed = 0, + $actual = 0, + $warnings = 0, + $errors = 0, + $file = null, + $printer = null; + + public function __construct(LimePrinter $printer, array $options = array()) + { + $this->printer = $printer; + $this->options = array_merge(array( + 'verbose' => false, + 'base_dir' => null, + ), $options); + } + + public function supportsThreading() + { + return false; + } + + private function stripBaseDir($path) + { + return is_null($this->options['base_dir']) ? $path : str_replace($this->options['base_dir'], '', $path); + } + + public function focus($file) + { + if ($this->file !== $file) + { + $this->printer->printLine('# '.$this->stripBaseDir($file), LimePrinter::INFO); + + $this->file = $file; + } + } + + public function close() + { + } + + public function plan($amount) + { + $this->expected += $amount; + } + + public function pass($message, $file, $line) + { + $this->actual++; + $this->passed++; + + if (empty($message)) + { + $this->printer->printLine('ok '.$this->actual, LimePrinter::OK); + } + else + { + $this->printer->printText('ok '.$this->actual, LimePrinter::OK); + $this->printer->printLine(' - '.$message); + } + } + + public function fail($message, $file, $line, $error = null) + { + $this->actual++; + + if (empty($message)) + { + $this->printer->printLine('not ok '.$this->actual, LimePrinter::NOT_OK); + } + else + { + $this->printer->printText('not ok '.$this->actual, LimePrinter::NOT_OK); + $this->printer->printLine(' - '.$message); + } + + $this->printer->printLine(sprintf('# Failed test (%s at line %s)', $this->stripBaseDir($file), $line), LimePrinter::COMMENT); + + if (!is_null($error)) + { + foreach (explode("\n", $error) as $line) + { + $this->printer->printLine('# '.$line, LimePrinter::COMMENT); + } + } + } + + public function skip($message, $file, $line) + { + $this->actual++; + $this->passed++; + + if (empty($message)) + { + $this->printer->printText('ok '.$this->actual, LimePrinter::SKIP); + $this->printer->printText(' '); + } + else + { + $this->printer->printText('ok '.$this->actual, LimePrinter::SKIP); + $this->printer->printText(' - '.$message.' '); + } + + $this->printer->printLine('# SKIP', LimePrinter::SKIP); + } + + public function todo($message, $file, $line) + { + $this->actual++; + $this->passed++; + + if (empty($message)) + { + $this->printer->printText('not ok '.$this->actual, LimePrinter::TODO); + $this->printer->printText(' '); + } + else + { + $this->printer->printText('not ok '.$this->actual, LimePrinter::TODO); + $this->printer->printText(' - '.$message.' '); + } + + $this->printer->printLine('# TODO', LimePrinter::TODO); + } + + public function warning($message, $file, $line) + { + $this->warnings++; + + $message .= sprintf("\n(in %s on line %s)", $this->stripBaseDir($file), $line); + + $this->printer->printLargeBox($message, LimePrinter::WARNING); + } + + public function error(LimeError $error) + { + $this->errors++; + + $message = sprintf("%s: %s\n(in %s on line %s)", $error->getType(), + $error->getMessage(), $this->stripBaseDir($error->getFile()), $error->getLine()); + + $this->printer->printLargeBox($message, LimePrinter::ERROR); + + $this->printer->printLine('Exception trace:', LimePrinter::COMMENT); + + $this->printTrace(null, $error->getFile(), $error->getLine()); + + foreach ($error->getTrace() as $trace) + { + // hide the part of the trace that is responsible for getting the + // annotations to work + if (strpos($trace['function'], '__lime_annotation_') === 0 && !$this->options['verbose']) + { + break; + } + + if (array_key_exists('class', $trace)) + { + $method = sprintf('%s%s%s()', $trace['class'], $trace['type'], $trace['function']); + } + else + { + $method = sprintf('%s()', $trace['function']); + } + + if (array_key_exists('file', $trace)) + { + $this->printTrace($method, $trace['file'], $trace['line']); + } + else + { + $this->printTrace($method); + } + } + + $this->printer->printLine(''); + } + + private function printTrace($method = null, $file = null, $line = null) + { + if (!is_null($method)) + { + $method .= ' '; + } + + $this->printer->printText(' '.$method.'at '); + + if (!is_null($file) && !is_null($line)) + { + $this->printer->printText($this->stripBaseDir($file), LimePrinter::TRACE); + $this->printer->printText(':'); + $this->printer->printLine($line, LimePrinter::TRACE); + } + else + { + $this->printer->printLine('[internal function]'); + } + } + + public function info($message) + { + $this->printer->printLine('# '.$message, LimePrinter::INFO); + } + + public function comment($message) + { + $this->printer->printLine('# '.$message, LimePrinter::COMMENT); + } + + public static function getMessages($actual, $expected, $passed, $errors, $warnings) + { + $messages = array(); + + if ($passed == $expected && $passed === $actual && $errors == 0) + { + if ($warnings > 0) + { + $messages[] = array('Looks like you\'re nearly there.', LimePrinter::WARNING); + } + else + { + $messages[] = array('Looks like everything went fine.', LimePrinter::HAPPY); + } + } + else if ($passed != $actual) + { + $messages[] = array(sprintf('Looks like you failed %s tests of %s.', $actual - $passed, $actual), LimePrinter::ERROR); + } + else if ($errors > 0) + { + $messages[] = array('Looks like some errors occurred.', LimePrinter::ERROR); + } + + if ($actual > $expected && $expected > 0) + { + $messages[] = array(sprintf('Looks like you only planned %s tests but ran %s.', $expected, $actual), LimePrinter::ERROR); + } + else if ($actual < $expected) + { + $messages[] = array(sprintf('Looks like you planned %s tests but only ran %s.', $expected, $actual), LimePrinter::ERROR); + } + + return $messages; + } + + public function flush() + { + if (is_null($this->expected)) + { + $this->plan($this->actual); + } + + $this->printer->printLine('1..'.$this->expected); + + $messages = self::getMessages($this->actual, $this->expected, $this->passed, $this->errors, $this->warnings); + + foreach ($messages as $message) + { + list ($message, $style) = $message; + + $this->printer->printBox(' '.$message, $style); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/output/LimeOutputXml.php b/tests/lib/vendor/lime/output/LimeOutputXml.php new file mode 100644 index 000000000000..213755bd1a6e --- /dev/null +++ b/tests/lib/vendor/lime/output/LimeOutputXml.php @@ -0,0 +1,139 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeOutputXml implements LimeOutputInterface +{ + protected + $ouptut = null; + + public function __construct() + { + $this->output = new LimeOutputArray(); + } + + public function supportsThreading() + { + return $this->output->supportsThreading(); + } + + public function focus($file) + { + return $this->output->focus($file); + } + + public function close() + { + return $this->output->close(); + } + + public function plan($amount) + { + return $this->output->plan($amount); + } + + public function pass($message, $file, $line) + { + return $this->output->plan($message, $file, $line); + } + + public function fail($message, $file, $line, $error = null) + { + return $this->output->fail($message, $file, $line, $error); + } + + public function skip($message, $file, $line) + { + return $this->output->skip($message, $file, $line); + } + + public function todo($message, $file, $line) + { + return $this->output->todo($message, $file, $line); + } + + public function warning($message, $file, $line) + { + return $this->output->warning($message, $file, $line); + } + + public function error(LimeError $error) + { + return $this->output->error($error); + } + + public function comment($message) + { + return $this->output->comment($message); + } + + public function flush() + { + print $this->toXml(); + } + + public function toXml() + { + $results = $this->output->toArray(); + + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + $dom->appendChild($testsuites = $dom->createElement('testsuites')); + + $errors = 0; + $failures = 0; + $errors = 0; + $skipped = 0; + $assertions = 0; + + foreach ($results as $result) + { + $testsuites->appendChild($testSuite = $dom->createElement('testsuite')); + $testSuite->setAttribute('name', basename($result['file'], '.php')); + $testSuite->setAttribute('file', $result['file']); + $testSuite->setAttribute('failures', count($result['stats']['failed'])); + $testSuite->setAttribute('errors', 0); + $testSuite->setAttribute('skipped', count($result['stats']['skipped'])); + $testSuite->setAttribute('tests', $result['stats']['plan']); + $testSuite->setAttribute('assertions', $result['stats']['plan']); + + $failures += count($result['stats']['failed']); + $skipped += count($result['stats']['skipped']); + $assertions += $result['stats']['plan']; + + foreach ($result['tests'] as $test) + { + $testSuite->appendChild($testCase = $dom->createElement('testcase')); + $testCase->setAttribute('name', $test['message']); + $testCase->setAttribute('file', $test['file']); + $testCase->setAttribute('line', $test['line']); + $testCase->setAttribute('assertions', 1); + if (!$test['status']) + { + $testCase->appendChild($failure = $dom->createElement('failure')); + $failure->setAttribute('type', 'lime'); + if (array_key_exists('error', $test)) + { + $failure->appendChild($dom->createTextNode($test['error'])); + } + } + } + } + + $testsuites->setAttribute('failures', $failures); + $testsuites->setAttribute('errors', $errors); + $testsuites->setAttribute('tests', $assertions); + $testsuites->setAttribute('assertions', $assertions); + $testsuites->setAttribute('skipped', $skipped); + + return $dom->saveXml(); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/parser/LimeParser.php b/tests/lib/vendor/lime/parser/LimeParser.php new file mode 100644 index 000000000000..736758019c47 --- /dev/null +++ b/tests/lib/vendor/lime/parser/LimeParser.php @@ -0,0 +1,43 @@ +output = $output; + } + + protected function clearErrors() + { + while (!empty($this->buffer)) + { + if (preg_match('/^\s*([\w\s]+): (.+) in (.+) on line (\d+)/', $this->buffer, $matches)) + { + $this->buffer = trim(substr($this->buffer, strlen($matches[0]))); + + if ($matches[1] == 'Warning') + { + $this->output->warning($matches[1].': '.$matches[2], $matches[3], $matches[4]); + } + else + { + $this->output->error(new LimeError($matches[2], $matches[3], $matches[4], $matches[1])); + } + + // consume Xdebug call stack + while (preg_match('/^(Call Stack:|\d\.\d+\s+\d+\s+\d+\.\s+.+:\d+)/', $this->buffer, $matches)) + { + $this->buffer = trim(substr($this->buffer, strlen($matches[0]))); + } + } + else + { + break; + } + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/parser/LimeParserInterface.php b/tests/lib/vendor/lime/parser/LimeParserInterface.php new file mode 100644 index 000000000000..43b6a641239a --- /dev/null +++ b/tests/lib/vendor/lime/parser/LimeParserInterface.php @@ -0,0 +1,8 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeParserRaw extends LimeParser +{ + protected + $suppressedMethods = array(), + $error = false; + + public function __construct(LimeOutputInterface $output, array $suppressedMethods = array()) + { + parent::__construct($output); + + $this->suppressedMethods = $suppressedMethods; + } + + public function parse($data) + { + $this->buffer .= $data; + + $lines = explode("\n", $this->buffer); + + while ($line = array_shift($lines)) + { + if (!empty($line)) + { + $this->error = false; + + set_error_handler(array($this, 'failedUnserialize')); + list($method, $arguments) = unserialize($line); + restore_error_handler(); + + if ($this->error) + { + // prepend the line again, maybe we can unserialize later + array_unshift($lines, $line); + break; + } + + if (!in_array($method, $this->suppressedMethods)) + { + foreach ($arguments as &$argument) + { + if (is_string($argument)) + { + $argument = stripcslashes($argument); + } + } + call_user_func_array(array($this->output, $method), $arguments); + } + } + } + + $this->buffer = implode("\n", $lines); + + $this->clearErrors(); + } + + public function done() + { + return empty($this->buffer); + } + + public function failedUnserialize() + { + $this->error = true; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/parser/LimeParserTap.php b/tests/lib/vendor/lime/parser/LimeParserTap.php new file mode 100644 index 000000000000..4bd5f3482ba4 --- /dev/null +++ b/tests/lib/vendor/lime/parser/LimeParserTap.php @@ -0,0 +1,81 @@ +buffer .= $data; + + while (!$this->done()) + { + if (preg_match('/^(.+)\n/', $this->buffer, $matches)) + { + $this->buffer = substr($this->buffer, strlen($matches[0])); + $line = $matches[0]; + + if (preg_match('/^1\.\.(\d+)\n/', $line, $matches)) + { + $this->output->plan((int)$matches[1]); + } + else if (preg_match('/^ok \d+( - (.+?))?( # (SKIP|TODO)( .+)?)?\n/', $line, $matches)) + { + $message = count($matches) > 2 ? $matches[2] : ''; + + if (count($matches) > 3) + { + if ($matches[4] == 'SKIP') + { + $this->output->skip($message, '', ''); + } + else + { + $this->output->todo($message, '', ''); + $this->output->warning('TODOs are expected to have status "not ok"', '', ''); + } + } + else + { + $this->output->pass($message, '', ''); + } + } + else if (preg_match('/^not ok \d+( - (.+?))?( # (SKIP|TODO)( .+)?)?\n/', $line, $matches)) + { + $message = count($matches) > 2 ? $matches[2] : ''; + + if (count($matches) > 3) + { + if ($matches[4] == 'SKIP') + { + $this->output->skip($message, '', ''); + $this->output->warning('Skipped tests are expected to have status "ok"', '', ''); + } + else + { + $this->output->todo($message, '', ''); + } + } + else + { + $this->output->fail($message, '', ''); + } + } + } + else + { + break; + } + } + + $this->clearErrors(); + } + + public function done() + { + return empty($this->buffer); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/shell/LimeShell.php b/tests/lib/vendor/lime/shell/LimeShell.php new file mode 100644 index 000000000000..f3fdbc7b50ff --- /dev/null +++ b/tests/lib/vendor/lime/shell/LimeShell.php @@ -0,0 +1,123 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Provides an interface to execute PHP code or files. + * + * @package lime + * @author Bernhard Schussek + * @author Fabien Potencier + * @version SVN: $Id: LimeShell.php 24323 2009-11-24 11:27:51Z bschussek $ + */ +abstract class LimeShell +{ + const + SUCCESS = 0, + FAILED = 1, + UNKNOWN = 255; + + protected static + $executable = null; + + /** + * Sets the preferred PHP executable. + * + * @param string $executable + */ + public static function setExecutable($executable) + { + self::$executable = $executable; + } + + /** + * Tries to find the system's PHP executable and returns it. + * + * @return string + */ + public static function getExecutable() + { + if (is_null(self::$executable)) + { + if (getenv('PHP_PATH')) + { + self::$executable = getenv('PHP_PATH'); + + if (!is_executable(self::$executable)) + { + throw new Exception('The defined PHP_PATH environment variable is not a valid PHP executable.'); + } + } + else + { + self::$executable = PHP_BINDIR.DIRECTORY_SEPARATOR.'php'; + } + } + + if (!is_executable(self::$executable)) + { + $path = getenv('PATH') ? getenv('PATH') : getenv('Path'); + $extensions = DIRECTORY_SEPARATOR == '\\' ? (getenv('PATHEXT') ? explode(PATH_SEPARATOR, getenv('PATHEXT')) : array('.exe', '.bat', '.cmd', '.com')) : array(''); + foreach (array('php5', 'php') as $executable) + { + foreach ($extensions as $extension) + { + foreach (explode(PATH_SEPARATOR, $path) as $dir) + { + $file = $dir.DIRECTORY_SEPARATOR.$executable.$extension; + if (is_executable($file)) + { + self::$executable = $file; + break 3; + } + } + } + } + + if (!is_executable(self::$executable)) + { + throw new Exception("Unable to find PHP executable."); + } + } + + return self::$executable; + } + + /** + * Parses the given CLI arguments and returns an array of options. + * + * @param array $arguments + * @return array + */ + public static function parseArguments(array $arguments) + { + $options = array(); + + foreach ($GLOBALS['argv'] as $parameter) + { + if (preg_match('/^--([a-zA-Z\-]+)=(.+)$/', $parameter, $matches)) + { + if (in_array($matches[2], array('true', 'false'))) + { + $matches[2] = eval($matches[2]); + } + + $options[$matches[1]] = $matches[2]; + } + else if (preg_match('/^--([a-zA-Z\-]+)$/', $parameter, $matches)) + { + $options[$matches[1]] = true; + } + } + + return $options; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/shell/LimeShellCode.php b/tests/lib/vendor/lime/shell/LimeShellCode.php new file mode 100644 index 000000000000..e5ba820550d9 --- /dev/null +++ b/tests/lib/vendor/lime/shell/LimeShellCode.php @@ -0,0 +1,12 @@ + $value) + { + $arguments[$argument] = '--'.$argument; + + if ($value !== true) + { + if (!is_string($value)) + { + $value = var_export($value, true); + } + + $arguments[$argument] .= '='.escapeshellarg($value); + } + } + + $this->errorFile = tempnam(sys_get_temp_dir(), 'lime'); + + // see http://trac.symfony-project.org/ticket/5437 for the explanation on the weird "cd" thing + $this->command = sprintf( + 'cd & %s %s %s 2>%s', + escapeshellarg(LimeShell::getExecutable()), + escapeshellarg($file), + implode(' ', $arguments), + $this->errorFile + ); + } + + public function execute() + { + // clear old errors + $this->errors = ''; + file_put_contents($this->errorFile, ''); + + ob_start(); + passthru($this->command, $this->status); + $this->output = ob_get_clean(); + $this->errors = file_get_contents($this->errorFile); + } + + public function getStatus() + { + return $this->status; + } + + public function getOutput() + { + return $this->output; + } + + public function getErrors() + { + return $this->errors; + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/shell/LimeShellProcess.php b/tests/lib/vendor/lime/shell/LimeShellProcess.php new file mode 100644 index 000000000000..a5ed8841b2f5 --- /dev/null +++ b/tests/lib/vendor/lime/shell/LimeShellProcess.php @@ -0,0 +1,35 @@ +errorHandle = fopen($this->errorFile, 'w+'); // clear error file + $this->handle = popen($this->command, 'r'); + } + + public function getStatus() + { + throw new BadMethodCallException('Status is not supported by processes'); + } + + public function getOutput() + { + return feof($this->handle) ? '' : fread($this->handle, 1024); + } + + public function getErrors() + { + // don't check feof here, for some reason some errors get dropped then + return fread($this->errorHandle, 1024); + } + + public function isClosed() + { + return feof($this->handle) && feof($this->errorHandle); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/tester/LimeTester.php b/tests/lib/vendor/lime/tester/LimeTester.php new file mode 100644 index 000000000000..ee5b3e895834 --- /dev/null +++ b/tests/lib/vendor/lime/tester/LimeTester.php @@ -0,0 +1,111 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +abstract class LimeTester implements LimeTesterInterface +{ + protected static + $factory = null; + + protected + $value = null, + $type = null; + + public static function create($value) + { + return self::getFactory()->create($value); + } + + public static function register($type, $tester) + { + return self::getFactory()->register($type, $tester); + } + + public static function unregister($type) + { + return self::getFactory()->unregister($type); + } + + private static function getFactory() + { + if (is_null(self::$factory)) + { + self::$factory = new LimeTesterFactory(); + } + + return self::$factory; + } + + public function __construct($value) + { + $this->value = $value; + } + + public function is(LimeTesterInterface $expected) + { + throw new LimeAssertionFailedException($this, $expected); + } + + public function isnt(LimeTesterInterface $expected) + { + throw new LimeAssertionFailedException($this, $expected); + } + + public function same(LimeTesterInterface $expected) + { + throw new LimeAssertionFailedException($this, $expected); + } + + public function isntSame(LimeTesterInterface $expected) + { + throw new LimeAssertionFailedException($this, $expected); + } + + public function like(LimeTesterInterface $expected) + { + throw new LimeAssertionFailedException($this, $expected); + } + + public function unlike(LimeTesterInterface $expected) + { + throw new LimeAssertionFailedException($this, $expected); + } + + public function greaterThan(LimeTesterInterface $expected) + { + throw new LimeAssertionFailedException($this, $expected); + } + + public function greaterThanEqual(LimeTesterInterface $expected) + { + throw new LimeAssertionFailedException($this, $expected); + } + + public function lessThan(LimeTesterInterface $expected) + { + throw new LimeAssertionFailedException($this, $expected); + } + + public function lessThanEqual(LimeTesterInterface $expected) + { + throw new LimeAssertionFailedException($this, $expected); + } + + public function contains(LimeTesterInterface $expected) + { + throw new LimeAssertionFailedException($this, $expected); + } + + public function containsNot(LimeTesterInterface $expected) + { + throw new LimeAssertionFailedException($this, $expected); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/tester/LimeTesterArray.php b/tests/lib/vendor/lime/tester/LimeTesterArray.php new file mode 100644 index 000000000000..f4cef52497e1 --- /dev/null +++ b/tests/lib/vendor/lime/tester/LimeTesterArray.php @@ -0,0 +1,293 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeTesterArray extends LimeTester implements ArrayAccess, Iterator +{ + protected + $type = 'array'; + + public function is(LimeTesterInterface $expected) + { + if (!$expected instanceof LimeTesterArray || $this->getType() !== $expected->getType()) + { + throw new LimeAssertionFailedException($this, $expected); + } + + $remaining = $this->value; + + foreach ($expected as $key => $value) + { + if (!isset($this[$key])) + { + throw new LimeAssertionFailedException($this, $expected->dumpExcerpt($key, $value)); + } + + try + { + $this[$key]->is($value); + } + catch (LimeAssertionFailedException $e) + { + throw new LimeAssertionFailedException($this->dumpExcerpt($key, $e->getActual()), $expected->dumpExcerpt($key, $e->getExpected())); + } + + unset($remaining[$key]); + } + + foreach ($remaining as $key => $value) + { + throw new LimeAssertionFailedException($this->dumpExcerpt($key, $value), $expected); + } + } + + public function isnt(LimeTesterInterface $expected) + { + if (!$expected instanceof LimeTesterArray || $this->getType() !== $expected->getType()) + { + return; + } + + foreach ($expected as $key => $value) + { + if (!isset($this[$key])) + { + return; + } + + try + { + $this[$key]->isnt($value); + return; + } + catch (LimeAssertionFailedException $e) + { + } + } + + throw new LimeAssertionFailedException($this, $expected); + } + + public function same(LimeTesterInterface $expected) + { + if (!$expected instanceof LimeTesterArray || $this->getType() !== $expected->getType()) + { + throw new LimeAssertionFailedException($this, $expected); + } + + for ($expected->rewind(), $this->rewind(); $expected->valid(); $expected->next(), $this->next()) + { + if (!$this->valid()) + { + throw new LimeAssertionFailedException($this, $expected->dumpExcerpt($expected->key(), $expected->current())); + } + + if ($this->key() != $expected->key()) + { + throw new LimeAssertionFailedException($this->dumpExcerpt(key($this->value), current($this->value)), $expected->dumpExcerpt($expected->key(), $expected->current())); + } + + try + { + $this->current()->same($expected->current()); + } + catch (LimeAssertionFailedException $e) + { + throw new LimeAssertionFailedException($this->dumpExcerpt($this->key(), $e->getActual()), $expected->dumpExcerpt($expected->key(), $e->getExpected())); + } + } + + if ($this->valid()) + { + throw new LimeAssertionFailedException($this->dumpExcerpt($this->key(), $this->current()), $expected); + } + } + + public function isntSame(LimeTesterInterface $expected) + { + if (!$expected instanceof LimeTesterArray || $this->getType() !== $expected->getType()) + { + return; + } + + for ($expected->rewind(), $this->rewind(); $expected->valid(); $expected->next(), $this->next()) + { + if (!$this->valid() || $this->key() !== $expected->key()) + { + return; + } + + try + { + $this->current()->isntSame($expected->current()); + } + catch (LimeAssertionFailedException $e) + { + throw new LimeAssertionFailedException($this->dumpExcerpt($this->key(), $e->getActual()), $expected->dumpExcerpt($expected->key(), $e->getExpected())); + } + } + } + + public function contains(LimeTesterInterface $expected) + { + foreach ($this as $key => $value) + { + try + { + $value->is($expected); + return; + } + catch (LimeAssertionFailedException $e) + { + } + } + + throw new LimeAssertionFailedException($this->dumpAll(), $expected); + } + + public function containsNot(LimeTesterInterface $expected) + { + foreach ($this as $key => $value) + { + $equal = true; + + try + { + $value->is($expected); + } + catch (LimeAssertionFailedException $e) + { + $equal = false; + } + + if ($equal) + { + throw new LimeAssertionFailedException($this->dumpAll(), $expected); + } + } + } + + public function __toString() + { + return $this->dumpExcerpt(); + } + + protected function getType() + { + return 'array'; + } + + protected function dumpAll() + { + $result = $this->getType().' ('; + + if (!empty($this->value)) + { + $result .= "\n"; + + foreach ($this->value as $k => $v) + { + $result .= sprintf(" %s => %s,\n", var_export($k, true), $this->indent($v)); + } + } + + $result .= ')'; + + return $result; + } + + protected function dumpExcerpt($key = null, $value = null) + { + $result = $this->getType().' ('; + + if (!empty($this->value)) + { + $truncated = false; + $result .= "\n"; + + foreach ($this->value as $k => $v) + { + if ((is_null($key) || $key != $k) && !$truncated) + { + $result .= " ...\n"; + $truncated = true; + } + else if ($k == $key) + { + $value = is_null($value) ? $v : $value; + $result .= sprintf(" %s => %s,\n", var_export($k, true), $this->indent($value)); + $truncated = false; + } + } + } + + $result .= ')'; + + return $result; + } + + protected function indent($lines) + { + $lines = explode("\n", $lines); + + foreach ($lines as $key => $line) + { + $lines[$key] = ' '.$line; + } + + return trim(implode("\n", $lines)); + } + + public function offsetGet($key) + { + return LimeTester::create($this->value[$key]); + } + + public function offsetExists($key) + { + return array_key_exists($key, $this->value); + } + + public function offsetSet($key, $value) + { + throw new BadMethodCallException('This method is not supported'); + } + + public function offsetUnset($key) + { + throw new BadMethodCallException('This method is not supported'); + } + + public function current() + { + return $this[$this->key()]; + } + + public function key() + { + return key($this->value); + } + + public function next() + { + next($this->value); + } + + public function valid() + { + return $this->key() !== null; + } + + public function rewind() + { + reset($this->value); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/tester/LimeTesterDouble.php b/tests/lib/vendor/lime/tester/LimeTesterDouble.php new file mode 100644 index 000000000000..5945c6844db3 --- /dev/null +++ b/tests/lib/vendor/lime/tester/LimeTesterDouble.php @@ -0,0 +1,83 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeTesterDouble extends LimeTesterInteger +{ + const + EPSILON = 0.0000000001; + + protected + $type = 'double'; + + public function __construct($value) + { + parent::__construct((double)$value); + } + + public function __toString() + { + if ($this->value == round($this->value)) + { + return sprintf('%.1f', $this->value); + } + else + { + return (string)$this->value; + } + } + + public function is(LimeTesterInterface $expected) + { + if (is_infinite($this->value) && is_infinite($expected->value)) + { + return; + } + + if (abs($this->value - $expected->value) >= self::EPSILON) + { + throw new LimeAssertionFailedException($this, $expected); + } + } + + public function isnt(LimeTesterInterface $expected) + { + if ((is_infinite($this->value) && is_infinite($expected->value)) || abs($this->value - $expected->value) < self::EPSILON) + { + throw new LimeAssertionFailedException($this, $expected); + } + } + + public function same(LimeTesterInterface $expected) + { + $this->is($expected); + + if (gettype($this->value) != gettype($expected->value)) + { + throw new LimeAssertionFailedException($this, $expected); + } + } + + public function isntSame(LimeTesterInterface $expected) + { + try + { + $this->is($expected); + } + catch (LimeAssertionFailedException $e) + { + if (gettype($this->value) == gettype($expected->value)) + { + throw $e; + } + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/tester/LimeTesterException.php b/tests/lib/vendor/lime/tester/LimeTesterException.php new file mode 100644 index 000000000000..347da2e5733c --- /dev/null +++ b/tests/lib/vendor/lime/tester/LimeTesterException.php @@ -0,0 +1,24 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeTesterException extends LimeTesterObject +{ + public function __construct(Exception $exception) + { + parent::__construct($exception); + + unset($this->value['file']); + unset($this->value['line']); + unset($this->value['trace']); + unset($this->value['string']); // some internal property of Exception + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/tester/LimeTesterFactory.php b/tests/lib/vendor/lime/tester/LimeTesterFactory.php new file mode 100644 index 000000000000..efbe1df89ccd --- /dev/null +++ b/tests/lib/vendor/lime/tester/LimeTesterFactory.php @@ -0,0 +1,113 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeTesterFactory +{ + protected + $testers = array( + 'null' => 'LimeTesterScalar', + 'integer' => 'LimeTesterInteger', + 'boolean' => 'LimeTesterScalar', + 'string' => 'LimeTesterString', + 'double' => 'LimeTesterDouble', + 'array' => 'LimeTesterArray', + 'object' => 'LimeTesterObject', + 'resource' => 'LimeTesterResource', + 'Exception' => 'LimeTesterException', + ); + + public function create($value) + { + $type = null; + + if (is_null($value)) + { + $type = 'null'; + } + else if (is_object($value) && array_key_exists(get_class($value), $this->testers)) + { + $type = get_class($value); + } + else if (is_object($value)) + { + $class = new ReflectionClass($value); + + foreach ($class->getInterfaces() as $interface) + { + if (array_key_exists($interface->getName(), $this->testers)) + { + $type = $interface->getName(); + break; + } + } + + $parentClass = $class; + + while ($parentClass = $parentClass->getParentClass()) + { + if (array_key_exists($parentClass->getName(), $this->testers)) + { + $type = $parentClass->getName(); + break; + } + } + + if (!empty($type)) + { + // cache the tester + $this->testers[$class->getName()] = $this->testers[$type]; + } + } + + if (empty($type)) + { + if (array_key_exists(gettype($value), $this->testers)) + { + $type = gettype($value); + } + else + { + throw new InvalidArgumentException(sprintf('No tester is registered for type "%s"', gettype($value))); + } + } + + $class = $this->testers[$type]; + + return new $class($value); + } + + public function register($type, $tester) + { + if (!class_exists($tester)) + { + throw new InvalidArgumentException(sprintf('The class "%s" does not exist', $tester)); + } + + $class = new ReflectionClass($tester); + + if (!$class->implementsInterface('LimeTesterInterface')) + { + throw new InvalidArgumentException('Testers must implement "LimeTesterInterface"'); + } + + $this->testers[$type] = $tester; + } + + public function unregister($type) + { + if (array_key_exists($type, $this->testers)) + { + unset($this->testers[$type]); + } + } + +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/tester/LimeTesterInteger.php b/tests/lib/vendor/lime/tester/LimeTesterInteger.php new file mode 100644 index 000000000000..d88461db168f --- /dev/null +++ b/tests/lib/vendor/lime/tester/LimeTesterInteger.php @@ -0,0 +1,17 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeTesterInteger extends LimeTesterScalar +{ + protected + $type = 'integer'; +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/tester/LimeTesterInterface.php b/tests/lib/vendor/lime/tester/LimeTesterInterface.php new file mode 100644 index 000000000000..38c110997b05 --- /dev/null +++ b/tests/lib/vendor/lime/tester/LimeTesterInterface.php @@ -0,0 +1,40 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +interface LimeTesterInterface +{ + public function __toString(); + + public function is(LimeTesterInterface $expected); + + public function isnt(LimeTesterInterface $expected); + + public function same(LimeTesterInterface $expected); + + public function isntSame(LimeTesterInterface $expected); + + public function like(LimeTesterInterface $expected); + + public function unlike(LimeTesterInterface $expected); + + public function greaterThan(LimeTesterInterface $expected); + + public function greaterThanEqual(LimeTesterInterface $expected); + + public function lessThan(LimeTesterInterface $expected); + + public function lessThanEqual(LimeTesterInterface $expected); + + public function contains(LimeTesterInterface $expected); + + public function containsNot(LimeTesterInterface $expected); +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/tester/LimeTesterObject.php b/tests/lib/vendor/lime/tester/LimeTesterObject.php new file mode 100644 index 000000000000..1fe2bf1c3c3a --- /dev/null +++ b/tests/lib/vendor/lime/tester/LimeTesterObject.php @@ -0,0 +1,121 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeTesterObject extends LimeTesterArray +{ + private static + $equal = array(), + $unequal = array(); + + private + $object = null; + + public static function toArray($object) + { + if (!is_object($object)) + { + throw new InvalidArgumentException('The argument must be an object'); + } + + $array = array(); + + foreach ((array)$object as $key => $value) + { + // properties are transformed to keys in the following way: + + // private $property => "\0Classname\0property" + // protected $property => "\0*\0property" + // public $property => "property" + + if (preg_match('/^\0.+\0(.+)$/', $key, $matches)) + { + $key = $matches[1]; + } + + $array[$key] = $value; + } + + return $array; + } + + public function __construct($object) + { + $this->object = $object; + $this->type = get_class($object); + + parent::__construct(self::toArray($object)); + } + + protected function getType() + { + return 'object('.$this->type.')'; + } + + public function is(LimeTesterInterface $expected) + { + // allow comparison with strings if object implements __toString() + if ($expected instanceof LimeTesterString && method_exists($this->object, '__toString')) + { + if ($expected->value != (string)$this->object) + { + throw new LimeAssertionFailedException($this, $expected); + } + } + else + { + // don't compare twice to allow for cyclic dependencies + if (in_array(array($this->value, $expected->value), self::$equal, true) || in_array(array($expected->value, $this->value), self::$equal, true)) + { + return; + } + + self::$equal[] = array($this->value, $expected->value); + + // don't compare objects if they are identical + // this helps to avoid the error "maximum function nesting level reached" + // CAUTION: this conditional clause is not tested + if (!$expected instanceof self || $this->object !== $expected->object) + { + parent::is($expected); + } + } + } + + public function isnt(LimeTesterInterface $expected) + { + // don't compare twice to allow for cyclic dependencies + if (in_array(array($this->value, $expected->value), self::$unequal, true) || in_array(array($expected->value, $this->value), self::$unequal, true)) + { + return; + } + + self::$unequal[] = array($this->value, $expected->value); + + parent::isnt($expected); + } + + public function same(LimeTesterInterface $expected) + { + if (!$expected instanceof self || $this->object !== $expected->object) + { + throw new LimeAssertionFailedException($this, $expected); + } + } + + public function isntSame(LimeTesterInterface $expected) + { + if ($expected instanceof self && $this->object === $expected->object) + { + throw new LimeAssertionFailedException($this, $expected); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/tester/LimeTesterResource.php b/tests/lib/vendor/lime/tester/LimeTesterResource.php new file mode 100644 index 000000000000..6f560ef38ffd --- /dev/null +++ b/tests/lib/vendor/lime/tester/LimeTesterResource.php @@ -0,0 +1,48 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeTesterResource extends LimeTester +{ + protected + $type = 'resource'; + + public function is(LimeTesterInterface $expected) + { + if ($this->value != $expected->value) + { + throw new LimeAssertionFailedException($this, $expected); + } + } + + public function isnt(LimeTesterInterface $expected) + { + if ($this->value == $expected->value) + { + throw new LimeAssertionFailedException($this, $expected); + } + } + + public function same(LimeTesterInterface $expected) + { + $this->is($expected); + } + + public function isntSame(LimeTesterInterface $expected) + { + $this->isnt($expected); + } + + public function __toString() + { + return sprintf('resource(%s) of type (%s)', (integer)$this->value, get_resource_type($this->value)); + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/tester/LimeTesterScalar.php b/tests/lib/vendor/lime/tester/LimeTesterScalar.php new file mode 100644 index 000000000000..c64cbb6e805e --- /dev/null +++ b/tests/lib/vendor/lime/tester/LimeTesterScalar.php @@ -0,0 +1,116 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeTesterScalar extends LimeTester +{ + protected + $type = 'scalar'; + + public function __construct($value) + { + $this->type = gettype($value); + + parent::__construct($value); + } + + public function __toString() + { + return var_export($this->value, true); + } + + private function equals(LimeTesterInterface $other) + { + $exp1 = $this->value; + $exp2 = $other->value; + + if (is_scalar($exp2) || is_null($exp2)) + { + // always compare as strings to avoid strange behaviour + // otherwise 0 == 'Foobar' + if (is_string($exp1) || is_string($exp2)) + { + $exp1 = (string)$exp1; + $exp2 = (string)$exp2; + } + + return $exp1 == $exp2; + } + else + { + return false; + } + } + + public function is(LimeTesterInterface $expected) + { + if (!$this->equals($expected)) + { + throw new LimeAssertionFailedException($this, $expected); + } + } + + public function same(LimeTesterInterface $expected) + { + if ($this->value !== $expected->value) + { + throw new LimeAssertionFailedException($this, $expected); + } + } + + public function isnt(LimeTesterInterface $expected) + { + if ($this->equals($expected)) + { + throw new LimeAssertionFailedException($this, $expected); + } + } + + public function isntSame(LimeTesterInterface $expected) + { + if ($this->value === $expected->value) + { + throw new LimeAssertionFailedException($this, $expected); + } + } + + public function greaterThan(LimeTesterInterface $expected) + { + if ($this->value <= $expected->value) + { + throw new LimeAssertionFailedException($this, $expected); + } + } + + public function greaterThanEqual(LimeTesterInterface $expected) + { + if ($this->value < $expected->value) + { + throw new LimeAssertionFailedException($this, $expected); + } + } + + public function lessThanEqual(LimeTesterInterface $expected) + { + if ($this->value > $expected->value) + { + throw new LimeAssertionFailedException($this, $expected); + } + } + + public function lessThan(LimeTesterInterface $expected) + { + if ($this->value >= $expected->value) + { + throw new LimeAssertionFailedException($this, $expected); + } + } +} \ No newline at end of file diff --git a/tests/lib/vendor/lime/tester/LimeTesterString.php b/tests/lib/vendor/lime/tester/LimeTesterString.php new file mode 100644 index 000000000000..d38204d2a9a4 --- /dev/null +++ b/tests/lib/vendor/lime/tester/LimeTesterString.php @@ -0,0 +1,51 @@ + + * (c) Bernhard Schussek + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +class LimeTesterString extends LimeTesterScalar +{ + protected + $type = 'string'; + + public function __toString() + { + return "'".$this->value."'"; + } + + public function is(LimeTesterInterface $expected) + { + // allow comparison with objects that implement __toString() + if ($expected instanceof LimeTesterObject) + { + $expected->is($this); + } + else + { + parent::is($expected); + } + } + + public function like(LimeTesterInterface $expected) + { + if (!preg_match($expected->value, $this->value)) + { + throw new LimeAssertionFailedException($this, $expected); + } + } + + public function unlike(LimeTesterInterface $expected) + { + if (preg_match($expected->value, $this->value)) + { + throw new LimeAssertionFailedException($this, $expected); + } + } +} \ No newline at end of file diff --git a/tests/unit/Symfony/Components/DependencyInjection/BuilderTest.php b/tests/unit/Symfony/Components/DependencyInjection/BuilderTest.php new file mode 100644 index 000000000000..085c21134ef6 --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/BuilderTest.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Builder; +use Symfony\Components\DependencyInjection\BuilderConfiguration; +use Symfony\Components\DependencyInjection\Definition; +use Symfony\Components\DependencyInjection\Reference; + +$fixturesPath = __DIR__.'/../../../../fixtures/Symfony/Components/DependencyInjection/'; + +$t = new LimeTest(55); + +// ->setDefinitions() ->addDefinitions() ->getDefinitions() ->setDefinition() ->getDefinition() ->hasDefinition() +$t->diag('->setDefinitions() ->addDefinitions() ->getDefinitions() ->setDefinition() ->getDefinition() ->hasDefinition()'); +$builder = new Builder(); +$definitions = array( + 'foo' => new Definition('FooClass'), + 'bar' => new Definition('BarClass'), +); +$builder->setDefinitions($definitions); +$t->is($builder->getDefinitions(), $definitions, '->setDefinitions() sets the service definitions'); +$t->ok($builder->hasDefinition('foo'), '->hasDefinition() returns true if a service definition exists'); +$t->ok(!$builder->hasDefinition('foobar'), '->hasDefinition() returns false if a service definition does not exist'); + +$builder->setDefinition('foobar', $foo = new Definition('FooBarClass')); +$t->is($builder->getDefinition('foobar'), $foo, '->getDefinition() returns a service definition if defined'); +$t->ok($builder->setDefinition('foobar', $foo = new Definition('FooBarClass')) === $foo, '->setDefinition() implements a fuild interface by returning the service reference'); + +$builder->addDefinitions($defs = array('foobar' => new Definition('FooBarClass'))); +$t->is($builder->getDefinitions(), array_merge($definitions, $defs), '->addDefinitions() adds the service definitions'); + +try +{ + $builder->getDefinition('baz'); + $t->fail('->getDefinition() throws an InvalidArgumentException if the service definition does not exist'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->getDefinition() throws an InvalidArgumentException if the service definition does not exist'); +} + +// ->register() +$t->diag('->register()'); +$builder = new Builder(); +$builder->register('foo', 'FooClass'); +$t->ok($builder->hasDefinition('foo'), '->register() registers a new service definition'); +$t->ok($builder->getDefinition('foo') instanceof Definition, '->register() returns the newly created Definition instance'); + +// ->hasService() +$t->diag('->hasService()'); +$builder = new Builder(); +$t->ok(!$builder->hasService('foo'), '->hasService() returns false if the service does not exist'); +$builder->register('foo', 'FooClass'); +$t->ok($builder->hasService('foo'), '->hasService() returns true if a service definition exists'); +$builder->bar = new stdClass(); +$t->ok($builder->hasService('bar'), '->hasService() returns true if a service exists'); + +// ->getService() +$t->diag('->getService()'); +$builder = new Builder(); +try +{ + $builder->getService('foo'); + $t->fail('->getService() throws an InvalidArgumentException if the service does not exist'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->getService() throws an InvalidArgumentException if the service does not exist'); +} +$builder->register('foo', 'stdClass'); +$t->ok(is_object($builder->getService('foo')), '->getService() returns the service definition associated with the id'); +$builder->bar = $bar = new stdClass(); +$t->is($builder->getService('bar'), $bar, '->getService() returns the service associated with the id'); +$builder->register('bar', 'stdClass'); +$t->is($builder->getService('bar'), $bar, '->getService() returns the service associated with the id even if a definition has been defined'); + +$builder->register('baz', 'stdClass')->setArguments(array(new Reference('baz'))); +try +{ + @$builder->getService('baz'); + $t->fail('->getService() throws a LogicException if the service has a circular reference to itself'); +} +catch (LogicException $e) +{ + $t->pass('->getService() throws a LogicException if the service has a circular reference to itself'); +} + +$builder->register('foobar', 'stdClass')->setShared(true); +$t->ok($builder->getService('bar') === $builder->getService('bar'), '->getService() always returns the same instance if the service is shared'); + +// ->getServiceIds() +$t->diag('->getServiceIds()'); +$builder = new Builder(); +$builder->register('foo', 'stdClass'); +$builder->bar = $bar = new stdClass(); +$builder->register('bar', 'stdClass'); +$t->is($builder->getServiceIds(), array('foo', 'bar', 'service_container'), '->getServiceIds() returns all defined service ids'); + +// ->setAlias() +$t->diag('->setAlias()'); +$builder = new Builder(); +$builder->register('foo', 'stdClass'); +$builder->setAlias('bar', 'foo'); +$t->ok($builder->hasService('bar'), '->setAlias() defines a new service'); +$t->ok($builder->getService('bar') === $builder->getService('foo'), '->setAlias() creates a service that is an alias to another one'); + +// ->getAliases() +$t->diag('->getAliases()'); +$builder = new Builder(); +$builder->setAlias('bar', 'foo'); +$builder->setAlias('foobar', 'foo'); +$t->is($builder->getAliases(), array('bar' => 'foo', 'foobar' => 'foo'), '->getAliases() returns all service aliases'); +$builder->register('bar', 'stdClass'); +$t->is($builder->getAliases(), array('foobar' => 'foo'), '->getAliases() does not return aliased services that have been overridden'); +$builder->setService('foobar', 'stdClass'); +$t->is($builder->getAliases(), array(), '->getAliases() does not return aliased services that have been overridden'); + +// ->createService() # file +$t->diag('->createService() # file'); +$builder = new Builder(); +$builder->register('foo1', 'FooClass')->setFile($fixturesPath.'/includes/foo.php'); +$t->ok($builder->getService('foo1'), '->createService() requires the file defined by the service definition'); +$builder->register('foo2', 'FooClass')->setFile($fixturesPath.'/includes/%file%.php'); +$builder->setParameter('file', 'foo'); +$t->ok($builder->getService('foo2'), '->createService() replaces parameters in the file provided by the service definition'); + +// ->createService() # class +$t->diag('->createService() # class'); +$builder = new Builder(); +$builder->register('foo1', '%class%'); +$builder->setParameter('class', 'stdClass'); +$t->ok($builder->getService('foo1') instanceof stdClass, '->createService() replaces parameters in the class provided by the service definition'); + +// ->createService() # arguments +$t->diag('->createService() # arguments'); +$builder = new Builder(); +$builder->register('bar', 'stdClass'); +$builder->register('foo1', 'FooClass')->addArgument(array('foo' => '%value%', '%value%' => 'foo', new Reference('bar'))); +$builder->setParameter('value', 'bar'); +$t->is($builder->getService('foo1')->arguments, array('foo' => 'bar', 'bar' => 'foo', $builder->getService('bar')), '->createService() replaces parameters and service references in the arguments provided by the service definition'); + +// ->createService() # constructor +$t->diag('->createService() # constructor'); +$builder = new Builder(); +$builder->register('bar', 'stdClass'); +$builder->register('foo1', 'FooClass')->setConstructor('getInstance')->addArgument(array('foo' => '%value%', '%value%' => 'foo', new Reference('bar'))); +$builder->setParameter('value', 'bar'); +$t->ok($builder->getService('foo1')->called, '->createService() calls the constructor to create the service instance'); +$t->is($builder->getService('foo1')->arguments, array('foo' => 'bar', 'bar' => 'foo', $builder->getService('bar')), '->createService() passes the arguments to the constructor'); + +// ->createService() # method calls +$t->diag('->createService() # method calls'); +$builder = new Builder(); +$builder->register('bar', 'stdClass'); +$builder->register('foo1', 'FooClass')->addMethodCall('setBar', array(array('%value%', new Reference('bar')))); +$builder->setParameter('value', 'bar'); +$t->is($builder->getService('foo1')->bar, array('bar', $builder->getService('bar')), '->createService() replaces the values in the method calls arguments'); + +// ->createService() # configurator +require_once $fixturesPath.'/includes/classes.php'; +$t->diag('->createService() # configurator'); +$builder = new Builder(); +$builder->register('foo1', 'FooClass')->setConfigurator('sc_configure'); +$t->ok($builder->getService('foo1')->configured, '->createService() calls the configurator'); + +$builder->register('foo2', 'FooClass')->setConfigurator(array('%class%', 'configureStatic')); +$builder->setParameter('class', 'BazClass'); +$t->ok($builder->getService('foo2')->configured, '->createService() calls the configurator'); + +$builder->register('baz', 'BazClass'); +$builder->register('foo3', 'FooClass')->setConfigurator(array(new Reference('baz'), 'configure')); +$t->ok($builder->getService('foo3')->configured, '->createService() calls the configurator'); + +$builder->register('foo4', 'FooClass')->setConfigurator('foo'); +try +{ + $builder->getService('foo4'); + $t->fail('->createService() throws an InvalidArgumentException if the configure callable is not a valid callable'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->createService() throws an InvalidArgumentException if the configure callable is not a valid callable'); +} + +// ::resolveValue() +$t->diag('::resolveValue()'); +$t->is(Builder::resolveValue('foo', array()), 'foo', '->resolveValue() returns its argument unmodified if no placeholders are found'); +$t->is(Builder::resolveValue('I\'m a %foo%', array('foo' => 'bar')), 'I\'m a bar', '->resolveValue() replaces placeholders by their values'); +$t->ok(Builder::resolveValue('%foo%', array('foo' => true)) === true, '->resolveValue() replaces arguments that are just a placeholder by their value without casting them to strings'); + +$t->is(Builder::resolveValue(array('%foo%' => '%foo%'), array('foo' => 'bar')), array('bar' => 'bar'), '->resolveValue() replaces placeholders in keys and values of arrays'); + +$t->is(Builder::resolveValue(array('%foo%' => array('%foo%' => array('%foo%' => '%foo%'))), array('foo' => 'bar')), array('bar' => array('bar' => array('bar' => 'bar'))), '->resolveValue() replaces placeholders in nested arrays'); + +$t->is(Builder::resolveValue('I\'m a %%foo%%', array('foo' => 'bar')), 'I\'m a %foo%', '->resolveValue() supports % escaping by doubling it'); +$t->is(Builder::resolveValue('I\'m a %foo% %%foo %foo%', array('foo' => 'bar')), 'I\'m a bar %foo bar', '->resolveValue() supports % escaping by doubling it'); + +try +{ + Builder::resolveValue('%foobar%', array()); + $t->fail('->resolveValue() throws a RuntimeException if a placeholder references a non-existant parameter'); +} +catch (RuntimeException $e) +{ + $t->pass('->resolveValue() throws a RuntimeException if a placeholder references a non-existant parameter'); +} + +try +{ + Builder::resolveValue('foo %foobar% bar', array()); + $t->fail('->resolveValue() throws a RuntimeException if a placeholder references a non-existant parameter'); +} +catch (RuntimeException $e) +{ + $t->pass('->resolveValue() throws a RuntimeException if a placeholder references a non-existant parameter'); +} + +// ->resolveServices() +$t->diag('->resolveServices()'); +$builder = new Builder(); +$builder->register('foo', 'FooClass'); +$t->is($builder->resolveServices(new Reference('foo')), $builder->getService('foo'), '->resolveServices() resolves service references to service instances'); +$t->is($builder->resolveServices(array('foo' => array('foo', new Reference('foo')))), array('foo' => array('foo', $builder->getService('foo'))), '->resolveServices() resolves service references to service instances in nested arrays'); + +// ->merge() +$t->diag('->merge()'); +$container = new Builder(); +$container->merge(null); +$t->is($container->getParameters(), array(), '->merge() accepts null as an argument'); +$t->is($container->getDefinitions(), array(), '->merge() accepts null as an argument'); + +$container = new Builder(array('bar' => 'foo')); +$config = new BuilderConfiguration(); +$config->setParameters(array('foo' => 'bar')); +$container->merge($config); +$t->is($container->getParameters(), array('bar' => 'foo', 'foo' => 'bar'), '->merge() merges current parameters with the loaded ones'); + +$container = new Builder(array('bar' => 'foo', 'foo' => 'baz')); +$config = new BuilderConfiguration(); +$config->setParameters(array('foo' => 'bar')); +$container->merge($config); +$t->is($container->getParameters(), array('bar' => 'foo', 'foo' => 'baz'), '->merge() does not change the already defined parameters'); + +$container = new Builder(array('bar' => 'foo')); +$config = new BuilderConfiguration(); +$config->setParameters(array('foo' => '%bar%')); +$container->merge($config); +$t->is($container->getParameters(), array('bar' => 'foo', 'foo' => 'foo'), '->merge() evaluates the values of the parameters towards already defined ones'); + +$container = new Builder(array('bar' => 'foo')); +$config = new BuilderConfiguration(); +$config->setParameters(array('foo' => '%bar%', 'baz' => '%foo%')); +$container->merge($config); +$t->is($container->getParameters(), array('bar' => 'foo', 'foo' => 'foo', 'baz' => 'foo'), '->merge() evaluates the values of the parameters towards already defined ones'); + +$container = new Builder(); +$container->register('foo', 'FooClass'); +$container->register('bar', 'BarClass'); +$config = new BuilderConfiguration(); +$config->setDefinition('baz', new Definition('BazClass')); +$config->setAlias('alias_for_foo', 'foo'); +$container->merge($config); +$t->is(array_keys($container->getDefinitions()), array('foo', 'bar', 'baz'), '->load() merges definitions already defined ones'); +$t->is($container->getAliases(), array('alias_for_foo' => 'foo'), '->merge() registers defined aliases'); + +$container = new Builder(); +$container->register('foo', 'FooClass'); +$config->setDefinition('foo', new Definition('BazClass')); +$container->merge($config); +$t->is($container->getDefinition('foo')->getClass(), 'BazClass', '->merge() overrides already defined services'); diff --git a/tests/unit/Symfony/Components/DependencyInjection/ContainerTest.php b/tests/unit/Symfony/Components/DependencyInjection/ContainerTest.php new file mode 100644 index 000000000000..34e660d10e23 --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/ContainerTest.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Container; + +$fixturesPath = __DIR__.'/../../../../fixtures/Symfony/Components/DependencyInjection/'; + +$t = new LimeTest(41); + +// __construct() +$t->diag('__construct()'); +$sc = new Container(); +$t->is(spl_object_hash($sc->getService('service_container')), spl_object_hash($sc), '__construct() automatically registers itself as a service'); + +$sc = new Container(array('foo' => 'bar')); +$t->is($sc->getParameters(), array('foo' => 'bar'), '__construct() takes an array of parameters as its first argument'); + +// ->setParameters() ->getParameters() +$t->diag('->setParameters() ->getParameters()'); + +$sc = new Container(); +$t->is($sc->getParameters(), array(), '->getParameters() returns an empty array if no parameter has been defined'); + +$sc->setParameters(array('foo' => 'bar')); +$t->is($sc->getParameters(), array('foo' => 'bar'), '->setParameters() sets the parameters'); + +$sc->setParameters(array('bar' => 'foo')); +$t->is($sc->getParameters(), array('bar' => 'foo'), '->setParameters() overrides the previous defined parameters'); + +$sc->setParameters(array('Bar' => 'foo')); +$t->is($sc->getParameters(), array('bar' => 'foo'), '->setParameters() converts the key to lowercase'); + +// ->setParameter() ->getParameter() +$t->diag('->setParameter() ->getParameter() '); + +$sc = new Container(array('foo' => 'bar')); +$sc->setParameter('bar', 'foo'); +$t->is($sc->getParameter('bar'), 'foo', '->setParameter() sets the value of a new parameter'); +$t->is($sc['bar'], 'foo', '->offsetGet() gets the value of a parameter'); + +$sc['bar1'] = 'foo1'; +$t->is($sc['bar1'], 'foo1', '->offsetset() sets the value of a parameter'); + +unset($sc['bar1']); +$t->ok(!isset($sc['bar1']), '->offsetUnset() removes a parameter'); + +$sc->setParameter('foo', 'baz'); +$t->is($sc->getParameter('foo'), 'baz', '->setParameter() overrides previously set parameter'); + +$sc->setParameter('Foo', 'baz1'); +$t->is($sc->getParameter('foo'), 'baz1', '->setParameter() converts the key to lowercase'); +$t->is($sc->getParameter('FOO'), 'baz1', '->getParameter() converts the key to lowercase'); +$t->is($sc['FOO'], 'baz1', '->offsetGet() converts the key to lowercase'); + +try +{ + $sc->getParameter('baba'); + $t->fail('->getParameter() thrown an \InvalidArgumentException if the key does not exist'); +} +catch (\InvalidArgumentException $e) +{ + $t->pass('->getParameter() thrown an \InvalidArgumentException if the key does not exist'); +} + +try +{ + $sc['baba']; + $t->fail('->offsetGet() thrown an \InvalidArgumentException if the key does not exist'); +} +catch (\InvalidArgumentException $e) +{ + $t->pass('->offsetGet() thrown an \InvalidArgumentException if the key does not exist'); +} + +// ->hasParameter() +$t->diag('->hasParameter()'); +$sc = new Container(array('foo' => 'bar')); +$t->ok($sc->hasParameter('foo'), '->hasParameter() returns true if a parameter is defined'); +$t->ok($sc->hasParameter('Foo'), '->hasParameter() converts the key to lowercase'); +$t->ok(isset($sc['Foo']), '->offsetExists() converts the key to lowercase'); +$t->ok(!$sc->hasParameter('bar'), '->hasParameter() returns false if a parameter is not defined'); +$t->ok(isset($sc['foo']), '->offsetExists() returns true if a parameter is defined'); +$t->ok(!isset($sc['bar']), '->offsetExists() returns false if a parameter is not defined'); + +// ->addParameters() +$t->diag('->addParameters()'); +$sc = new Container(array('foo' => 'bar')); +$sc->addParameters(array('bar' => 'foo')); +$t->is($sc->getParameters(), array('foo' => 'bar', 'bar' => 'foo'), '->addParameters() adds parameters to the existing ones'); +$sc->addParameters(array('Bar' => 'fooz')); +$t->is($sc->getParameters(), array('foo' => 'bar', 'bar' => 'fooz'), '->addParameters() converts keys to lowercase'); + +// ->setService() ->hasService() ->getService() +$t->diag('->setService() ->hasService() ->getService()'); +$sc = new Container(); +$sc->setService('foo', $obj = new stdClass()); +$t->is(spl_object_hash($sc->getService('foo')), spl_object_hash($obj), '->setService() registers a service under a key name'); + +$sc->foo1 = $obj1 = new stdClass(); +$t->is(spl_object_hash($sc->foo1), spl_object_hash($obj1), '->__set() sets a service'); + +$t->is(spl_object_hash($sc->foo), spl_object_hash($obj), '->__get() gets a service by name'); +$t->ok($sc->hasService('foo'), '->hasService() returns true if the service is defined'); +$t->ok(isset($sc->foo), '->__isset() returns true if the service is defined'); +$t->ok(!$sc->hasService('bar'), '->hasService() returns false if the service is not defined'); +$t->ok(!isset($sc->bar), '->__isset() returns false if the service is not defined'); + +// ->getServiceIds() +$t->diag('->getServiceIds()'); +$sc = new Container(); +$sc->setService('foo', $obj = new stdClass()); +$sc->setService('bar', $obj = new stdClass()); +$t->is($sc->getServiceIds(), array('service_container', 'foo', 'bar'), '->getServiceIds() returns all defined service ids'); + +class ProjectServiceContainer extends Container +{ + public $__bar, $__foo_bar, $__foo_baz; + + public function __construct() + { + parent::__construct(); + + $this->__bar = new stdClass(); + $this->__foo_bar = new stdClass(); + $this->__foo_baz = new stdClass(); + } + + protected function getBarService() + { + return $this->__bar; + } + + protected function getFooBarService() + { + return $this->__foo_bar; + } + + protected function getFoo_BazService() + { + return $this->__foo_baz; + } +} + +$sc = new ProjectServiceContainer(); +$t->is(spl_object_hash($sc->getService('bar')), spl_object_hash($sc->__bar), '->getService() looks for a getXXXService() method'); +$t->ok($sc->hasService('bar'), '->hasService() returns true if the service has been defined as a getXXXService() method'); + +$sc->setService('bar', $bar = new stdClass()); +$t->is(spl_object_hash($sc->getService('bar')), spl_object_hash($bar), '->getService() prefers to return a service defined with setService() than one defined with a getXXXService() method'); + +try +{ + $sc->getService('baba'); + $t->fail('->getService() thrown an \InvalidArgumentException if the service does not exist'); +} +catch (\InvalidArgumentException $e) +{ + $t->pass('->getService() thrown an \InvalidArgumentException if the service does not exist'); +} + +try +{ + $sc->baba; + $t->fail('->__get() thrown an \InvalidArgumentException if the service does not exist'); +} +catch (\InvalidArgumentException $e) +{ + $t->pass('->__get() thrown an \InvalidArgumentException if the service does not exist'); +} + +try +{ + unset($sc->baba); + $t->fail('->__unset() thrown an LogicException if you try to remove a service'); +} +catch (LogicException $e) +{ + $t->pass('->__unset() thrown an LogicException if you try to remove a service'); +} + +$t->is(spl_object_hash($sc->getService('foo_bar')), spl_object_hash($sc->__foo_bar), '->getService() camelizes the service id when looking for a method'); +$t->is(spl_object_hash($sc->getService('foo.baz')), spl_object_hash($sc->__foo_baz), '->getService() camelizes the service id when looking for a method'); + +// Iterator +$t->diag('implements Iterator'); +$sc = new ProjectServiceContainer(); +$sc->setService('foo', $foo = new stdClass()); +$services = array(); +foreach ($sc as $id => $service) +{ + $services[$id] = spl_object_hash($service); +} +$t->is($services, array( + 'service_container' => spl_object_hash($sc), + 'bar' => spl_object_hash($sc->__bar), + 'foo_bar' => spl_object_hash($sc->__foo_bar), + 'foo.baz' => spl_object_hash($sc->__foo_baz), + 'foo' => spl_object_hash($foo)), +'Container implements the Iterator interface'); diff --git a/tests/unit/Symfony/Components/DependencyInjection/CrossCheckTest.php b/tests/unit/Symfony/Components/DependencyInjection/CrossCheckTest.php new file mode 100644 index 000000000000..4c55dc08afc3 --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/CrossCheckTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Builder; + +$fixturesPath = realpath(__DIR__.'/../../../../fixtures/Symfony/Components/DependencyInjection/'); + +require_once $fixturesPath.'/includes/classes.php'; +require_once $fixturesPath.'/includes/foo.php'; + +$t = new LimeTest(30); + +// cross-check loaders/dumpers +$t->diag('cross-check loaders/dumpers'); + +$fixtures = array( + 'services1.xml' => 'xml', + 'services2.xml' => 'xml', + 'services6.xml' => 'xml', + 'services8.xml' => 'xml', + 'services9.xml' => 'xml', + + 'services1.yml' => 'yaml', + 'services2.yml' => 'yaml', + 'services6.yml' => 'yaml', + 'services8.yml' => 'yaml', + 'services9.yml' => 'yaml', +); + +foreach ($fixtures as $fixture => $type) +{ + $loaderClass = 'Symfony\\Components\\DependencyInjection\\Loader\\'.ucfirst($type).'FileLoader'; + $dumperClass = 'Symfony\\Components\\DependencyInjection\\Dumper\\'.ucfirst($type).'Dumper'; + + $container1 = new Builder(); + $loader1 = new $loaderClass($container1); + $loader1->load($fixturesPath.'/'.$type.'/'.$fixture); + $container1->setParameter('path', $fixturesPath.'/includes'); + + $dumper = new $dumperClass($container1); + $tmp = tempnam('sf_service_container', 'sf'); + file_put_contents($tmp, $dumper->dump()); + + $container2 = new Builder(); + $loader2 = new $loaderClass($container2); + $loader2->load($tmp); + $container2->setParameter('path', $fixturesPath.'/includes'); + + unlink($tmp); + + $t->is(serialize($container1), serialize($container2), 'loading a dump from a previously loaded container returns the same container'); + + $t->is($container1->getParameters(), $container2->getParameters(), '->getParameters() returns the same value for both containers'); + + $services1 = array(); + foreach ($container1 as $id => $service) + { + $services1[$id] = serialize($service); + } + $services2 = array(); + foreach ($container2 as $id => $service) + { + $services2[$id] = serialize($service); + } + + unset($services1['service_container'], $services2['service_container']); + + $t->is($services1, $services2, 'Iterator on the containers returns the same services'); +} diff --git a/tests/unit/Symfony/Components/DependencyInjection/DefinitionTest.php b/tests/unit/Symfony/Components/DependencyInjection/DefinitionTest.php new file mode 100644 index 000000000000..d9da8643755b --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/DefinitionTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Definition; + +$t = new LimeTest(21); + +// __construct() +$t->diag('__construct()'); + +$def = new Definition('stdClass'); +$t->is($def->getClass(), 'stdClass', '__construct() takes the class name as its first argument'); + +$def = new Definition('stdClass', array('foo')); +$t->is($def->getArguments(), array('foo'), '__construct() takes an optional array of arguments as its second argument'); + +// ->setConstructor() ->getConstructor() +$t->diag('->setConstructor() ->getConstructor()'); +$def = new Definition('stdClass'); +$t->is(spl_object_hash($def->setConstructor('foo')), spl_object_hash($def), '->setConstructor() implements a fluent interface'); +$t->is($def->getConstructor(), 'foo', '->getConstructor() returns the constructor name'); + +// ->setClass() ->getClass() +$t->diag('->setClass() ->getClass()'); +$def = new Definition('stdClass'); +$t->is(spl_object_hash($def->setClass('foo')), spl_object_hash($def), '->setClass() implements a fluent interface'); +$t->is($def->getClass(), 'foo', '->getClass() returns the class name'); + +// ->setArguments() ->getArguments() ->addArgument() +$t->diag('->setArguments() ->getArguments() ->addArgument()'); +$def = new Definition('stdClass'); +$t->is(spl_object_hash($def->setArguments(array('foo'))), spl_object_hash($def), '->setArguments() implements a fluent interface'); +$t->is($def->getArguments(), array('foo'), '->getArguments() returns the arguments'); +$t->is(spl_object_hash($def->addArgument('bar')), spl_object_hash($def), '->addArgument() implements a fluent interface'); +$t->is($def->getArguments(), array('foo', 'bar'), '->addArgument() adds an argument'); + +// ->setMethodCalls() ->getMethodCalls() ->addMethodCall() +$t->diag('->setMethodCalls() ->getMethodCalls() ->addMethodCall()'); +$def = new Definition('stdClass'); +$t->is(spl_object_hash($def->setMethodCalls(array(array('foo', array('foo'))))), spl_object_hash($def), '->setMethodCalls() implements a fluent interface'); +$t->is($def->getMethodCalls(), array(array('foo', array('foo'))), '->getMethodCalls() returns the methods to call'); +$t->is(spl_object_hash($def->addMethodCall('bar', array('bar'))), spl_object_hash($def), '->addMethodCall() implements a fluent interface'); +$t->is($def->getMethodCalls(), array(array('foo', array('foo')), array('bar', array('bar'))), '->addMethodCall() adds a method to call'); + +// ->setFile() ->getFile() +$t->diag('->setFile() ->getFile()'); +$def = new Definition('stdClass'); +$t->is(spl_object_hash($def->setFile('foo')), spl_object_hash($def), '->setFile() implements a fluent interface'); +$t->is($def->getFile(), 'foo', '->getFile() returns the file to include'); + +// ->setShared() ->isShared() +$t->diag('->setShared() ->isShared()'); +$def = new Definition('stdClass'); +$t->is($def->isShared(), true, '->isShared() returns true by default'); +$t->is(spl_object_hash($def->setShared(false)), spl_object_hash($def), '->setShared() implements a fluent interface'); +$t->is($def->isShared(), false, '->isShared() returns false if the instance must not be shared'); + +// ->setConfigurator() ->getConfigurator() +$t->diag('->setConfigurator() ->getConfigurator()'); +$def = new Definition('stdClass'); +$t->is(spl_object_hash($def->setConfigurator('foo')), spl_object_hash($def), '->setConfigurator() implements a fluent interface'); +$t->is($def->getConfigurator(), 'foo', '->getConfigurator() returns the configurator'); diff --git a/tests/unit/Symfony/Components/DependencyInjection/Dumper/DumperTest.php b/tests/unit/Symfony/Components/DependencyInjection/Dumper/DumperTest.php new file mode 100644 index 000000000000..e1b5ac51236e --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/Dumper/DumperTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Builder; +use Symfony\Components\DependencyInjection\Dumper\Dumper; + +$t = new LimeTest(1); + +class ProjectDumper extends Dumper +{ +} + +$builder = new Builder(); +$dumper = new ProjectDumper($builder); +try +{ + $dumper->dump(); + $t->fail('->dump() returns a LogicException if the dump() method has not been overriden by a children class'); +} +catch (LogicException $e) +{ + $t->pass('->dump() returns a LogicException if the dump() method has not been overriden by a children class'); +} diff --git a/tests/unit/Symfony/Components/DependencyInjection/Dumper/GraphvizDumperTest.php b/tests/unit/Symfony/Components/DependencyInjection/Dumper/GraphvizDumperTest.php new file mode 100644 index 000000000000..a46fa1e7311c --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/Dumper/GraphvizDumperTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Builder; +use Symfony\Components\DependencyInjection\Dumper\GraphvizDumper; + +$t = new LimeTest(4); + +$fixturesPath = __DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/'; + +// ->dump() +$t->diag('->dump()'); +$dumper = new GraphvizDumper($container = new Builder()); + +$t->is($dumper->dump(), file_get_contents($fixturesPath.'/graphviz/services1.dot'), '->dump() dumps an empty container as an empty dot file'); + +$container = new Builder(); +$dumper = new GraphvizDumper($container); + +$container = include $fixturesPath.'/containers/container9.php'; +$dumper = new GraphvizDumper($container); +$t->is($dumper->dump(), str_replace('%path%', __DIR__, file_get_contents($fixturesPath.'/graphviz/services9.dot')), '->dump() dumps services'); + +$container = include $fixturesPath.'/containers/container10.php'; +$dumper = new GraphvizDumper($container); +$t->is($dumper->dump(), str_replace('%path%', __DIR__, file_get_contents($fixturesPath.'/graphviz/services10.dot')), '->dump() dumps services'); + +$container = include $fixturesPath.'/containers/container10.php'; +$dumper = new GraphvizDumper($container); +$t->is($dumper->dump(array( + 'graph' => array('ratio' => 'normal'), + 'node' => array('fontsize' => 13, 'fontname' => 'Verdana', 'shape' => 'square'), + 'edge' => array('fontsize' => 12, 'fontname' => 'Verdana', 'color' => 'white', 'arrowhead' => 'closed', 'arrowsize' => 1), + 'node.instance' => array('fillcolor' => 'green', 'style' => 'empty'), + 'node.definition' => array('fillcolor' => 'grey'), + 'node.missing' => array('fillcolor' => 'red', 'style' => 'empty'), +)), str_replace('%path%', __DIR__, file_get_contents($fixturesPath.'/graphviz/services10-1.dot')), '->dump() dumps services'); diff --git a/tests/unit/Symfony/Components/DependencyInjection/Dumper/PhpDumperTest.php b/tests/unit/Symfony/Components/DependencyInjection/Dumper/PhpDumperTest.php new file mode 100644 index 000000000000..326b85c4a345 --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/Dumper/PhpDumperTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Builder; +use Symfony\Components\DependencyInjection\Dumper\PhpDumper; + +$t = new LimeTest(5); + +$fixturesPath = realpath(__DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/'); + +// ->dump() +$t->diag('->dump()'); +$dumper = new PhpDumper($container = new Builder()); + +$t->is($dumper->dump(), file_get_contents($fixturesPath.'/php/services1.php'), '->dump() dumps an empty container as an empty PHP class'); +$t->is($dumper->dump(array('class' => 'Container', 'base_class' => 'AbstractContainer')), file_get_contents($fixturesPath.'/php/services1-1.php'), '->dump() takes a class and a base_class options'); + +$container = new Builder(); +$dumper = new PhpDumper($container); + +// ->addParameters() +$t->diag('->addParameters()'); +$container = include $fixturesPath.'/containers/container8.php'; +$dumper = new PhpDumper($container); +$t->is($dumper->dump(), file_get_contents($fixturesPath.'/php/services8.php'), '->dump() dumps parameters'); + +// ->addService() +$t->diag('->addService()'); +$container = include $fixturesPath.'/containers/container9.php'; +$dumper = new PhpDumper($container); +$t->is($dumper->dump(), str_replace('%path%', $fixturesPath.'/includes', file_get_contents($fixturesPath.'/php/services9.php')), '->dump() dumps services'); + +$dumper = new PhpDumper($container = new Builder()); +$container->register('foo', 'FooClass')->addArgument(new stdClass()); +try +{ + $dumper->dump(); + $t->fail('->dump() throws a RuntimeException if the container to be dumped has reference to objects or resources'); +} +catch (RuntimeException $e) +{ + $t->pass('->dump() throws a RuntimeException if the container to be dumped has reference to objects or resources'); +} diff --git a/tests/unit/Symfony/Components/DependencyInjection/Dumper/XmlDumperTest.php b/tests/unit/Symfony/Components/DependencyInjection/Dumper/XmlDumperTest.php new file mode 100644 index 000000000000..9f49c8ee5488 --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/Dumper/XmlDumperTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Builder; +use Symfony\Components\DependencyInjection\Dumper\XmlDumper; + +$t = new LimeTest(4); + +$fixturesPath = realpath(__DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/'); + +// ->dump() +$t->diag('->dump()'); +$dumper = new XmlDumper($container = new Builder()); + +$t->is($dumper->dump(), file_get_contents($fixturesPath.'/xml/services1.xml'), '->dump() dumps an empty container as an empty XML file'); + +$container = new Builder(); +$dumper = new XmlDumper($container); + +// ->addParameters() +$t->diag('->addParameters()'); +$container = include $fixturesPath.'//containers/container8.php'; +$dumper = new XmlDumper($container); +$t->is($dumper->dump(), file_get_contents($fixturesPath.'/xml/services8.xml'), '->dump() dumps parameters'); + +// ->addService() +$t->diag('->addService()'); +$container = include $fixturesPath.'/containers/container9.php'; +$dumper = new XmlDumper($container); +$t->is($dumper->dump(), str_replace('%path%', $fixturesPath.'/includes', file_get_contents($fixturesPath.'/xml/services9.xml')), '->dump() dumps services'); + +$dumper = new XmlDumper($container = new Builder()); +$container->register('foo', 'FooClass')->addArgument(new stdClass()); +try +{ + $dumper->dump(); + $t->fail('->dump() throws a RuntimeException if the container to be dumped has reference to objects or resources'); +} +catch (RuntimeException $e) +{ + $t->pass('->dump() throws a RuntimeException if the container to be dumped has reference to objects or resources'); +} diff --git a/tests/unit/Symfony/Components/DependencyInjection/Dumper/YamlDumperTest.php b/tests/unit/Symfony/Components/DependencyInjection/Dumper/YamlDumperTest.php new file mode 100644 index 000000000000..19d5764f37ae --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/Dumper/YamlDumperTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Builder; +use Symfony\Components\DependencyInjection\Dumper\YamlDumper; + +$t = new LimeTest(4); + +$fixturesPath = realpath(__DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/'); + +// ->dump() +$t->diag('->dump()'); +$dumper = new YamlDumper($container = new Builder()); + +$t->is($dumper->dump(), file_get_contents($fixturesPath.'/yaml/services1.yml'), '->dump() dumps an empty container as an empty YAML file'); + +$container = new Builder(); +$dumper = new YamlDumper($container); + +// ->addParameters() +$t->diag('->addParameters()'); +$container = include $fixturesPath.'/containers/container8.php'; +$dumper = new YamlDumper($container); +$t->is($dumper->dump(), file_get_contents($fixturesPath.'/yaml/services8.yml'), '->dump() dumps parameters'); + +// ->addService() +$t->diag('->addService()'); +$container = include $fixturesPath.'/containers/container9.php'; +$dumper = new YamlDumper($container); +$t->is($dumper->dump(), str_replace('%path%', $fixturesPath.'/includes', file_get_contents($fixturesPath.'/yaml/services9.yml')), '->dump() dumps services'); + +$dumper = new YamlDumper($container = new Builder()); +$container->register('foo', 'FooClass')->addArgument(new stdClass()); +try +{ + $dumper->dump(); + $t->fail('->dump() throws a RuntimeException if the container to be dumped has reference to objects or resources'); +} +catch (RuntimeException $e) +{ + $t->pass('->dump() throws a RuntimeException if the container to be dumped has reference to objects or resources'); +} diff --git a/tests/unit/Symfony/Components/DependencyInjection/Loader/FileLoaderTest.php b/tests/unit/Symfony/Components/DependencyInjection/Loader/FileLoaderTest.php new file mode 100644 index 000000000000..b0e6ed5b743e --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/Loader/FileLoaderTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Builder; +use Symfony\Components\DependencyInjection\Loader\FileLoader; + +$t = new LimeTest(9); + +class ProjectLoader extends FileLoader +{ + public $paths; + + public function load($resource) + { + } + + public function getAbsolutePath($file, $currentPath = null) + { + return parent::getAbsolutePath($file, $currentPath); + } +} + +// __construct() +$t->diag('__construct()'); +$loader = new ProjectLoader(__DIR__); +$t->is($loader->paths, array(__DIR__), '__construct() takes a path as its second argument'); + +$loader = new ProjectLoader(array(__DIR__, __DIR__)); +$t->is($loader->paths, array(__DIR__, __DIR__), '__construct() takes an array of paths as its second argument'); + +// ->getAbsolutePath() +$t->diag('->getAbsolutePath()'); +$loader = new ProjectLoader(array(__DIR__.'/../../../../../bin')); +$t->is($loader->getAbsolutePath('/foo.xml'), '/foo.xml', '->getAbsolutePath() return the path unmodified if it is already an absolute path'); +$t->is($loader->getAbsolutePath('c:\\\\foo.xml'), 'c:\\\\foo.xml', '->getAbsolutePath() return the path unmodified if it is already an absolute path'); +$t->is($loader->getAbsolutePath('c:/foo.xml'), 'c:/foo.xml', '->getAbsolutePath() return the path unmodified if it is already an absolute path'); +$t->is($loader->getAbsolutePath('\\server\\foo.xml'), '\\server\\foo.xml', '->getAbsolutePath() return the path unmodified if it is already an absolute path'); + +$t->is($loader->getAbsolutePath('FileLoaderTest.php', __DIR__), __DIR__.'/FileLoaderTest.php', '->getAbsolutePath() returns an absolute filename if the file exists in the current path'); + +$t->is($loader->getAbsolutePath('prove.php', __DIR__), __DIR__.'/../../../../../bin/prove.php', '->getAbsolutePath() returns an absolute filename if the file exists in one of the paths given in the constructor'); + +$t->is($loader->getAbsolutePath('foo.xml', __DIR__), 'foo.xml', '->getAbsolutePath() returns the path unmodified if it is unable to find it in the given paths'); diff --git a/tests/unit/Symfony/Components/DependencyInjection/Loader/IniLoaderTest.php b/tests/unit/Symfony/Components/DependencyInjection/Loader/IniLoaderTest.php new file mode 100644 index 000000000000..6de3ced42512 --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/Loader/IniLoaderTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Builder; +use Symfony\Components\DependencyInjection\Loader\IniFileLoader; + +$t = new LimeTest(5); + +$fixturesPath = realpath(__DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/'); + +$loader = new IniFileLoader($fixturesPath.'/ini'); +$config = $loader->load(array('parameters.ini')); +$t->is($config->getParameters(), array('foo' => 'bar', 'bar' => '%foo%'), '->load() takes an array of file names as its first argument'); + +$loader = new IniFileLoader($fixturesPath.'/ini'); +$config = $loader->load('parameters.ini'); +$t->is($config->getParameters(), array('foo' => 'bar', 'bar' => '%foo%'), '->load() takes a single file name as its first argument'); + +$loader = new IniFileLoader($fixturesPath.'/ini'); +$config = $loader->load(array('parameters.ini', 'parameters1.ini')); +$t->is($config->getParameters(), array('foo' => 'foo', 'bar' => '%foo%', 'baz' => 'baz'), '->load() merges parameters from all given files'); + +try +{ + $loader->load('foo.ini'); + $t->fail('->load() throws an InvalidArgumentException if the loaded file does not exist'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->load() throws an InvalidArgumentException if the loaded file does not exist'); +} + +try +{ + @$loader->load('nonvalid.ini'); + $t->fail('->load() throws an InvalidArgumentException if the loaded file is not parseable'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->load() throws an InvalidArgumentException if the loaded file is not parseable'); +} diff --git a/tests/unit/Symfony/Components/DependencyInjection/Loader/LoaderExtensionTest.php b/tests/unit/Symfony/Components/DependencyInjection/Loader/LoaderExtensionTest.php new file mode 100644 index 000000000000..3f0361d4d93a --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/Loader/LoaderExtensionTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +require_once __DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/includes/ProjectExtension.php'; + +$t = new LimeTest(2); + +// ->load() +$t->diag('->load()'); +$extension = new ProjectExtension(); + +try +{ + $extension->load('foo', array()); + $t->fail('->load() throws an InvalidArgumentException if the tag does not exist'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->load() throws an InvalidArgumentException if the tag does not exist'); +} + +$config = $extension->load('bar', array('foo' => 'bar')); +$t->is($config->getParameters(), array('project.parameter.bar' => 'bar'), '->load() calls the method tied to the given tag'); diff --git a/tests/unit/Symfony/Components/DependencyInjection/Loader/LoaderTest.php b/tests/unit/Symfony/Components/DependencyInjection/Loader/LoaderTest.php new file mode 100644 index 000000000000..59df8aab0ae6 --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/Loader/LoaderTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Loader\Loader; + +require_once __DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/includes/ProjectExtension.php'; + +class ProjectLoader extends Loader +{ + public function load($resource) + { + } +} + +$t = new LimeTest(1); + +// ::registerExtension() ::getExtension() +$t->diag('::registerExtension() ::getExtension()'); +ProjectLoader::registerExtension($extension = new ProjectExtension()); +$t->ok(ProjectLoader::getExtension('project') === $extension, '::registerExtension() registers an extension'); diff --git a/tests/unit/Symfony/Components/DependencyInjection/Loader/XmlFileLoaderTest.php b/tests/unit/Symfony/Components/DependencyInjection/Loader/XmlFileLoaderTest.php new file mode 100644 index 000000000000..d23f5732b802 --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/Loader/XmlFileLoaderTest.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Builder; +use Symfony\Components\DependencyInjection\Reference; +use Symfony\Components\DependencyInjection\Definition; +use Symfony\Components\DependencyInjection\Loader\Loader; +use Symfony\Components\DependencyInjection\Loader\XmlFileLoader; + +$t = new LimeTest(44); + +$fixturesPath = realpath(__DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/'); + +require_once $fixturesPath.'/includes/ProjectExtension.php'; + +class ProjectLoader extends XmlFileLoader +{ + public function getFilesAsXml(array $files) + { + return parent::getFilesAsXml($files); + } +} + +// ->getFilesAsXml() +$t->diag('->getFilesAsXml()'); + +$loader = new ProjectLoader($fixturesPath.'/ini'); + +try +{ + $loader->getFilesAsXml(array('foo.xml')); + $t->fail('->load() throws an InvalidArgumentException if the loaded file does not exist'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->load() throws an InvalidArgumentException if the loaded file does not exist'); +} + +try +{ + $loader->getFilesAsXml(array('parameters.ini')); + $t->fail('->load() throws an InvalidArgumentException if the loaded file is not a valid XML file'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->load() throws an InvalidArgumentException if the loaded file is not a valid XML file'); +} + +$loader = new ProjectLoader($fixturesPath.'/xml'); + +try +{ + $loader->getFilesAsXml(array('nonvalid.xml')); + $t->fail('->load() throws an InvalidArgumentException if the loaded file does not validate the XSD'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->load() throws an InvalidArgumentException if the loaded file does not validate the XSD'); +} + +$xmls = $loader->getFilesAsXml(array('services1.xml')); +$t->is(count($xmls), 1, '->getFilesAsXml() returns an array of simple xml objects'); +$t->is(key($xmls), realpath($fixturesPath.'/xml/services1.xml'), '->getFilesAsXml() returns an array where the keys are absolutes paths to the original XML file'); +$t->is(get_class(current($xmls)), 'Symfony\\Components\\DependencyInjection\\SimpleXMLElement', '->getFilesAsXml() returns an array where values are SimpleXMLElement objects'); + +// ->load() # parameters +$t->diag('->load() # parameters'); +$loader = new ProjectLoader($fixturesPath.'/xml'); +$config = $loader->load(array('services2.xml')); +$t->is($config->getParameters(), array('a string', 'foo' => 'bar', 'values' => array(0, 'integer' => 4, 100 => null, 'true', true, false, 'on', 'off', 'float' => 1.3, 1000.3, 'a string', array('foo', 'bar')), 'foo_bar' => new Reference('foo_bar')), '->load() converts XML values to PHP ones'); + +$loader = new ProjectLoader($fixturesPath.'/xml'); +$config = $loader->load(array('services2.xml', 'services3.xml')); +$t->is($config->getParameters(), array('a string', 'foo' => 'foo', 'values' => array(true, false), 'foo_bar' => new Reference('foo_bar')), '->load() merges the first level of arguments when multiple files are loaded'); + +// ->load() # imports +$t->diag('->load() # imports'); +$config = $loader->load(array('services4.xml')); +$t->is($config->getParameters(), array('a string', 'foo' => 'bar', 'bar' => '%foo%', 'values' => array(true, false), 'foo_bar' => new Reference('foo_bar')), '->load() imports and merges imported files'); + +// ->load() # anonymous services +$t->diag('->load() # anonymous services'); +$config = $loader->load(array('services5.xml')); +$services = $config->getDefinitions(); +$t->is(count($services), 3, '->load() attributes unique ids to anonymous services'); +$args = $services['foo']->getArguments(); +$t->is(count($args), 1, '->load() references anonymous services as "normal" ones'); +$t->is(get_class($args[0]), 'Symfony\\Components\\DependencyInjection\\Reference', '->load() converts anonymous services to references to "normal" services'); +$t->ok(isset($services[(string) $args[0]]), '->load() makes a reference to the created ones'); +$inner = $services[(string) $args[0]]; +$t->is($inner->getClass(), 'BarClass', '->load() uses the same configuration as for the anonymous ones'); + +$args = $inner->getArguments(); +$t->is(count($args), 1, '->load() references anonymous services as "normal" ones'); +$t->is(get_class($args[0]), 'Symfony\\Components\\DependencyInjection\\Reference', '->load() converts anonymous services to references to "normal" services'); +$t->ok(isset($services[(string) $args[0]]), '->load() makes a reference to the created ones'); +$inner = $services[(string) $args[0]]; +$t->is($inner->getClass(), 'BazClass', '->load() uses the same configuration as for the anonymous ones'); + +// ->load() # services +$t->diag('->load() # services'); +$config = $loader->load(array('services6.xml')); +$services = $config->getDefinitions(); +$t->ok(isset($services['foo']), '->load() parses elements'); +$t->is(get_class($services['foo']), 'Symfony\\Components\\DependencyInjection\\Definition', '->load() converts element to Definition instances'); +$t->is($services['foo']->getClass(), 'FooClass', '->load() parses the class attribute'); +$t->ok($services['shared']->isShared(), '->load() parses the shared attribute'); +$t->ok(!$services['non_shared']->isShared(), '->load() parses the shared attribute'); +$t->is($services['constructor']->getConstructor(), 'getInstance', '->load() parses the constructor attribute'); +$t->is($services['file']->getFile(), '%path%/foo.php', '->load() parses the file tag'); +$t->is($services['arguments']->getArguments(), array('foo', new Reference('foo'), array(true, false)), '->load() parses the argument tags'); +$t->is($services['configurator1']->getConfigurator(), 'sc_configure', '->load() parses the configurator tag'); +$t->is($services['configurator2']->getConfigurator(), array(new Reference('baz'), 'configure'), '->load() parses the configurator tag'); +$t->is($services['configurator3']->getConfigurator(), array('BazClass', 'configureStatic'), '->load() parses the configurator tag'); +$t->is($services['method_call1']->getMethodCalls(), array(array('setBar', array())), '->load() parses the method_call tag'); +$t->is($services['method_call2']->getMethodCalls(), array(array('setBar', array('foo', new Reference('foo'), array(true, false)))), '->load() parses the method_call tag'); +$aliases = $config->getAliases(); +$t->ok(isset($aliases['alias_for_foo']), '->load() parses elements'); +$t->is($aliases['alias_for_foo'], 'foo', '->load() parses aliases'); + +$config = $loader->load(array('services6.xml', 'services7.xml')); +$services = $config->getDefinitions(); +$t->is($services['foo']->getClass(), 'BarClass', '->load() merges the services when multiple files are loaded'); + +// ::convertDomElementToArray() +$t->diag('::convertDomElementToArray()'); +$doc = new DOMDocument("1.0"); +$doc->loadXML('bar'); +$t->is(ProjectLoader::convertDomElementToArray($doc->documentElement), 'bar', '::convertDomElementToArray() converts a \DomElement to an array'); + +$doc = new DOMDocument("1.0"); +$doc->loadXML(''); +$t->is(ProjectLoader::convertDomElementToArray($doc->documentElement), array('foo' => 'bar'), '::convertDomElementToArray() converts a \DomElement to an array'); + +$doc = new DOMDocument("1.0"); +$doc->loadXML('bar'); +$t->is(ProjectLoader::convertDomElementToArray($doc->documentElement), array('foo' => 'bar'), '::convertDomElementToArray() converts a \DomElement to an array'); + +$doc = new DOMDocument("1.0"); +$doc->loadXML('barbar'); +$t->is(ProjectLoader::convertDomElementToArray($doc->documentElement), array('foo' => array('value' => 'bar', 'foo' => 'bar')), '::convertDomElementToArray() converts a \DomElement to an array'); + +$doc = new DOMDocument("1.0"); +$doc->loadXML(''); +$t->is(ProjectLoader::convertDomElementToArray($doc->documentElement), array('foo' => null), '::convertDomElementToArray() converts a \DomElement to an array'); + +$doc = new DOMDocument("1.0"); +$doc->loadXML(''); +$t->is(ProjectLoader::convertDomElementToArray($doc->documentElement), array('foo' => null), '::convertDomElementToArray() converts a \DomElement to an array'); + +// extensions +$t->diag('extensions'); +Loader::registerExtension(new ProjectExtension()); +$loader = new ProjectLoader($fixturesPath.'/xml'); + +$config = $loader->load('services10.xml'); +$services = $config->getDefinitions(); +$parameters = $config->getParameters(); +$t->ok(isset($services['project.service.bar']), '->load() parses extension elements'); +$t->ok(isset($parameters['project.parameter.bar']), '->load() parses extension elements'); + +try +{ + $config = $loader->load('services11.xml'); + $t->fail('->load() throws an InvalidArgumentException if the tag is not valid'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->load() throws an InvalidArgumentException if the tag is not valid'); +} + +try +{ + $config = $loader->load('services12.xml'); + $t->fail('->load() throws an InvalidArgumentException if an extension is not loaded'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->load() throws an InvalidArgumentException if an extension is not loaded'); +} diff --git a/tests/unit/Symfony/Components/DependencyInjection/Loader/YamlFileLoaderTest.php b/tests/unit/Symfony/Components/DependencyInjection/Loader/YamlFileLoaderTest.php new file mode 100644 index 000000000000..7455e2b6e0da --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/Loader/YamlFileLoaderTest.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Builder; +use Symfony\Components\DependencyInjection\Reference; +use Symfony\Components\DependencyInjection\Definition; +use Symfony\Components\DependencyInjection\Loader\Loader; +use Symfony\Components\DependencyInjection\Loader\YamlFileLoader; + +$t = new LimeTest(29); + +$fixturesPath = realpath(__DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/'); + +require_once $fixturesPath.'/includes/ProjectExtension.php'; + +class ProjectLoader extends YamlFileLoader +{ + public function getFilesAsArray(array $files) + { + return parent::getFilesAsArray($files); + } +} + +// ->getFilesAsArray() +$t->diag('->getFilesAsArray()'); + +$loader = new ProjectLoader($fixturesPath.'/ini'); + +try +{ + $loader->getFilesAsArray(array('foo.yml')); + $t->fail('->load() throws an InvalidArgumentException if the loaded file does not exist'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->load() throws an InvalidArgumentException if the loaded file does not exist'); +} + +try +{ + $loader->getFilesAsArray(array('parameters.ini')); + $t->fail('->load() throws an InvalidArgumentException if the loaded file is not a valid YAML file'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->load() throws an InvalidArgumentException if the loaded file is not a valid YAML file'); +} + +$loader = new ProjectLoader($fixturesPath.'/yaml'); + +foreach (array('nonvalid1', 'nonvalid2') as $fixture) +{ + try + { + $loader->getFilesAsArray(array($fixture.'.yml')); + $t->fail('->load() throws an InvalidArgumentException if the loaded file does not validate'); + } + catch (InvalidArgumentException $e) + { + $t->pass('->load() throws an InvalidArgumentException if the loaded file does not validate'); + } +} + +$yamls = $loader->getFilesAsArray(array('services1.yml')); +$t->ok(is_array($yamls), '->getFilesAsArray() returns an array'); +$t->is(key($yamls), realpath($fixturesPath.'/yaml/services1.yml'), '->getFilesAsArray() returns an array where the keys are absolutes paths to the original YAML file'); + +// ->load() # parameters +$t->diag('->load() # parameters'); +$loader = new ProjectLoader($fixturesPath.'/yaml'); +$config = $loader->load(array('services2.yml')); +$t->is($config->getParameters(), array('foo' => 'bar', 'values' => array(true, false, 0, 1000.3), 'bar' => 'foo', 'foo_bar' => new Reference('foo_bar')), '->load() converts YAML keys to lowercase'); + +$loader = new ProjectLoader($fixturesPath.'/yaml'); +$config = $loader->load(array('services2.yml', 'services3.yml')); +$t->is($config->getParameters(), array('foo' => 'foo', 'values' => array(true, false), 'bar' => 'foo', 'foo_bar' => new Reference('foo_bar')), '->load() merges the first level of arguments when multiple files are loaded'); + +// ->load() # imports +$t->diag('->load() # imports'); +$config = $loader->load(array('services4.yml')); +$t->is($config->getParameters(), array('foo' => 'bar', 'bar' => '%foo%', 'values' => array(true, false), 'foo_bar' => new Reference('foo_bar')), '->load() imports and merges imported files'); + +// ->load() # services +$t->diag('->load() # services'); +$config = $loader->load(array('services6.yml')); +$services = $config->getDefinitions(); +$t->ok(isset($services['foo']), '->load() parses service elements'); +$t->is(get_class($services['foo']), 'Symfony\\Components\\DependencyInjection\\Definition', '->load() converts service element to Definition instances'); +$t->is($services['foo']->getClass(), 'FooClass', '->load() parses the class attribute'); +$t->ok($services['shared']->isShared(), '->load() parses the shared attribute'); +$t->ok(!$services['non_shared']->isShared(), '->load() parses the shared attribute'); +$t->is($services['constructor']->getConstructor(), 'getInstance', '->load() parses the constructor attribute'); +$t->is($services['file']->getFile(), '%path%/foo.php', '->load() parses the file tag'); +$t->is($services['arguments']->getArguments(), array('foo', new Reference('foo'), array(true, false)), '->load() parses the argument tags'); +$t->is($services['configurator1']->getConfigurator(), 'sc_configure', '->load() parses the configurator tag'); +$t->is($services['configurator2']->getConfigurator(), array(new Reference('baz'), 'configure'), '->load() parses the configurator tag'); +$t->is($services['configurator3']->getConfigurator(), array('BazClass', 'configureStatic'), '->load() parses the configurator tag'); +$t->is($services['method_call1']->getMethodCalls(), array(array('setBar', array())), '->load() parses the method_call tag'); +$t->is($services['method_call2']->getMethodCalls(), array(array('setBar', array('foo', new Reference('foo'), array(true, false)))), '->load() parses the method_call tag'); +$aliases = $config->getAliases(); +$t->ok(isset($aliases['alias_for_foo']), '->load() parses aliases'); +$t->is($aliases['alias_for_foo'], 'foo', '->load() parses aliases'); + +$config = $loader->load(array('services6.yml', 'services7.yml')); +$services = $config->getDefinitions(); +$t->is($services['foo']->getClass(), 'BarClass', '->load() merges the services when multiple files are loaded'); + +// extensions +$t->diag('extensions'); +Loader::registerExtension(new ProjectExtension()); +$loader = new ProjectLoader($fixturesPath.'/yaml'); + +$config = $loader->load('services10.yml'); +$services = $config->getDefinitions(); +$parameters = $config->getParameters(); +$t->ok(isset($services['project.service.bar']), '->load() parses extension elements'); +$t->ok(isset($parameters['project.parameter.bar']), '->load() parses extension elements'); + +try +{ + $config = $loader->load('services11.yml'); + $t->fail('->load() throws an InvalidArgumentException if the tag is not valid'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->load() throws an InvalidArgumentException if the tag is not valid'); +} + +try +{ + $config = $loader->load('services12.yml'); + $t->fail('->load() throws an InvalidArgumentException if an extension is not loaded'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->load() throws an InvalidArgumentException if an extension is not loaded'); +} diff --git a/tests/unit/Symfony/Components/DependencyInjection/ParameterTest.php b/tests/unit/Symfony/Components/DependencyInjection/ParameterTest.php new file mode 100644 index 000000000000..482caf78d0a1 --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/ParameterTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Parameter; + +$t = new LimeTest(1); + +// __construct() ->__toString() +$t->diag('__construct() ->__toString()'); + +$ref = new Parameter('foo'); +$t->is((string) $ref, 'foo', '__construct() sets the id of the parameter, which is used for the __toString() method'); diff --git a/tests/unit/Symfony/Components/DependencyInjection/ReferenceTest.php b/tests/unit/Symfony/Components/DependencyInjection/ReferenceTest.php new file mode 100644 index 000000000000..993711ccd6c0 --- /dev/null +++ b/tests/unit/Symfony/Components/DependencyInjection/ReferenceTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +use Symfony\Components\DependencyInjection\Reference; + +$t = new LimeTest(1); + +// __construct() ->__toString() +$t->diag('__construct() ->__toString()'); + +$ref = new Reference('foo'); +$t->is((string) $ref, 'foo', '__construct() sets the id of the reference, which is used for the __toString() method'); diff --git a/tests/unit/Symfony/Components/EventDispatcher/EventDispatcherTest.php b/tests/unit/Symfony/Components/EventDispatcher/EventDispatcherTest.php new file mode 100644 index 000000000000..0aae435969b3 --- /dev/null +++ b/tests/unit/Symfony/Components/EventDispatcher/EventDispatcherTest.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +use Symfony\Components\EventDispatcher\Event; +use Symfony\Components\EventDispatcher\EventDispatcher; + +$t = new LimeTest(19); + +$dispatcher = new EventDispatcher(); + +// ->connect() ->disconnect() +$t->diag('->connect() ->disconnect()'); +$dispatcher->connect('bar', 'listenToBar'); +$t->is($dispatcher->getListeners('bar'), array('listenToBar'), '->connect() connects a listener to an event name'); +$dispatcher->connect('bar', 'listenToBarBar'); +$t->is($dispatcher->getListeners('bar'), array('listenToBar', 'listenToBarBar'), '->connect() can connect several listeners for the same event name'); + +$dispatcher->connect('barbar', 'listenToBarBar'); +$dispatcher->disconnect('bar', 'listenToBarBar'); +$t->is($dispatcher->getListeners('bar'), array('listenToBar'), '->disconnect() disconnects a listener for an event name'); +$t->is($dispatcher->getListeners('barbar'), array('listenToBarBar'), '->disconnect() disconnects a listener for an event name'); + +$t->ok($dispatcher->disconnect('foobar', 'listen') === false, '->disconnect() returns false if the listener does not exist'); + +// ->getListeners() ->hasListeners() +$t->diag('->getListeners() ->hasListeners()'); +$t->is($dispatcher->hasListeners('foo'), false, '->hasListeners() returns false if the event has no listener'); +$dispatcher->connect('foo', 'listenToFoo'); +$t->is($dispatcher->hasListeners('foo'), true, '->hasListeners() returns true if the event has some listeners'); +$dispatcher->disconnect('foo', 'listenToFoo'); +$t->is($dispatcher->hasListeners('foo'), false, '->hasListeners() returns false if the event has no listener'); + +$t->is($dispatcher->getListeners('bar'), array('listenToBar'), '->getListeners() returns an array of listeners connected to the given event name'); +$t->is($dispatcher->getListeners('foobar'), array(), '->getListeners() returns an empty array if no listener are connected to the given event name'); + +$listener = new Listener(); + +// ->notify() +$t->diag('->notify()'); +$listener->reset(); +$dispatcher = new EventDispatcher(); +$dispatcher->connect('foo', array($listener, 'listenToFoo')); +$dispatcher->connect('foo', array($listener, 'listenToFooBis')); +$e = $dispatcher->notify($event = new Event(new stdClass(), 'foo')); +$t->is($listener->getValue(), 'listenToFoolistenToFooBis', '->notify() notifies all registered listeners in order'); +$t->is($e, $event, '->notify() returns the event object'); + +$listener->reset(); +$dispatcher = new EventDispatcher(); +$dispatcher->connect('foo', array($listener, 'listenToFooBis')); +$dispatcher->connect('foo', array($listener, 'listenToFoo')); +$dispatcher->notify(new Event(new stdClass(), 'foo')); +$t->is($listener->getValue(), 'listenToFooBislistenToFoo', '->notify() notifies all registered listeners in order'); + +// ->notifyUntil() +$t->diag('->notifyUntil()'); +$listener->reset(); +$dispatcher = new EventDispatcher(); +$dispatcher->connect('foo', array($listener, 'listenToFoo')); +$dispatcher->connect('foo', array($listener, 'listenToFooBis')); +$e = $dispatcher->notifyUntil($event = new Event(new stdClass(), 'foo')); +$t->is($listener->getValue(), 'listenToFoolistenToFooBis', '->notifyUntil() notifies all registered listeners in order and stops if it returns true'); +$t->is($e, $event, '->notifyUntil() returns the event object'); + +$listener->reset(); +$dispatcher = new EventDispatcher(); +$dispatcher->connect('foo', array($listener, 'listenToFooBis')); +$dispatcher->connect('foo', array($listener, 'listenToFoo')); +$e = $dispatcher->notifyUntil($event = new Event(new stdClass(), 'foo')); +$t->is($listener->getValue(), 'listenToFooBis', '->notifyUntil() notifies all registered listeners in order and stops if it returns true'); + +// ->filter() +$t->diag('->filter()'); +$listener->reset(); +$dispatcher = new EventDispatcher(); +$dispatcher->connect('foo', array($listener, 'filterFoo')); +$dispatcher->connect('foo', array($listener, 'filterFooBis')); +$e = $dispatcher->filter($event = new Event(new stdClass(), 'foo'), 'foo'); +$t->is($e->getReturnValue(), '-*foo*-', '->filter() filters a value'); +$t->is($e, $event, '->filter() returns the event object'); + +$listener->reset(); +$dispatcher = new EventDispatcher(); +$dispatcher->connect('foo', array($listener, 'filterFooBis')); +$dispatcher->connect('foo', array($listener, 'filterFoo')); +$e = $dispatcher->filter($event = new Event(new stdClass(), 'foo'), 'foo'); +$t->is($e->getReturnValue(), '*-foo-*', '->filter() filters a value'); + +class Listener +{ + protected + $value = ''; + + function filterFoo(Event $event, $foo) + { + return "*$foo*"; + } + + function filterFooBis(Event $event, $foo) + { + return "-$foo-"; + } + + function listenToFoo(Event $event) + { + $this->value .= 'listenToFoo'; + } + + function listenToFooBis(Event $event) + { + $this->value .= 'listenToFooBis'; + + return true; + } + + function getValue() + { + return $this->value; + } + + function reset() + { + $this->value = ''; + } +} diff --git a/tests/unit/Symfony/Components/EventDispatcher/EventTest.php b/tests/unit/Symfony/Components/EventDispatcher/EventTest.php new file mode 100644 index 000000000000..024801b42979 --- /dev/null +++ b/tests/unit/Symfony/Components/EventDispatcher/EventTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +use Symfony\Components\EventDispatcher\Event; + +$t = new LimeTest(11); + +$subject = new stdClass(); +$parameters = array('foo' => 'bar'); +$event = new Event($subject, 'name', $parameters); + +// ->getSubject() +$t->diag('->getSubject()'); +$t->is($event->getSubject(), $subject, '->getSubject() returns the event subject'); + +// ->getName() +$t->diag('->getName()'); +$t->is($event->getName(), 'name', '->getName() returns the event name'); + +// ->getParameters() +$t->diag('->getParameters()'); +$t->is($event->getParameters(), $parameters, '->getParameters() returns the event parameters'); + +// ->getReturnValue() ->setReturnValue() +$t->diag('->getReturnValue() ->setReturnValue()'); +$event->setReturnValue('foo'); +$t->is($event->getReturnValue(), 'foo', '->getReturnValue() returns the return value of the event'); + +// ->setProcessed() ->isProcessed() +$t->diag('->setProcessed() ->isProcessed()'); +$event->setProcessed(true); +$t->is($event->isProcessed(), true, '->isProcessed() returns true if the event has been processed'); +$event->setProcessed(false); +$t->is($event->isProcessed(), false, '->setProcessed() changes the processed status'); + +// ArrayAccess interface +$t->diag('ArrayAccess interface'); +$t->is($event['foo'], 'bar', 'Event implements the ArrayAccess interface'); +$event['foo'] = 'foo'; +$t->is($event['foo'], 'foo', 'Event implements the ArrayAccess interface'); + +try +{ + $event['foobar']; + $t->fail('::offsetGet() throws an \InvalidArgumentException exception when the parameter does not exist'); +} +catch (\InvalidArgumentException $e) +{ + $t->pass('::offsetGet() throws an \InvalidArgumentException exception when the parameter does not exist'); +} + +$t->ok(isset($event['foo']), 'Event implements the ArrayAccess interface'); +unset($event['foo']); +$t->ok(!isset($event['foo']), 'Event implements the ArrayAccess interface'); diff --git a/tests/unit/Symfony/Components/OutputEscaper/ArrayDecoratorTest.php b/tests/unit/Symfony/Components/OutputEscaper/ArrayDecoratorTest.php new file mode 100644 index 000000000000..52df5e664bad --- /dev/null +++ b/tests/unit/Symfony/Components/OutputEscaper/ArrayDecoratorTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +use Symfony\Components\OutputEscaper\Escaper; + +$t = new LimeTest(11); + +$a = array('escaped!', 1, null, array(2, 'escaped!')); +$escaped = Escaper::escape('esc_entities', $a); + +// ->getRaw() +$t->diag('->getRaw()'); +$t->is($escaped->getRaw(0), 'escaped!', '->getRaw() returns the raw value'); + +// ArrayAccess interface +$t->diag('ArrayAccess interface'); +$t->is($escaped[0], '<strong>escaped!</strong>', 'The escaped object behaves like an array'); +$t->is($escaped[2], null, 'The escaped object behaves like an array'); +$t->is($escaped[3][1], '<strong>escaped!</strong>', 'The escaped object behaves like an array'); + +$t->ok(isset($escaped[1]), 'The escaped object behaves like an array (isset)'); + +$t->diag('ArrayAccess interface is read only'); +try +{ + unset($escaped[0]); + $t->fail('The escaped object is read only (unset)'); +} +catch (\LogicException $e) +{ + $t->pass('The escaped object is read only (unset)'); +} + +try +{ + $escaped[0] = 12; + $t->fail('The escaped object is read only (set)'); +} +catch (\LogicException $e) +{ + $t->pass('The escaped object is read only (set)'); +} + +// Iterator interface +$t->diag('Iterator interface'); +foreach ($escaped as $key => $value) +{ + switch ($key) + { + case 0: + $t->is($value, '<strong>escaped!</strong>', 'The escaped object behaves like an array'); + break; + case 1: + $t->is($value, 1, 'The escaped object behaves like an array'); + break; + case 2: + $t->is($value, null, 'The escaped object behaves like an array'); + break; + case 3: + break; + default: + $t->fail('The escaped object behaves like an array'); + } +} + +// Coutable interface +$t->diag('Countable interface'); +$t->is(count($escaped), 4, 'The escaped object implements the Countable interface'); diff --git a/tests/unit/Symfony/Components/OutputEscaper/EscaperTest.php b/tests/unit/Symfony/Components/OutputEscaper/EscaperTest.php new file mode 100644 index 000000000000..d6a16e7e2d48 --- /dev/null +++ b/tests/unit/Symfony/Components/OutputEscaper/EscaperTest.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +use Symfony\Components\OutputEscaper\Escaper; +use Symfony\Components\OutputEscaper\Safe; +use Symfony\Components\OutputEscaper\IteratorDecorator; +use Symfony\Components\OutputEscaper\ArrayDecorator; +use Symfony\Components\OutputEscaper\ObjectDecorator; + +$t = new LimeTest(39); + +class OutputEscaperTestClass +{ + public $title = 'escaped!'; + + public function getTitle() + { + return $this->title; + } + + public function getTitleTitle() + { + $o = new self; + + return $o->getTitle(); + } +} + +class OutputEscaperTestClassChild extends OutputEscaperTestClass +{ +} + +// ::escape() +$t->diag('::escape()'); +$t->diag('::escape() does not escape special values'); +$t->ok(Escaper::escape('esc_entities', null) === null, '::escape() returns null if the value to escape is null'); +$t->ok(Escaper::escape('esc_entities', false) === false, '::escape() returns false if the value to escape is false'); +$t->ok(Escaper::escape('esc_entities', true) === true, '::escape() returns true if the value to escape is true'); + +$t->diag('::escape() does not escape a value when escaping method is ESC_RAW'); +$t->is(Escaper::escape('esc_raw', 'escaped!'), 'escaped!', '::escape() takes an escaping strategy function name as its first argument'); + +$t->diag('::escape() escapes strings'); +$t->is(Escaper::escape('esc_entities', 'escaped!'), '<strong>escaped!</strong>', '::escape() returns an escaped string if the value to escape is a string'); +$t->is(Escaper::escape('esc_entities', 'échappé'), '<strong>échappé</strong>', '::escape() returns an escaped string if the value to escape is a string'); + +$t->diag('::escape() escapes arrays'); +$input = array( + 'foo' => 'escaped!', + 'bar' => array('foo' => 'escaped!'), +); +$output = Escaper::escape('esc_entities', $input); +$t->ok($output instanceof ArrayDecorator, '::escape() returns a ArrayDecorator object if the value to escape is an array'); +$t->is($output['foo'], '<strong>escaped!</strong>', '::escape() escapes all elements of the original array'); +$t->is($output['bar']['foo'], '<strong>escaped!</strong>', '::escape() is recursive'); +$t->is($output->getRawValue(), $input, '->getRawValue() returns the unescaped value'); + +$t->diag('::escape() escapes objects'); +$input = new OutputEscaperTestClass(); +$output = Escaper::escape('esc_entities', $input); +$t->ok($output instanceof ObjectDecorator, '::escape() returns a ObjectDecorator object if the value to escape is an object'); +$t->is($output->getTitle(), '<strong>escaped!</strong>', '::escape() escapes all methods of the original object'); +$t->is($output->title, '<strong>escaped!</strong>', '::escape() escapes all properties of the original object'); +$t->is($output->getTitleTitle(), '<strong>escaped!</strong>', '::escape() is recursive'); +$t->is($output->getRawValue(), $input, '->getRawValue() returns the unescaped value'); + +$t->is(Escaper::escape('esc_entities', $output)->getTitle(), '<strong>escaped!</strong>', '::escape() does not double escape an object'); +$t->ok(Escaper::escape('esc_entities', new \DirectoryIterator('.')) instanceof IteratorDecorator, '::escape() returns a IteratorDecorator object if the value to escape is an object that implements the ArrayAccess interface'); + +$t->diag('::escape() does not escape object marked as being safe'); +$t->ok(Escaper::escape('esc_entities', new Safe(new OutputEscaperTestClass())) instanceof OutputEscaperTestClass, '::escape() returns the original value if it is marked as being safe'); + +Escaper::markClassAsSafe('OutputEscaperTestClass'); +$t->ok(Escaper::escape('esc_entities', new OutputEscaperTestClass()) instanceof OutputEscaperTestClass, '::escape() returns the original value if the object class is marked as being safe'); +$t->ok(Escaper::escape('esc_entities', new OutputEscaperTestClassChild()) instanceof OutputEscaperTestClassChild, '::escape() returns the original value if one of the object parent class is marked as being safe'); + +$t->diag('::escape() cannot escape resources'); +$fh = fopen(__FILE__, 'r'); +try +{ + Escaper::escape('esc_entities', $fh); + $t->fail('::escape() throws an InvalidArgumentException if the value cannot be escaped'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('::escape() throws an InvalidArgumentException if the value cannot be escaped'); +} + +// ::unescape() +$t->diag('::unescape()'); +$t->diag('::unescape() does not unescape special values'); +$t->ok(Escaper::unescape(null) === null, '::unescape() returns null if the value to unescape is null'); +$t->ok(Escaper::unescape(false) === false, '::unescape() returns false if the value to unescape is false'); +$t->ok(Escaper::unescape(true) === true, '::unescape() returns true if the value to unescape is true'); + +$t->diag('::unescape() unescapes strings'); +$t->is(Escaper::unescape('<strong>escaped!</strong>'), 'escaped!', '::unescape() returns an unescaped string if the value to unescape is a string'); +$t->is(Escaper::unescape('<strong>échappé</strong>'), 'échappé', '::unescape() returns an unescaped string if the value to unescape is a string'); + +$t->diag('::unescape() unescapes arrays'); +$input = Escaper::escape('esc_entities', array( + 'foo' => 'escaped!', + 'bar' => array('foo' => 'escaped!'), +)); +$output = Escaper::unescape($input); +$t->ok(is_array($output), '::unescape() returns an array if the input is a ArrayDecorator object'); +$t->is($output['foo'], 'escaped!', '::unescape() unescapes all elements of the original array'); +$t->is($output['bar']['foo'], 'escaped!', '::unescape() is recursive'); + +$t->diag('::unescape() unescapes objects'); +$object = new OutputEscaperTestClass(); +$input = Escaper::escape('esc_entities', $object); +$output = Escaper::unescape($input); +$t->ok($output instanceof OutputEscaperTestClass, '::unescape() returns the original object when a ObjectDecorator object is passed'); +$t->is($output->getTitle(), 'escaped!', '::unescape() unescapes all methods of the original object'); +$t->is($output->title, 'escaped!', '::unescape() unescapes all properties of the original object'); +$t->is($output->getTitleTitle(), 'escaped!', '::unescape() is recursive'); + +$t->ok(IteratorDecorator::unescape(Escaper::escape('esc_entities', new DirectoryIterator('.'))) instanceof DirectoryIterator, '::unescape() unescapes IteratorDecorator objects'); + +$t->diag('::unescape() does not unescape object marked as being safe'); +$t->ok(Escaper::unescape(Escaper::escape('esc_entities', new Safe(new OutputEscaperTestClass()))) instanceof OutputEscaperTestClass, '::unescape() returns the original value if it is marked as being safe'); + +Escaper::markClassAsSafe('OutputEscaperTestClass'); +$t->ok(Escaper::unescape(Escaper::escape('esc_entities', new OutputEscaperTestClass())) instanceof OutputEscaperTestClass, '::unescape() returns the original value if the object class is marked as being safe'); +$t->ok(Escaper::unescape(Escaper::escape('esc_entities', new OutputEscaperTestClassChild())) instanceof OutputEscaperTestClassChild, '::unescape() returns the original value if one of the object parent class is marked as being safe'); + +$t->diag('::unescape() do nothing to resources'); +$fh = fopen(__FILE__, 'r'); +$t->is(Escaper::unescape($fh), $fh, '::unescape() do nothing to resources'); + +$t->diag('::unescape() unescapes mixed arrays'); +$object = new OutputEscaperTestClass(); +$input = array( + 'foo' => 'bar', + 'bar' => Escaper::escape('esc_entities', 'bar'), + 'foobar' => Escaper::escape('esc_entities', $object), +); +$output = array( + 'foo' => 'bar', + 'bar' => 'bar', + 'foobar' => $object, +); +$t->is(Escaper::unescape($input), $output, '::unescape() unescapes values with some escaped and unescaped values'); diff --git a/tests/unit/Symfony/Components/OutputEscaper/ObjectDecoratorTest.php b/tests/unit/Symfony/Components/OutputEscaper/ObjectDecoratorTest.php new file mode 100644 index 000000000000..36fa855e7e01 --- /dev/null +++ b/tests/unit/Symfony/Components/OutputEscaper/ObjectDecoratorTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +use Symfony\Components\OutputEscaper\Escaper; + +$t = new LimeTest(3); + +class OutputEscaperTest +{ + public function __toString() + { + return $this->getTitle(); + } + + public function getTitle() + { + return 'escaped!'; + } + + public function getTitles() + { + return array(1, 2, 'escaped!'); + } +} + +$object = new OutputEscaperTest(); +$escaped = Escaper::escape('esc_entities', $object); + +$t->is($escaped->getTitle(), '<strong>escaped!</strong>', 'The escaped object behaves like the real object'); + +$array = $escaped->getTitles(); +$t->is($array[2], '<strong>escaped!</strong>', 'The escaped object behaves like the real object'); + +// __toString() +$t->diag('__toString()'); + +$t->is($escaped->__toString(), '<strong>escaped!</strong>', 'The escaped object behaves like the real object'); diff --git a/tests/unit/Symfony/Components/OutputEscaper/SafeTest.php b/tests/unit/Symfony/Components/OutputEscaper/SafeTest.php new file mode 100644 index 000000000000..317ab2b83afa --- /dev/null +++ b/tests/unit/Symfony/Components/OutputEscaper/SafeTest.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +use Symfony\Components\OutputEscaper\Safe; + +$t = new LimeTest(13); + +// ->getValue() +$t->diag('->getValue()'); +$safe = new Safe('foo'); +$t->is($safe->getValue(), 'foo', '->getValue() returns the embedded value'); + +// ->__set() ->__get() +$t->diag('->__set() ->__get()'); + +class TestClass1 +{ + public $foo = 'bar'; +} + +$safe = new Safe(new TestClass1()); + +$t->is($safe->foo, 'bar', '->__get() returns the object parameter'); +$safe->foo = 'baz'; +$t->is($safe->foo, 'baz', '->__set() sets the object parameter'); + +// ->__call() +$t->diag('->__call()'); + +class TestClass2 +{ + public function doSomething() + { + return 'ok'; + } +} + +$safe = new Safe(new TestClass2()); +$t->is($safe->doSomething(), 'ok', '->__call() invokes the embedded method'); + +// ->__isset() ->__unset() +$t->diag('->__isset() ->__unset()'); + +class TestClass3 +{ + public + $boolValue = true, + $nullValue = null; +} + +$safe = new Safe(new TestClass3()); + +$t->is(isset($safe->boolValue), true, '->__isset() returns true if the property is not null'); +$t->is(isset($safe->nullValue), false, '->__isset() returns false if the property is null'); +$t->is(isset($safe->undefinedValue), false, '->__isset() returns false if the property does not exist'); + +unset($safe->boolValue); +$t->is(isset($safe->boolValue), false, '->__unset() unsets the embedded property'); + +// Iterator +$t->diag('Iterator'); + +$input = array('one' => 1, 'two' => 2, 'three' => 3, 'children' => array(1, 2, 3)); +$output = array(); + +$safe = new Safe($input); +foreach ($safe as $key => $value) +{ + $output[$key] = $value; +} +$t->same($output, $input, '"Iterator" implementation imitates an array'); + +// ArrayAccess +$t->diag('ArrayAccess'); + +$safe = new Safe(array('foo' => 'bar')); + +$t->is($safe['foo'], 'bar', '"ArrayAccess" implementation returns a value from the embedded array'); +$safe['foo'] = 'baz'; +$t->is($safe['foo'], 'baz', '"ArrayAccess" implementation sets a value on the embedded array'); +$t->is(isset($safe['foo']), true, '"ArrayAccess" checks if a value is set on the embedded array'); +unset($safe['foo']); +$t->is(isset($safe['foo']), false, '"ArrayAccess" unsets a value on the embedded array'); diff --git a/tests/unit/Symfony/Components/Templating/EngineTest.php b/tests/unit/Symfony/Components/Templating/EngineTest.php new file mode 100644 index 000000000000..aa734648e44a --- /dev/null +++ b/tests/unit/Symfony/Components/Templating/EngineTest.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +require_once __DIR__.'/../../../../lib/SymfonyTests/Components/Templating/SimpleHelper.php'; + +use Symfony\Components\Templating\Engine; +use Symfony\Components\Templating\Loader\Loader; +use Symfony\Components\Templating\Loader\CompilableLoaderInterface; +use Symfony\Components\Templating\Helper\HelperSet; +use Symfony\Components\Templating\Renderer\Renderer; +use Symfony\Components\Templating\Renderer\PhpRenderer; +use Symfony\Components\Templating\Storage\Storage; +use Symfony\Components\Templating\Storage\StringStorage; + +$t = new LimeTest(33); + +class ProjectTemplateEngine extends Engine +{ + public function getLoader() + { + return $this->loader; + } + + public function getRenderers() + { + return $this->renderers; + } +} + +class ProjectTemplateRenderer extends PhpRenderer +{ + public function getEngine() + { + return $this->engine; + } +} + +class ProjectTemplateLoader extends Loader +{ + public $templates = array(); + + public function setTemplate($name, $template) + { + $this->templates[$name] = $template; + } + + public function load($template, $renderer = 'php') + { + if (isset($this->templates[$template.'.'.$renderer])) + { + return new StringStorage($this->templates[$template.'.'.$renderer]); + } + + return false; + } +} + +$loader = new ProjectTemplateLoader(); +$renderer = new ProjectTemplateRenderer(); + +// __construct() +$t->diag('__construct()'); +$engine = new ProjectTemplateEngine($loader); +$t->is($engine->getLoader(), $loader, '__construct() takes a loader instance as its second first argument'); +$t->is(array_keys($engine->getRenderers()), array('php'), '__construct() automatically registers a PHP renderer if none is given'); +$t->ok($engine->getHelperSet() instanceof HelperSet, '__construct() automatically creates a helper set if none is given'); + +$engine = new ProjectTemplateEngine($loader, array('foo' => $renderer)); +$t->is(array_keys($engine->getRenderers()), array('foo', 'php'), '__construct() takes an array of renderers as its third argument'); +$t->ok($renderer->getEngine() === $engine, '__construct() registers itself on all renderers'); + +$engine = new ProjectTemplateEngine($loader, array('php' => $renderer)); +$t->ok($engine->getRenderers() === array('php' => $renderer), '__construct() can overridde the default PHP renderer'); + +$engine = new ProjectTemplateEngine($loader, array(), $helperSet = new HelperSet()); +$t->ok($engine->getHelperSet() === $helperSet, '__construct() takes a helper set as its third argument'); + +// ->getHelperSet() ->setHelperSet() +$t->diag('->getHelperSet() ->setHelperSet()'); +$engine = new ProjectTemplateEngine($loader); +$engine->setHelperSet(new HelperSet(array('foo' => $helper = new SimpleHelper('bar')))); +$t->is((string) $engine->getHelperSet()->get('foo'), 'bar', '->setHelperSet() sets a helper set'); + +// __get() +$t->diag('__get()'); +$t->is($engine->foo, $helper, '->__get() returns the value of a helper'); + +try +{ + $engine->bar; + $t->fail('->__get() throws an InvalidArgumentException if the helper is not defined'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->__get() throws an InvalidArgumentException if the helper is not defined'); +} + +// ->get() ->set() ->has() +$t->diag('->get() ->set() ->has()'); +$engine = new ProjectTemplateEngine($loader); +$engine->set('foo', 'bar'); +$t->is($engine->get('foo'), 'bar', '->set() sets a slot value'); +$t->is($engine->get('bar', 'bar'), 'bar', '->get() takes a default value to return if the slot does not exist'); + +$t->ok($engine->has('foo'), '->has() returns true if the slot exists'); +$t->ok(!$engine->has('bar'), '->has() returns false if the slot does not exist'); + +// ->output() +$t->diag('->output()'); +ob_start(); +$ret = $engine->output('foo'); +$output = ob_get_clean(); +$t->is($output, 'bar', '->output() outputs the content of a slot'); +$t->is($ret, true, '->output() returns true if the slot exists'); + +ob_start(); +$ret = $engine->output('bar', 'bar'); +$output = ob_get_clean(); +$t->is($output, 'bar', '->output() takes a default value to return if the slot does not exist'); +$t->is($ret, true, '->output() returns true if the slot does not exist but a default value is provided'); + +ob_start(); +$ret = $engine->output('bar'); +$output = ob_get_clean(); +$t->is($output, '', '->output() outputs nothing if the slot does not exist'); +$t->is($ret, false, '->output() returns false if the slot does not exist'); + +// ->start() ->stop() +$t->diag('->start() ->stop()'); +$engine->start('bar'); +echo 'foo'; +$engine->stop(); +$t->is($engine->get('bar'), 'foo', '->start() starts a slot'); +$t->ok($engine->has('bar'), '->starts() starts a slot'); + +$engine->start('bar'); +try +{ + $engine->start('bar'); + $engine->stop(); + $t->fail('->start() throws an InvalidArgumentException if a slot with the same name is already started'); +} +catch (InvalidArgumentException $e) +{ + $engine->stop(); + $t->pass('->start() throws an InvalidArgumentException if a slot with the same name is already started'); +} + +try +{ + $engine->stop(); + $t->fail('->stop() throws an LogicException if no slot is started'); +} +catch (LogicException $e) +{ + $t->pass('->stop() throws an LogicException if no slot is started'); +} + +// ->extend() ->render() +$t->diag('->extend() ->render()'); +$engine = new ProjectTemplateEngine($loader); +try +{ + $engine->render('name'); + $t->fail('->render() throws an InvalidArgumentException if the template does not exist'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->render() throws an InvalidArgumentException if the template does not exist'); +} + +try +{ + $loader->setTemplate('name.foo', 'foo'); + $engine->render('foo:name'); + $t->fail('->render() throws an InvalidArgumentException if no renderer is registered for the given renderer'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->render() throws an InvalidArgumentException if no renderer is registered for the given renderer'); +} + +$engine->getHelperSet()->set(new SimpleHelper('bar')); +$loader->setTemplate('foo.php', 'extend("layout"); echo $this->foo.$foo ?>'); +$loader->setTemplate('layout.php', '-get("content") ?>-'); +$t->is($engine->render('foo', array('foo' => 'foo')), '-barfoo-', '->render() uses the decorator to decorate the template'); + +$loader->setTemplate('bar.php', 'bar'); +$loader->setTemplate('foo.php', 'extend("layout"); echo $foo ?>'); +$loader->setTemplate('layout.php', 'render("bar") ?>-get("content") ?>-'); +$t->is($engine->render('foo', array('foo' => 'foo', 'bar' => 'bar')), 'bar-foo-', '->render() supports render() calls in templates'); + +class CompilableTemplateLoader extends Loader implements CompilableLoaderInterface +{ + public function load($template, $renderer = 'php') + { + return new StringStorage($template, 'foo'); + } + + public function compile($template) + { + return 'COMPILED'; + } +} + +class FooTemplateRenderer extends Renderer +{ + public function evaluate(Storage $template, array $parameters = array()) + { + return 'foo'; + } +} + +$t->diag('compilable templates'); +$engine = new ProjectTemplateEngine(new CompilableTemplateLoader(), array('foo' => new FooTemplateRenderer())); +$t->is($engine->render('index'), 'foo', '->load() takes into account the renderer embedded in the Storage instance if not null'); + +// ->escape() +$t->diag('->escape()'); +$engine = new ProjectTemplateEngine($loader); +$t->is($engine->escape('
'), '<br />', '->escape() escapes strings'); +$t->is($engine->escape($foo = new stdClass()), $foo, '->escape() does nothing on non strings'); + +// ->getCharset() ->setCharset() +$t->diag('->getCharset() ->setCharset()'); +$engine = new ProjectTemplateEngine($loader); +$t->is($engine->getCharset(), 'UTF-8', '->getCharset() returns UTF-8 by default'); +$engine->setCharset('ISO-8859-1'); +$t->is($engine->getCharset(), 'ISO-8859-1', '->setCharset() changes the default charset to use'); diff --git a/tests/unit/Symfony/Components/Templating/Helper/AssetsTest.php b/tests/unit/Symfony/Components/Templating/Helper/AssetsTest.php new file mode 100644 index 000000000000..6e2c0c7a6b87 --- /dev/null +++ b/tests/unit/Symfony/Components/Templating/Helper/AssetsTest.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\Templating\Helper\AssetsHelper; + +$t = new LimeTest(25); + +// __construct() +$t->diag('__construct()'); +$helper = new AssetsHelper('foo', 'http://www.example.com', 'abcd'); +$t->is($helper->getBasePath(), '/foo/', '__construct() takes a base path as its first argument'); +$t->is($helper->getBaseURLs(), array('http://www.example.com'), '__construct() takes a base URL as its second argument'); +$t->is($helper->getVersion(), 'abcd', '__construct() takes a version as its thrid argument'); + +// ->getBasePath() ->setBasePath() +$t->diag('->getBasePath() ->setBasePath()'); +$helper = new AssetsHelper(); +$helper->setBasePath('foo/'); +$t->is($helper->getBasePath(), '/foo/', '->setBasePath() prepends a / if needed'); +$helper->setBasePath('/foo'); +$t->is($helper->getBasePath(), '/foo/', '->setBasePath() appends a / is needed'); +$helper->setBasePath(''); +$t->is($helper->getBasePath(), '/', '->setBasePath() returns / if no base path is defined'); +$helper->setBasePath('0'); +$t->is($helper->getBasePath(), '/0/', '->setBasePath() returns /0/ if 0 is given'); + +// ->getVersion() ->getVersion() +$t->diag('->getVersion() ->getVersion()'); +$helper = new AssetsHelper(); +$helper->setVersion('foo'); +$t->is($helper->getVersion(), 'foo', '->setVersion() sets the version'); + +// ->setBaseURLs() ->getBaseURLs() +$t->diag('->setBaseURLs() ->getBaseURLs()'); +$helper = new AssetsHelper(); +$helper->setBaseURLs('http://www.example.com/'); +$t->is($helper->getBaseURLs(), array('http://www.example.com'), '->setBaseURLs() removes the / at the of an absolute base path'); +$helper->setBaseURLs(array('http://www1.example.com/', 'http://www2.example.com/')); +$URLs = array(); +for ($i = 0; $i < 20; $i++) +{ + $URLs[] = $helper->getBaseURL($i); +} +$URLs = array_values(array_unique($URLs)); +sort($URLs); +$t->is($URLs, array('http://www1.example.com', 'http://www2.example.com'), '->getBaseURL() returns a random base URL if several are given'); +$helper->setBaseURLs(''); +$t->is($helper->getBaseURL(1), '', '->getBaseURL() returns an empty string if no base URL exist'); + +// ->getUrl() +$t->diag('->getUrl()'); +$helper = new AssetsHelper(); +$t->is($helper->getUrl('http://example.com/foo.js'), 'http://example.com/foo.js', '->getUrl() does nothing if an absolute URL is given'); + +$helper = new AssetsHelper(); +$t->is($helper->getUrl('foo.js'), '/foo.js', '->getUrl() appends a / on relative paths'); +$t->is($helper->getUrl('/foo.js'), '/foo.js', '->getUrl() does nothing on absolute paths'); + +$helper = new AssetsHelper('/foo'); +$t->is($helper->getUrl('foo.js'), '/foo/foo.js', '->getUrl() appends the basePath on relative paths'); +$t->is($helper->getUrl('/foo.js'), '/foo.js', '->getUrl() does not append the basePath on absolute paths'); + +$helper = new AssetsHelper(null, 'http://assets.example.com/'); +$t->is($helper->getUrl('foo.js'), 'http://assets.example.com/foo.js', '->getUrl() prepends the base URL'); +$t->is($helper->getUrl('/foo.js'), 'http://assets.example.com/foo.js', '->getUrl() prepends the base URL'); + +$helper = new AssetsHelper(null, 'http://www.example.com/foo'); +$t->is($helper->getUrl('foo.js'), 'http://www.example.com/foo/foo.js', '->getUrl() prepends the base URL with a path'); +$t->is($helper->getUrl('/foo.js'), 'http://www.example.com/foo/foo.js', '->getUrl() prepends the base URL with a path'); + +$helper = new AssetsHelper('/foo', 'http://www.example.com/'); +$t->is($helper->getUrl('foo.js'), 'http://www.example.com/foo/foo.js', '->getUrl() prepends the base URL and the base path if defined'); +$t->is($helper->getUrl('/foo.js'), 'http://www.example.com/foo.js', '->getUrl() prepends the base URL but not the base path on absolute paths'); + +$helper = new AssetsHelper('/bar', 'http://www.example.com/foo'); +$t->is($helper->getUrl('foo.js'), 'http://www.example.com/foo/bar/foo.js', '->getUrl() prepends the base URL and the base path if defined'); +$t->is($helper->getUrl('/foo.js'), 'http://www.example.com/foo/foo.js', '->getUrl() prepends the base URL but not the base path on absolute paths'); + +$helper = new AssetsHelper('/bar', 'http://www.example.com/foo', 'abcd'); +$t->is($helper->getUrl('foo.js'), 'http://www.example.com/foo/bar/foo.js?abcd', '->getUrl() appends the version if defined'); diff --git a/tests/unit/Symfony/Components/Templating/Helper/HelperSetTest.php b/tests/unit/Symfony/Components/Templating/Helper/HelperSetTest.php new file mode 100644 index 000000000000..a8f957752223 --- /dev/null +++ b/tests/unit/Symfony/Components/Templating/Helper/HelperSetTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +require_once __DIR__.'/../../../../../lib/SymfonyTests/Components/Templating/SimpleHelper.php'; + +use Symfony\Components\Templating\Helper\HelperSet; +use Symfony\Components\Templating\Engine; +use Symfony\Components\Templating\Loader\FilesystemLoader; + +$t = new LimeTest(7); + +$engine = new Engine(new FilesystemLoader('/')); + +// __construct() +$t->diag('__construct()'); +$helperSet = new HelperSet(array('foo' => $helper = new SimpleHelper('foo'))); +$t->ok($helperSet->has('foo'), '__construct() takes an array of helpers as its first argument'); + +// ->setEngine() +$t->diag('->getEngine()'); +$helperSet = new HelperSet(array('foo' => $helper = new SimpleHelper('foo'))); +$t->ok($helper->getHelperSet() === $helperSet, '->__construct() changes the embedded helper set of the given helpers'); + +// ->get() ->set() ->has() +$t->diag('->getHelper() ->setHelper() ->has()'); +$helperSet = new HelperSet(); +$helperSet->set($helper = new SimpleHelper('bar')); +$t->ok($helper->getHelperSet() === $helperSet, '->set() changes the embedded helper set of the helper'); +$t->is((string) $helperSet->get('foo'), 'bar', '->set() sets a helper value'); + +$t->ok($helperSet->has('foo'), '->has() returns true if the helper is defined'); +$t->ok(!$helperSet->has('bar'), '->has() returns false if the helper is not defined'); + +try +{ + $helperSet->get('bar'); + $t->fail('->get() throws an InvalidArgumentException if the helper is not defined'); +} +catch (InvalidArgumentException $e) +{ + $t->pass('->get() throws an InvalidArgumentException if the helper is not defined'); +} diff --git a/tests/unit/Symfony/Components/Templating/Helper/HelperTest.php b/tests/unit/Symfony/Components/Templating/Helper/HelperTest.php new file mode 100644 index 000000000000..e77856a634b6 --- /dev/null +++ b/tests/unit/Symfony/Components/Templating/Helper/HelperTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\Templating\Helper\Helper; +use Symfony\Components\Templating\Helper\HelperSet; + +$t = new LimeTest(1); + +class ProjectTemplateHelper extends Helper +{ + public function getName() + { + return 'foo'; + } +} + +// ->getHelperSet() ->setHelperSet() +$t->diag('->getHelperSet() ->setHelperSet()'); +$helper = new ProjectTemplateHelper(); +$helper->setHelperSet($helperSet = new HelperSet(array($helper))); +$t->ok($helperSet === $helper->getHelperSet(), '->setHelperSet() sets the helper set related to this helper'); diff --git a/tests/unit/Symfony/Components/Templating/Helper/JavascriptsHelperTest.php b/tests/unit/Symfony/Components/Templating/Helper/JavascriptsHelperTest.php new file mode 100644 index 000000000000..6e4464e2199a --- /dev/null +++ b/tests/unit/Symfony/Components/Templating/Helper/JavascriptsHelperTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\Templating\Helper\AssetsHelper; +use Symfony\Components\Templating\Helper\JavascriptsHelper; +use Symfony\Components\Templating\Helper\HelperSet; +use Symfony\Components\Templating\Engine; +use Symfony\Components\Templating\Loader\FilesystemLoader; + +$t = new LimeTest(4); + +$helperSet = new HelperSet(array( + new AssetsHelper(), +)); + +// ->add() +$t->diag('->add()'); +$helper = new JavascriptsHelper(); +$helperSet->set($helper); +$helper->add('foo'); +$t->is($helper->get(), array('/foo' => array()), '->add() adds a JavaScript'); +$helper->add('/foo'); +$t->is($helper->get(), array('/foo' => array()), '->add() does not add the same JavaScript twice'); +$helper = new JavascriptsHelper(); +$helperSet->set($helper); +$helperSet->get('assets')->setBaseURLs('http://assets.example.com/'); +$helper->add('foo'); +$t->is($helper->get(), array('http://assets.example.com/foo' => array()), '->add() converts the JavaScript to a public path'); + +// ->__toString() +$t->diag('->__toString()'); +$helper = new JavascriptsHelper(); +$helperSet->set($helper); +$helperSet->get('assets')->setBaseURLs(''); +$helperSet->setEngine($engine = new Engine(new FilesystemLoader('/'))); +$helper->add('foo', array('class' => 'ba>')); +$t->is($helper->__toString(), '', '->__toString() converts the JavaScript configuration to HTML'); diff --git a/tests/unit/Symfony/Components/Templating/Helper/StylesheetsHelperTest.php b/tests/unit/Symfony/Components/Templating/Helper/StylesheetsHelperTest.php new file mode 100644 index 000000000000..3464278444eb --- /dev/null +++ b/tests/unit/Symfony/Components/Templating/Helper/StylesheetsHelperTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\Templating\Helper\AssetsHelper; +use Symfony\Components\Templating\Helper\StylesheetsHelper; +use Symfony\Components\Templating\Helper\HelperSet; +use Symfony\Components\Templating\Engine; +use Symfony\Components\Templating\Loader\FilesystemLoader; + +$t = new LimeTest(4); + +$helperSet = new HelperSet(array( + new AssetsHelper(), +)); + +// ->add() +$t->diag('->add()'); +$helper = new StylesheetsHelper(); +$helperSet->set($helper); +$helper->add('foo'); +$t->is($helper->get(), array('/foo' => array()), '->add() adds a stylesheet'); +$helper->add('/foo'); +$t->is($helper->get(), array('/foo' => array()), '->add() does not add the same stylesheet twice'); +$helper = new StylesheetsHelper(); +$helperSet->set($helper); +$helperSet->get('assets')->setBaseURLs('http://assets.example.com/'); +$helper->add('foo'); +$t->is($helper->get(), array('http://assets.example.com/foo' => array()), '->add() converts the stylesheet to a public path'); + +// ->__toString() +$t->diag('->__toString()'); +$helper = new StylesheetsHelper(); +$helperSet->set($helper); +$helperSet->get('assets')->setBaseURLs(''); +$helperSet->setEngine($engine = new Engine(new FilesystemLoader('/'))); +$helper->add('foo', array('media' => 'ba>')); +$t->is($helper->__toString(), '', '->__toString() converts the stylesheet configuration to HTML'); diff --git a/tests/unit/Symfony/Components/Templating/Loader/CacheLoaderTest.php b/tests/unit/Symfony/Components/Templating/Loader/CacheLoaderTest.php new file mode 100644 index 000000000000..164134a57b7b --- /dev/null +++ b/tests/unit/Symfony/Components/Templating/Loader/CacheLoaderTest.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +require_once __DIR__.'/../../../../../lib/SymfonyTests/Components/Templating/ProjectTemplateDebugger.php'; + +use Symfony\Components\Templating\Loader\Loader; +use Symfony\Components\Templating\Loader\CacheLoader; +use Symfony\Components\Templating\Loader\CompilableLoaderInterface; + +$t = new LimeTest(9); + +class ProjectTemplateLoader extends CacheLoader +{ + public function getDir() + { + return $this->dir; + } + + public function getLoader() + { + return $this->loader; + } +} + +class ProjectTemplateLoaderVar extends Loader +{ + public function getIndexTemplate() + { + return 'Hello World'; + } + + public function getSpecialTemplate() + { + return 'Hello {{ name }}'; + } + + public function load($template, $renderer = 'php') + { + if (method_exists($this, $method = 'get'.ucfirst($template).'Template')) + { + return $this->$method(); + } + + return false; + } +} + +class CompilableTemplateLoader extends ProjectTemplateLoaderVar implements CompilableLoaderInterface +{ + public function compile($template) + { + return preg_replace('/{{\s*([a-zA-Z0-9_]+)\s*}}/', '', $template); + } +} + +// __construct() +$t->diag('__construct()'); +$loader = new ProjectTemplateLoader($varLoader = new ProjectTemplateLoaderVar(), sys_get_temp_dir()); +$t->ok($loader->getLoader() === $varLoader, '__construct() takes a template loader as its first argument'); +$t->is($loader->getDir(), sys_get_temp_dir(), '__construct() takes a directory where to store the cache as its second argument'); + +// ->load() +$t->diag('->load()'); + +$dir = sys_get_temp_dir().DIRECTORY_SEPARATOR.rand(111111, 999999); +mkdir($dir, 0777, true); + +$loader = new ProjectTemplateLoader($varLoader = new ProjectTemplateLoaderVar(), $dir); +$loader->setDebugger($debugger = new ProjectTemplateDebugger()); +$t->ok($loader->load('foo') === false, '->load() returns false if the embed loader is not able to load the template'); +$loader->load('index'); +$t->ok($debugger->hasMessage('Storing template'), '->load() logs a "Storing template" message if the template is found'); +$loader->load('index'); +$t->ok($debugger->hasMessage('Fetching template'), '->load() logs a "Storing template" message if the template is fetched from cache'); + +$t->diag('load() template compilation'); +$dir = sys_get_temp_dir().DIRECTORY_SEPARATOR.rand(111111, 999999); +mkdir($dir, 0777, true); + +$loader = new ProjectTemplateLoader(new CompilableTemplateLoader(), $dir); +$loader->setDebugger($debugger = new ProjectTemplateDebugger()); +$template = $loader->load('special', 'comp'); +$t->ok($debugger->hasMessage('Storing template'), '->load() logs a "Storing template" message if the template is found'); +$t->is($template->getRenderer(), 'php', '->load() changes the renderer to php if the template is compilable'); + +$template = $loader->load('special', 'comp'); +$t->ok($debugger->hasMessage('Fetching template'), '->load() logs a "Storing template" message if the template is fetched from cache'); +$t->is($template->getRenderer(), 'php', '->load() changes the renderer to php if the template is compilable'); diff --git a/tests/unit/Symfony/Components/Templating/Loader/ChainLoaderTest.php b/tests/unit/Symfony/Components/Templating/Loader/ChainLoaderTest.php new file mode 100644 index 000000000000..5bd56e098e40 --- /dev/null +++ b/tests/unit/Symfony/Components/Templating/Loader/ChainLoaderTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +require_once __DIR__.'/../../../../../lib/SymfonyTests/Components/Templating/ProjectTemplateDebugger.php'; + +use Symfony\Components\Templating\Loader\ChainLoader; +use Symfony\Components\Templating\Loader\FilesystemLoader; +use Symfony\Components\Templating\Storage\FileStorage; + +$fixturesPath = realpath(__DIR__.'/../../../../../fixtures/Symfony/Components/Templating/'); + +$t = new LimeTest(5); + +class ProjectTemplateLoader extends ChainLoader +{ + public function getLoaders() + { + return $this->loaders; + } +} + +$loader1 = new FilesystemLoader($fixturesPath.'/null/%name%'); +$loader2 = new FilesystemLoader($fixturesPath.'/templates/%name%.%renderer%'); + +// __construct() +$t->diag('__construct()'); +$loader = new ProjectTemplateLoader(array($loader1, $loader2)); +$t->is($loader->getLoaders(), array($loader1, $loader2), '__construct() takes an array of template loaders as its second argument'); + +// ->addLoader() +$t->diag('->addLoader()'); +$loader = new ProjectTemplateLoader(array($loader1)); +$loader->addLoader($loader2); +$t->is($loader->getLoaders(), array($loader1, $loader2), '->addLoader() adds a template loader at the end of the loaders'); + +// ->load() +$t->diag('->load()'); +$loader = new ProjectTemplateLoader(array($loader1, $loader2)); +$t->ok($loader->load('bar') === false, '->load() returns false if the template is not found'); +$t->ok($loader->load('foo', 'xml') === false, '->load() returns false if the template does not exists for the given renderer'); +$t->ok($loader->load('foo') instanceof FileStorage, '->load() returns a FileStorage if the template exists'); diff --git a/tests/unit/Symfony/Components/Templating/Loader/FilesystemLoaderTest.php b/tests/unit/Symfony/Components/Templating/Loader/FilesystemLoaderTest.php new file mode 100644 index 000000000000..71612bc1b7ce --- /dev/null +++ b/tests/unit/Symfony/Components/Templating/Loader/FilesystemLoaderTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +require_once __DIR__.'/../../../../../lib/SymfonyTests/Components/Templating/ProjectTemplateDebugger.php'; + +use Symfony\Components\Templating\Loader\FilesystemLoader; +use Symfony\Components\Templating\Storage\FileStorage; + +$fixturesPath = realpath(__DIR__.'/../../../../../fixtures/Symfony/Components/Templating/'); + +$t = new LimeTest(15); + +class ProjectTemplateLoader extends FilesystemLoader +{ + public function getTemplatePathPatterns() + { + return $this->templatePathPatterns; + } + + static public function isAbsolutePath($path) + { + return parent::isAbsolutePath($path); + } +} + +// ->isAbsolutePath() +$t->diag('->isAbsolutePath()'); +$t->ok(ProjectTemplateLoader::isAbsolutePath('/foo.xml'), '->isAbsolutePath() returns true if the path is an absolute path'); +$t->ok(ProjectTemplateLoader::isAbsolutePath('c:\\\\foo.xml'), '->isAbsolutePath() returns true if the path is an absolute path'); +$t->ok(ProjectTemplateLoader::isAbsolutePath('c:/foo.xml'), '->isAbsolutePath() returns true if the path is an absolute path'); +$t->ok(ProjectTemplateLoader::isAbsolutePath('\\server\\foo.xml'), '->isAbsolutePath() returns true if the path is an absolute path'); + +// __construct() +$t->diag('__construct()'); +$pathPattern = $fixturesPath.'/templates/%name%.%renderer%'; +$path = $fixturesPath.'/templates'; +$loader = new ProjectTemplateLoader($pathPattern); +$t->is($loader->getTemplatePathPatterns(), array($pathPattern), '__construct() takes a path as its second argument'); +$loader = new ProjectTemplateLoader(array($pathPattern)); +$t->is($loader->getTemplatePathPatterns(), array($pathPattern), '__construct() takes an array of paths as its second argument'); + +// ->load() +$t->diag('->load()'); +$loader = new ProjectTemplateLoader($pathPattern); +$storage = $loader->load($path.'/foo.php'); +$t->ok($storage instanceof FileStorage, '->load() returns a FileStorage if you pass an absolute path'); +$t->is((string) $storage, $path.'/foo.php', '->load() returns a FileStorage pointing to the passed absolute path'); + +$t->ok($loader->load('bar') === false, '->load() returns false if the template is not found'); + +$storage = $loader->load('foo'); +$t->ok($storage instanceof FileStorage, '->load() returns a FileStorage if you pass a relative template that exists'); +$t->is((string) $storage, $path.'/foo.php', '->load() returns a FileStorage pointing to the absolute path of the template'); + +$loader = new ProjectTemplateLoader($pathPattern); +$loader->setDebugger($debugger = new ProjectTemplateDebugger()); +$t->ok($loader->load('foo', 'xml') === false, '->load() returns false if the template does not exists for the given renderer'); +$t->ok($debugger->hasMessage('Failed loading template'), '->load() logs a "Failed loading template" message if the template is not found'); + +$loader = new ProjectTemplateLoader(array($fixturesPath.'/null/%name%', $pathPattern)); +$loader->setDebugger($debugger = new ProjectTemplateDebugger()); +$loader->load('foo'); +$t->ok($debugger->hasMessage('Failed loading template'), '->load() logs a "Failed loading template" message if the template is not found'); +$t->ok($debugger->hasMessage('Loaded template file'), '->load() logs a "Loaded template file" message if the template is found'); diff --git a/tests/unit/Symfony/Components/Templating/Loader/LoaderTest.php b/tests/unit/Symfony/Components/Templating/Loader/LoaderTest.php new file mode 100644 index 000000000000..0e2a8ae22c28 --- /dev/null +++ b/tests/unit/Symfony/Components/Templating/Loader/LoaderTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +require_once __DIR__.'/../../../../../lib/SymfonyTests/Components/Templating/ProjectTemplateDebugger.php'; + +use Symfony\Components\Templating\Loader\Loader; + +$t = new LimeTest(1); + +class ProjectTemplateLoader extends Loader +{ + public function load($template, $renderer = 'php') + { + } + + public function getDebugger() + { + return $this->debugger; + } +} + +// ->setDebugger() +$t->diag('->setDebugger()'); +$loader = new ProjectTemplateLoader(); +$loader->setDebugger($debugger = new ProjectTemplateDebugger()); +$t->ok($loader->getDebugger() === $debugger, '->setDebugger() sets the debugger instance'); diff --git a/tests/unit/Symfony/Components/Templating/Renderer/PhpRendererTest.php b/tests/unit/Symfony/Components/Templating/Renderer/PhpRendererTest.php new file mode 100644 index 000000000000..9f40a38d2120 --- /dev/null +++ b/tests/unit/Symfony/Components/Templating/Renderer/PhpRendererTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\Templating\Renderer\PhpRenderer; +use Symfony\Components\Templating\Storage\Storage; +use Symfony\Components\Templating\Storage\StringStorage; +use Symfony\Components\Templating\Storage\FileStorage; + +$t = new LimeTest(2); + +$renderer = new PhpRenderer(); + +// ->evaluate() +$t->diag('->evaluate()'); + +$template = new StringStorage(''); +$t->is($renderer->evaluate($template, array('foo' => 'bar')), 'bar', '->evaluate() renders templates that are instances of StringStorage'); + +$template = new FileStorage(__DIR__.'/../../../../../fixtures/Symfony/Components/Templating/templates/foo.php'); +$t->is($renderer->evaluate($template, array('foo' => 'bar')), 'bar', '->evaluate() renders templates that are instances of FileStorage'); diff --git a/tests/unit/Symfony/Components/Templating/Renderer/RendererTest.php b/tests/unit/Symfony/Components/Templating/Renderer/RendererTest.php new file mode 100644 index 000000000000..427979e97f69 --- /dev/null +++ b/tests/unit/Symfony/Components/Templating/Renderer/RendererTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +require_once __DIR__.'/../../../../../lib/SymfonyTests/Components/Templating/SimpleHelper.php'; + +use Symfony\Components\Templating\Engine; +use Symfony\Components\Templating\Renderer\Renderer; +use Symfony\Components\Templating\Storage\Storage; +use Symfony\Components\Templating\Loader\FilesystemLoader; + +$t = new LimeTest(3); + +class ProjectTemplateRenderer extends Renderer +{ + public function getEngine() + { + return $this->engine; + } + + public function evaluate(Storage $template, array $parameters = array()) + { + } +} + +$loader = new FilesystemLoader(array(__DIR__.'/fixtures/templates/%name%.%renderer%')); +$engine = new Engine($loader); +$engine->set('foo', 'bar'); +$engine->getHelperSet()->set(new SimpleHelper('foo'), 'bar'); + +// ->setEngine() +$t->diag('->setEngine()'); +$renderer = new ProjectTemplateRenderer(); +$renderer->setEngine($engine); +$t->ok($renderer->getEngine() === $engine, '->setEngine() sets the engine instance tied to this renderer'); + +// __call() +$t->diag('__call()'); +$renderer = new ProjectTemplateRenderer(); +$renderer->setEngine($engine); +$t->is($renderer->get('foo'), 'bar', '__call() proxies to the embedded engine instance'); + +// __get() +$t->diag('__get()'); +$renderer = new ProjectTemplateRenderer(); +$renderer->setEngine($engine); +$t->is((string) $renderer->bar, 'foo', '__get() proxies to the embedded engine instance'); diff --git a/tests/unit/Symfony/Components/Templating/Storage/FileStorageTest.php b/tests/unit/Symfony/Components/Templating/Storage/FileStorageTest.php new file mode 100644 index 000000000000..314e6bf2525a --- /dev/null +++ b/tests/unit/Symfony/Components/Templating/Storage/FileStorageTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\Templating\Storage\Storage; +use Symfony\Components\Templating\Storage\FileStorage; + +$t = new LimeTest(1); + +$storage = new FileStorage('foo'); +$t->ok($storage instanceof Storage, 'FileStorage is an instance of Storage'); diff --git a/tests/unit/Symfony/Components/Templating/Storage/StorageTest.php b/tests/unit/Symfony/Components/Templating/Storage/StorageTest.php new file mode 100644 index 000000000000..ad7de8d10469 --- /dev/null +++ b/tests/unit/Symfony/Components/Templating/Storage/StorageTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\Templating\Storage\Storage; +use Symfony\Components\Templating\Renderer\PhpRenderer; + +$t = new LimeTest(2); + +// __construct() __toString() +$t->diag('__construct() __toString()'); + +$storage = new Storage('foo'); +$t->is((string) $storage, 'foo', '__toString() returns the template name'); + +// ->getRenderer() +$t->diag('->getRenderer()'); +$storage = new Storage('foo', $renderer = new PhpRenderer()); +$t->ok($storage->getRenderer() === $renderer, '->getRenderer() returns the renderer'); diff --git a/tests/unit/Symfony/Components/Templating/Storage/StringStorageTest.php b/tests/unit/Symfony/Components/Templating/Storage/StringStorageTest.php new file mode 100644 index 000000000000..86f2c78f8208 --- /dev/null +++ b/tests/unit/Symfony/Components/Templating/Storage/StringStorageTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../../bootstrap.php'; + +use Symfony\Components\Templating\Storage\Storage; +use Symfony\Components\Templating\Storage\StringStorage; + +$t = new LimeTest(1); + +$storage = new StringStorage('foo'); +$t->ok($storage instanceof Storage, 'StringStorage is an instance of Storage'); diff --git a/tests/unit/Symfony/Components/YAML/DumperTest.php b/tests/unit/Symfony/Components/YAML/DumperTest.php new file mode 100644 index 000000000000..dceaebdece22 --- /dev/null +++ b/tests/unit/Symfony/Components/YAML/DumperTest.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +use Symfony\Components\YAML\YAML; +use Symfony\Components\YAML\Parser; +use Symfony\Components\YAML\Dumper; + +YAML::setSpecVersion('1.1'); + +$t = new LimeTest(148); + +$parser = new Parser(); +$dumper = new Dumper(); + +$path = __DIR__.'/../../../../fixtures/Symfony/Components/YAML'; +$files = $parser->parse(file_get_contents($path.'/index.yml')); +foreach ($files as $file) +{ + $t->diag($file); + + $yamls = file_get_contents($path.'/'.$file.'.yml'); + + // split YAMLs documents + foreach (preg_split('/^---( %YAML\:1\.0)?/m', $yamls) as $yaml) + { + if (!$yaml) + { + continue; + } + + $test = $parser->parse($yaml); + if (isset($test['dump_skip']) && $test['dump_skip']) + { + continue; + } + else if (isset($test['todo']) && $test['todo']) + { + $t->todo($test['test']); + } + else + { + $expected = eval('return '.trim($test['php']).';'); + + $t->is($parser->parse($dumper->dump($expected, 10)), $expected, $test['test']); + } + } +} + +// inline level +$array = array( + '' => 'bar', + 'foo' => '#bar', + 'foo\'bar' => array(), + 'bar' => array(1, 'foo'), + 'foobar' => array( + 'foo' => 'bar', + 'bar' => array(1, 'foo'), + 'foobar' => array( + 'foo' => 'bar', + 'bar' => array(1, 'foo'), + ), + ), +); + +$expected = <<is($dumper->dump($array, -10), $expected, '->dump() takes an inline level argument'); +$t->is($dumper->dump($array, 0), $expected, '->dump() takes an inline level argument'); + +$expected = <<is($dumper->dump($array, 1), $expected, '->dump() takes an inline level argument'); + +$expected = <<is($dumper->dump($array, 2), $expected, '->dump() takes an inline level argument'); + +$expected = <<is($dumper->dump($array, 3), $expected, '->dump() takes an inline level argument'); + +$expected = <<is($dumper->dump($array, 4), $expected, '->dump() takes an inline level argument'); +$t->is($dumper->dump($array, 10), $expected, '->dump() takes an inline level argument'); + +// objects +$t->diag('Objects support'); +class A +{ + public $a = 'foo'; +} +$a = array('foo' => new A(), 'bar' => 1); +$t->is($dumper->dump($a), '{ foo: !!php/object:O:1:"A":1:{s:1:"a";s:3:"foo";}, bar: 1 }', '->dump() is able to dump objects'); diff --git a/tests/unit/Symfony/Components/YAML/InlineTest.php b/tests/unit/Symfony/Components/YAML/InlineTest.php new file mode 100644 index 000000000000..72842ee0c804 --- /dev/null +++ b/tests/unit/Symfony/Components/YAML/InlineTest.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +use Symfony\Components\YAML\YAML; +use Symfony\Components\YAML\Inline; + +YAML::setSpecVersion('1.1'); + +$t = new LimeTest(124); + +// ::load() +$t->diag('::load()'); + +$testsForLoad = array( + '' => '', + 'null' => null, + 'false' => false, + 'true' => true, + '12' => 12, + '"quoted string"' => 'quoted string', + "'quoted string'" => 'quoted string', + '12.30e+02' => 12.30e+02, + '0x4D2' => 0x4D2, + '02333' => 02333, + '.Inf' => -log(0), + '-.Inf' => log(0), + '123456789123456789' => '123456789123456789', + '"foo\r\nbar"' => "foo\r\nbar", + "'foo#bar'" => 'foo#bar', + "'foo # bar'" => 'foo # bar', + "'#cfcfcf'" => '#cfcfcf', + + '2007-10-30' => mktime(0, 0, 0, 10, 30, 2007), + '2007-10-30T02:59:43Z' => gmmktime(2, 59, 43, 10, 30, 2007), + '2007-10-30 02:59:43 Z' => gmmktime(2, 59, 43, 10, 30, 2007), + + '"a \\"string\\" with \'quoted strings inside\'"' => 'a "string" with \'quoted strings inside\'', + "'a \"string\" with ''quoted strings inside'''" => 'a "string" with \'quoted strings inside\'', + + // sequences + // urls are no key value mapping. see #3609. Valid yaml "key: value" mappings require a space after the colon + '[foo, http://urls.are/no/mappings, false, null, 12]' => array('foo', 'http://urls.are/no/mappings', false, null, 12), + '[ foo , bar , false , null , 12 ]' => array('foo', 'bar', false, null, 12), + '[\'foo,bar\', \'foo bar\']' => array('foo,bar', 'foo bar'), + + // mappings + '{foo:bar,bar:foo,false:false,null:null,integer:12}' => array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12), + '{ foo : bar, bar : foo, false : false, null : null, integer : 12 }' => array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12), + '{foo: \'bar\', bar: \'foo: bar\'}' => array('foo' => 'bar', 'bar' => 'foo: bar'), + '{\'foo\': \'bar\', "bar": \'foo: bar\'}' => array('foo' => 'bar', 'bar' => 'foo: bar'), + '{\'foo\'\'\': \'bar\', "bar\"": \'foo: bar\'}' => array('foo\'' => 'bar', "bar\"" => 'foo: bar'), + '{\'foo: \': \'bar\', "bar: ": \'foo: bar\'}' => array('foo: ' => 'bar', "bar: " => 'foo: bar'), + + // nested sequences and mappings + '[foo, [bar, foo]]' => array('foo', array('bar', 'foo')), + '[foo, {bar: foo}]' => array('foo', array('bar' => 'foo')), + '{ foo: {bar: foo} }' => array('foo' => array('bar' => 'foo')), + '{ foo: [bar, foo] }' => array('foo' => array('bar', 'foo')), + + '[ foo, [ bar, foo ] ]' => array('foo', array('bar', 'foo')), + + '[{ foo: {bar: foo} }]' => array(array('foo' => array('bar' => 'foo'))), + + '[foo, [bar, [foo, [bar, foo]], foo]]' => array('foo', array('bar', array('foo', array('bar', 'foo')), 'foo')), + + '[foo, {bar: foo, foo: [foo, {bar: foo}]}, [foo, {bar: foo}]]' => array('foo', array('bar' => 'foo', 'foo' => array('foo', array('bar' => 'foo'))), array('foo', array('bar' => 'foo'))), + + '[foo, bar: { foo: bar }]' => array('foo', '1' => array('bar' => array('foo' => 'bar'))), +); + +foreach ($testsForLoad as $yaml => $value) +{ + $t->is(Inline::load($yaml), $value, sprintf('::load() converts an inline YAML to a PHP structure (%s)', $yaml)); +} + +$testsForDump = array( + 'null' => null, + 'false' => false, + 'true' => true, + '12' => 12, + "'quoted string'" => 'quoted string', + '12.30e+02' => 12.30e+02, + '1234' => 0x4D2, + '1243' => 02333, + '.Inf' => -log(0), + '-.Inf' => log(0), + '"foo\r\nbar"' => "foo\r\nbar", + "'foo#bar'" => 'foo#bar', + "'foo # bar'" => 'foo # bar', + "'#cfcfcf'" => '#cfcfcf', + + "'a \"string\" with ''quoted strings inside'''" => 'a "string" with \'quoted strings inside\'', + + // sequences + '[foo, bar, false, null, 12]' => array('foo', 'bar', false, null, 12), + '[\'foo,bar\', \'foo bar\']' => array('foo,bar', 'foo bar'), + + // mappings + '{ foo: bar, bar: foo, \'false\': false, \'null\': null, integer: 12 }' => array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12), + '{ foo: bar, bar: \'foo: bar\' }' => array('foo' => 'bar', 'bar' => 'foo: bar'), + + // nested sequences and mappings + '[foo, [bar, foo]]' => array('foo', array('bar', 'foo')), + + '[foo, [bar, [foo, [bar, foo]], foo]]' => array('foo', array('bar', array('foo', array('bar', 'foo')), 'foo')), + + '{ foo: { bar: foo } }' => array('foo' => array('bar' => 'foo')), + + '[foo, { bar: foo }]' => array('foo', array('bar' => 'foo')), + + '[foo, { bar: foo, foo: [foo, { bar: foo }] }, [foo, { bar: foo }]]' => array('foo', array('bar' => 'foo', 'foo' => array('foo', array('bar' => 'foo'))), array('foo', array('bar' => 'foo'))), +); + +// ::dump() +$t->diag('::dump()'); +foreach ($testsForDump as $yaml => $value) +{ + $t->is(Inline::dump($value), $yaml, sprintf('::dump() converts a PHP structure to an inline YAML (%s)', $yaml)); +} + +foreach ($testsForLoad as $yaml => $value) +{ + if ($value == 1230) + { + continue; + } + + $t->is(Inline::load(Inline::dump($value)), $value, 'check consistency'); +} + +foreach ($testsForDump as $yaml => $value) +{ + if ($value == 1230) + { + continue; + } + + $t->is(Inline::load(Inline::dump($value)), $value, 'check consistency'); +} diff --git a/tests/unit/Symfony/Components/YAML/ParserTest.php b/tests/unit/Symfony/Components/YAML/ParserTest.php new file mode 100644 index 000000000000..34ccc5bef6fc --- /dev/null +++ b/tests/unit/Symfony/Components/YAML/ParserTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../../../bootstrap.php'; + +use Symfony\Components\YAML\YAML; +use Symfony\Components\YAML\Parser; + +YAML::setSpecVersion('1.1'); + +$t = new LimeTest(148); + +$parser = new Parser(); + +$path = __DIR__.'/../../../../fixtures/Symfony/Components/YAML'; +$files = $parser->parse(file_get_contents($path.'/index.yml')); +foreach ($files as $file) +{ + $t->diag($file); + + $yamls = file_get_contents($path.'/'.$file.'.yml'); + + // split YAMLs documents + foreach (preg_split('/^---( %YAML\:1\.0)?/m', $yamls) as $yaml) + { + if (!$yaml) + { + continue; + } + + $test = $parser->parse($yaml); + if (isset($test['todo']) && $test['todo']) + { + $t->todo($test['test']); + } + else + { + $expected = var_export(eval('return '.trim($test['php']).';'), true); + + $t->is(var_export($parser->parse($test['yaml']), true), $expected, $test['test']); + } + } +} + +// test tabs in YAML +$yamls = array( + "foo:\n bar", + "foo:\n bar", + "foo:\n bar", + "foo:\n bar", +); + +foreach ($yamls as $yaml) +{ + try + { + $content = $parser->parse($yaml); + $t->fail('YAML files must not contain tabs'); + } + catch (InvalidArgumentException $e) + { + $t->pass('YAML files must not contain tabs'); + } +} + +// objects +$t->diag('Objects support'); +class A +{ + public $a = 'foo'; +} +$a = array('foo' => new A(), 'bar' => 1); +$t->is($parser->parse(<<parse() is able to dump objects'); diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php new file mode 100644 index 000000000000..32a6688314ee --- /dev/null +++ b/tests/unit/bootstrap.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../lib/vendor/lime/LimeAutoloader.php'; +LimeAutoloader::register(); + +require_once __DIR__.'/../../src/Symfony/Foundation/ClassLoader.php'; +$loader = new Symfony\Foundation\ClassLoader(); +$loader->registerNamespace('Symfony', __DIR__.'/../../src'); +$loader->register();