diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 6e5b032..2eda52d 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: - php-versions: ['8.2', '8.3'] + php-versions: ['8.3', '8.4'] steps: - name: Checkout diff --git a/.gitignore b/.gitignore index fdbb5a5..68e5859 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ build ./composer.lock var /docs/_site/ +/examples/**.csv +/examples/00-SimpleCases/*.csv diff --git a/.phpunit.result.cache b/.phpunit.result.cache new file mode 100644 index 0000000..9fd033b --- /dev/null +++ b/.phpunit.result.cache @@ -0,0 +1 @@ +{"version":1,"defects":{"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainRepeatOperationTest::testRepeatDataItem":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainRepeatOperationTest::testRepeatGroupedItem":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainRepeatOperationTest::testAsyncDisabled":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainRepeatOperationTest::testAsyncEnabled":3,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainSplitOperationTest::testDataProcessing":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainSplitOperationTest::testStopProcess":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\FailSafeOperationTest::testSingleFail":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\FailSafeOperationTest::testToManyFail":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Loader\\FileWriterOperationTest::testWrite":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Loader\\FileWriterOperationTest::testTmpFileDeleted":4,"ChainOperation\\SimpleHttpOperationTest::testTimeout":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\RuleTransformOperationTest::testMultiColumnRulesAddingData":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\RuleTransformOperationTest::testMultiColumnRulesReplacingData":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\RuleTransformOperationTest::testDynamicColumn":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\RuleTransformOperationTest::testMultipleDynamic":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\RuleTransformOperationTest::testSimpleDynamic":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testSimpleChain":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testChainBreak":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testSingleGrouping":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testDoubleGrouping":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testMixItems":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testAsyncItems":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testReturnOfAsyncItems":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testMaxAsyncItems":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testAsyncItemsWithMixItem":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testAsyncDisabledWithSplit":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testException":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderV2Test::testCreateChainWithNoFactories":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderV2Test::testCreateChainWithMaxAsynchronousItems":4,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testSupportsReturnsTrueForMatchingConfigAndFlavor":4,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testSupportsReturnsFalseForDifferentConfigClass":4,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testSupportsReturnsFalseForDifferentFlavor":4,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testSupportsReturnsTrueForMatchingSubclass":4,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildCreatesOperationWithConfig":4,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildInjectsChainBuilder":4,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildInjectsCustomDependencies":4,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildInjectsFlavor":4,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildUsesDefaultParameterValues":4,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildThrowsExceptionForMissingParameter":4,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildInjectsMultipleParameters":4,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildWithNoConstructor":4,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testSupportsChecksFlavorMatch":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testGroupBySingleKey":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testGroupByNestedKey":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testGroupWithIdentifierKey":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testGroupWithNestedIdentifierKey":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testMultipleGroupsWithoutIdentifier":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testDataClearedAfterStop":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testMultipleStopCycles":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testGroupingPreservesAllDataFields":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testOverwriteWithSameIdentifier":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testNumericGroupKeys":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractCsvWithDefaultDelimiter":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractCsvWithCustomDelimiter":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractCsvWithCustomEnclosure":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractCsvFromArrayWithFileKey":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractCsvWithNestedFileKey":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractEmptyCsv":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractCsvWithQuotedFields":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testFileExtractedItemContainsCorrectFilename":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractMultipleRows":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractCsvWithEscapeCharacter":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testExtractFromArrayWithFileKey":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testExtractWithNestedFileKey":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testFileExtractedItemContainsOriginalData":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testFileExtractedItemWithArrayInput":4,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainMergeOperationTest::testProcessStopCallsAllChains":3},"times":{"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderTest::testSimpleBuild":0.005,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderTest::testBuildWihMultipleSameOperation":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderTest::testWithVariableOptions":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderTest::testUnknownOperation":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainRepeatOperationTest::testRepeatDataItem":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainRepeatOperationTest::testRepeatGroupedItem":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainRepeatOperationTest::testAsyncDisabled":3.584,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainRepeatOperationTest::testAsyncEnabled":6,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainSplitOperationTest::testDataProcessing":0.006,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainSplitOperationTest::testStopProcess":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\FailSafeOperationTest::testSingleFail":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\FailSafeOperationTest::testToManyFail":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Loader\\FileWriterOperationTest::testWrite":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Loader\\FileWriterOperationTest::testTmpFileDeleted":0.001,"ChainOperation\\SimpleHttpOperationTest::testTimeout":76.017,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\RuleTransformOperationTest::testMultiColumnRulesAddingData":0.003,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\RuleTransformOperationTest::testMultiColumnRulesReplacingData":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\RuleTransformOperationTest::testDynamicColumn":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\RuleTransformOperationTest::testMultipleDynamic":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\RuleTransformOperationTest::testSimpleDynamic":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testSimpleChain":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testChainBreak":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testSingleGrouping":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testDoubleGrouping":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testMixItems":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testAsyncItems":1.931,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testReturnOfAsyncItems":2,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testMaxAsyncItems":4.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testAsyncItemsWithMixItem":1.999,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testAsyncDisabledWithSplit":3.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainProcessorTest::testException":0.002,"Oliverde8\\Component\\RuleEngine\\Tests\\RuleApplierTest::testApplyingSimpleRule":0.002,"Oliverde8\\Component\\RuleEngine\\Tests\\RuleApplierTest::testApplyConstantValue":0,"Oliverde8\\Component\\RuleEngine\\Tests\\RuleApplierTest::testApplyConsecutiveRules":0,"Oliverde8\\Component\\RuleEngine\\Tests\\RuleApplierTest::testApplyConsecutiveRulesWithConstant":0,"Oliverde8\\Component\\RuleEngine\\Tests\\RuleApplierTest::testNoResults":0,"Oliverde8\\Component\\RuleEngine\\Tests\\RuleApplierTest::testUnknownRule":0.001,"Oliverde8\\Component\\RuleEngine\\Tests\\RuleApplierTest::testValidationError":0.001,"Oliverde8\\Component\\RuleEngine\\Tests\\Rules\\ConstantTest::testConstants":0.001,"Oliverde8\\Component\\RuleEngine\\Tests\\Rules\\ConstantTest::testRuleCode":0,"Oliverde8\\Component\\RuleEngine\\Tests\\Rules\\GetTest::testWithoutLocale":0,"Oliverde8\\Component\\RuleEngine\\Tests\\Rules\\GetTest::testUnfound":0,"Oliverde8\\Component\\RuleEngine\\Tests\\Rules\\GetTest::testRuleCode":0,"Oliverde8\\Component\\RuleEngine\\Tests\\Rules\\ImplodeTest::testImplode":0,"Oliverde8\\Component\\RuleEngine\\Tests\\Rules\\ImplodeTest::testTwoLevelImplode":0,"Oliverde8\\Component\\RuleEngine\\Tests\\Rules\\ImplodeTest::testFourLevelImplode":0,"Oliverde8\\Component\\RuleEngine\\Tests\\Rules\\ImplodeTest::testRuleCode":0,"Oliverde8\\Component\\RuleEngine\\Tests\\Rules\\StrToLowerTest::testStrToLower":0,"Oliverde8\\Component\\RuleEngine\\Tests\\Rules\\StrToLowerTest::testRuleCode":0,"Oliverde8\\Component\\RuleEngine\\Tests\\Rules\\StrToUpperTest::testStrToUpper":0,"Oliverde8\\Component\\RuleEngine\\Tests\\Rules\\StrToUpperTest::testRuleCode":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderV2Test::testCreateChainWithNoFactories":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderV2Test::testCreateChainWithSingleOperation":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderV2Test::testCreateChainWithMultipleOperations":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderV2Test::testCreateChainWithMultipleFactories":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderV2Test::testCreateChainWithFactoryNotSupporting":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderV2Test::testCreateChainWithNoMatchingFactory":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderV2Test::testCreateChainWithMaxAsynchronousItems":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderV2Test::testFactoryReceivesChainBuilderInstance":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderV2Test::testCreateChainWithIterableFactories":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainBuilderV2Test::testCreateChainPreservesOperationOrder":0,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testConstructorThrowsExceptionForNonConfigurableOperation":0.002,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testSupportsReturnsTrueForMatchingConfigAndFlavor":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testSupportsReturnsFalseForDifferentConfigClass":0,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testSupportsReturnsFalseForDifferentFlavor":0,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testSupportsReturnsTrueForMatchingSubclass":0,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildCreatesOperationWithConfig":0,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildInjectsChainBuilder":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildInjectsCustomDependencies":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildInjectsFlavor":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildUsesDefaultParameterValues":0,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildThrowsExceptionForMissingParameter":0,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildInjectsMultipleParameters":0,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testBuildWithNoConstructor":0,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testSupportsChecksFlavorMatch":0,"Oliverde8\\Component\\PhpEtl\\Tests\\GenericChainFactoryTest::testConstructorValidatesOperationClass":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testGroupBySingleKey":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testGroupByNestedKey":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testGroupWithIdentifierKey":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testGroupWithNestedIdentifierKey":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testMultipleGroupsWithoutIdentifier":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testEmptyDataReturnsStopItem":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testDataClearedAfterStop":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testMultipleStopCycles":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testGroupingPreservesAllDataFields":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testOverwriteWithSameIdentifier":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Grouping\\SimpleGroupingOperationTest::testNumericGroupKeys":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractCsvWithDefaultDelimiter":0.005,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractCsvWithCustomDelimiter":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractCsvWithCustomEnclosure":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractCsvFromArrayWithFileKey":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractCsvWithNestedFileKey":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractEmptyCsv":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractCsvWithQuotedFields":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testFileExtractedItemContainsCorrectFilename":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractMultipleRows":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testExtractCsvWithEscapeCharacter":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\CsvExtractOperationTest::testGetConfigurationClass":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testExtractSimpleJsonArray":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testExtractJsonObject":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testExtractFromArrayWithFileKey":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testExtractWithNestedFileKey":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testExtractEmptyJsonArray":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testExtractNestedJsonStructure":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testExtractJsonWithSpecialCharacters":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testFileExtractedItemContainsOriginalData":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testFileExtractedItemWithArrayInput":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testExtractJsonWithNumericKeys":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testExtractJsonWithBooleanAndNull":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\JsonExtractOperationTest::testExtractLargeJsonArray":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\ExternalFileFinderOperationTest::testFindFilesWithSimplePattern":0.001,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\ExternalFileFinderOperationTest::testFindFilesWithNoMatches":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\ExternalFileFinderOperationTest::testFindFilesWithComplexPattern":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\ExternalFileFinderOperationTest::testFindFilesInEmptyDirectory":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\ExternalFileFinderOperationTest::testFindAllFilesWithMatchAllPattern":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\ExternalFileFinderOperationTest::testFileSystemIsPassedToExternalFileItem":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\ExternalFileFinderOperationTest::testExternalFileItemInitialState":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\ExternalFileFinderOperationTest::testFindFilesWithCaseInsensitivePattern":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\ExternalFileFinderOperationTest::testFindFilesWithNumericPattern":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\ExternalFileFinderOperationTest::testDirectoryWithExpression":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\ExternalFileFinderOperationTest::testDirectoryExpressionWithComplexPath":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\ExternalFileFinderOperationTest::testFindFilesWithSpecialCharactersInName":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Extract\\ExternalFileFinderOperationTest::testMultipleExtensionsPattern":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\ExternalFileProcessorOperationTest::testProcessNewFileMovesToProcessing":0.005,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\ExternalFileProcessorOperationTest::testProcessProcessingFileMovesToProcessed":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\ExternalFileProcessorOperationTest::testProcessNewFileCreatesLocalCopy":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\ExternalFileProcessorOperationTest::testProcessFileWithNestedDirectory":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\ExternalFileProcessorOperationTest::testProcessFilePreservesFilename":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\ExternalFileProcessorOperationTest::testProcessMultipleFilesInSequence":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\ExternalFileProcessorOperationTest::testNewFileResultContainsDataItemAndExternalFileItem":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\ExternalFileProcessorOperationTest::testProcessingFileOnlyReturnsExternalFileItem":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\ExternalFileProcessorOperationTest::testProcessFileCreatesProcessingDirectory":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\ExternalFileProcessorOperationTest::testProcessFileCreatesProcessedDirectory":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\ExternalFileProcessorOperationTest::testStateTransitionFromNewToProcessing":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\ExternalFileProcessorOperationTest::testStateTransitionFromProcessingToProcessed":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\Transformer\\ExternalFileProcessorOperationTest::testFilenameExtraction":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainMergeOperationTest::testMergeTwoChains":0.002,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainMergeOperationTest::testMergeThreeChains":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainMergeOperationTest::testMergeWithMultipleItemsPerChain":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainMergeOperationTest::testMergePreservesOrder":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainMergeOperationTest::testProcessStopCallsAllChains":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainMergeOperationTest::testMergeWithEmptyChain":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainMergeOperationTest::testGetChainProcessors":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainMergeOperationTest::testMergeWithDifferentDataTypes":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainMergeOperationTest::testMergeWithChainTransformations":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainMergeOperationTest::testGetConfigurationClass":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainMergeOperationTest::testMergeDoesNotStopOnFirstChain":0,"Oliverde8\\Component\\PhpEtl\\Tests\\ChainOperation\\ChainMergeOperationTest::testMergeWithComplexChainOperations":0}} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1977fba..0000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -language: php -php: - - '8.1' - - '8.2' - - '8.3'' - -env: - - SYMFONY_DEPRECATIONS_HELPER=disabled - -before_script: - - composer self-update - - composer install - -script: - - ./vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover=coverage.clover - -notifications: - email: false - -after_script: - - wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover coverage.clover diff --git a/CHANGELOG.md b/CHANGELOG.md index cd71985..b31a13c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # š 2.0.0 š +- :star2: - **NEW PARADIGM** - use php for configurations instead of yaml (**Breaking Change**) - :star2: - Complete rewrite of the ChainProcessor - :star2: - Optimized chain processor not to send as many stop items when multiple chains are involved - :star2: - Allows ChainProcessor to output through generators the items at the end. This is great to remove all limitations of the current sub chains. diff --git a/composer.json b/composer.json index d7b01ae..42d855d 100644 --- a/composer.json +++ b/composer.json @@ -4,20 +4,20 @@ "license": "MIT", "description": "A small etl coded in php, to extract/transform/load Edit with ease.", "require": { - "php": ">=8.2", + "php": ">=8.3", "oliverde8/associative-array-simplified": "^1.0", "psr/log": "^2.0|^3.0", - "symfony/expression-language": "^6.0|^7.0", - "symfony/filesystem": "^6.0|^7.0", - "symfony/validator": "^6.0|^7.0", - "symfony/yaml": "^6.0|^7.0" + "symfony/expression-language": "^6.4|^7.4|^8.0", + "symfony/filesystem": "^6.4|^7.4|^8.0", + "symfony/validator": "^6.4|^7.4|^8.0", + "symfony/yaml": "^6.4|^7.4|^8.0" }, "require-dev": { "phpunit/php-code-coverage": "^9.2", "phpunit/phpunit": "^9.6", "rector/rector": "^1.2", - "symfony/console": "^7.1", - "symfony/http-client": "^7.2" + "symfony/console": "^7.0|^8.0", + "symfony/http-client": "^6.4|^7.4|^8.0" }, "suggest": { "league/flysystem": "For transfaring files from external file systems", diff --git a/docs/_includes/doc/getting-started/usage-symfony.md b/docs/_includes/doc/getting-started/usage-symfony.md index 2c30ab6..c5a4804 100644 --- a/docs/_includes/doc/getting-started/usage-symfony.md +++ b/docs/_includes/doc/getting-started/usage-symfony.md @@ -1,39 +1,260 @@ #### Creating an ETL chain -Each chain is declared in a single file. The name of the chain is the name of the file created in `/config/etl/`. +To create an ETL chain in Symfony, you need to create a service that implements `ChainDefinitionInterface`. +The chain is built using typed PHP configuration objects. + +**1. Create a chain definition service:** + +```php +addLink(new CsvExtractConfig()) + ->addLink((new RuleTransformConfig(false)) + ->addColumn('customer_id', [['get' => ['field' => 'ID']]]) + ->addColumn('full_name', [ + ['implode' => [ + 'values' => [ + [['get' => ['field' => 'FirstName']]], + [['get' => ['field' => 'LastName']]], + ], + 'with' => ' ', + ]] + ]) + ->addColumn('email', [['get' => ['field' => 'Email']]]) + ) + ->addLink(new CsvFileWriterConfig('output/customers.csv')); + } +} +``` + +**2. Register the service** (if not using autoconfigure): + +The interface has the `#[AutoconfigureTag('etl.chain_definition')]` attribute, so services implementing it are automatically tagged when autoconfigure is enabled (default in Symfony). + +```yaml +services: + App\Etl\ChainDefinition\CustomerImportDefinition: + tags: ['etl.chain_definition'] +``` + +**3. Configure maxAsynchronousItems and other chain settings:** + +```php +class HighVolumeImportDefinition implements ChainDefinitionInterface +{ + public function getKey(): string + { + return 'high-volume-import'; + } + + public function build(): ChainConfig + { + $chainConfig = new ChainConfig(); + $chainConfig->setMaxAsynchronousItems(100); // Process up to 100 items in parallel + + return $chainConfig + ->addLink(new CsvExtractConfig()) + ->addLink((new RuleTransformConfig(false)) + ->addColumn('id', [['get' => ['field' => 'ID']]]) + ) + ->addLink(new CsvFileWriterConfig('output/processed.csv')); + } +} +``` + +**4. You can also inject dependencies** into your chain definition: + +```php +class ApiImportDefinition implements ChainDefinitionInterface +{ + public function __construct( + private string $apiUrl, + ) {} + + public function getKey(): string + { + return 'api-import'; + } + + public function build(): ChainConfig + { + return (new ChainConfig()) + ->addLink(new SimpleHttpConfig( + url: $this->apiUrl, + method: 'GET', + responseIsJson: true + )) + ->addLink(new LogConfig( + message: 'Imported record', + level: 'info' + )) + ->addLink(new CsvFileWriterConfig('output/api-data.csv')); + } +} +``` + +#### Creating custom operations + +Custom operations are automatically registered when they implement `ConfigurableChainOperationInterface`. The bundle's compiler pass discovers them and sets up dependency injection automatically. + +**1. Create a config class:** + +```php +logger->info('Processing item', ['field' => $this->config->targetField]); + + // Your transformation logic here + $item[$this->config->targetField] = strtoupper($item[$this->config->targetField] ?? ''); + + $output[] = $item; + } +} +``` + +The operation is automatically registered and all dependencies (except `$config`, `$chainBuilder`, and `$flavor`) are auto-injected using Symfony's autowiring. + +**3. Use it in your chain:** + +```php +class MyChainDefinition implements ChainDefinitionInterface +{ + public function getKey(): string + { + return 'my-custom-chain'; + } + + public function build(): ChainConfig + { + return (new ChainConfig()) + ->addLink(new CsvExtractConfig()) + ->addLink(new CustomTransformConfig('email', 'uppercase')) + ->addLink(new CsvFileWriterConfig('output/transformed.csv')); + } +} +``` + +**How automatic dependency injection works:** + +The `ChainBuilderV2Compiler` compiler pass: +- Discovers all services implementing `ConfigurableChainOperationInterface` +- Identifies the config class from the constructor (must implement `OperationConfigInterface`) +- Resolves all constructor dependencies at compile time using Symfony's dependency injection +- Creates a `GenericChainFactory` for each operation with resolved dependencies +- Automatically skips injection for `$config`, `$chainBuilder`, and `$flavor` (handled by the factory) + +**Manual dependency injection:** + +If you need more control over dependency injection, you can configure arguments manually: -Example: ```yaml -chain: - "Dummy Step": - operation: rule-engine-transformer - options: - add: true - columns: - test: - rules: - - get : {field: [0, 'uid']} +services: + App\Etl\Operation\CustomTransformOperation: + arguments: + $logger: '@monolog.logger.etl' ``` +Or use the `#[Autowire]` attribute in PHP 8.1+: + +```php +use Symfony\Component\DependencyInjection\Attribute\Autowire; + +class CustomTransformOperation implements ConfigurableChainOperationInterface +{ + public function __construct( + private CustomTransformConfig $config, + private ChainBuilderV2 $chainBuilder, + private string $flavor, + #[Autowire(service: 'monolog.logger.etl')] + private LoggerInterface $logger, + #[Autowire('%app.etl.batch_size%')] + private int $batchSize, + ) {} +} +``` + +The compiler pass respects: +- Manually configured service arguments +- `#[Autowire]` attributes on constructor parameters +- Default parameter values +- Nullable parameters + #### Executing a chain ```sh -./bin/console etl:execute demo '[["test1"],["test2"]]' '{"opt1": "val1"}' +./bin/console etl:execute customer-import '[["test1"],["test2"]]' '{"opt1": "val1"}' ``` -The first argument is the input, depending on your chain it can be empty. -The second are parameters that will be available in the context of each link in the chain. +The first argument is the chain key (returned by `getKey()`). +The second argument is the input data, depending on your chain it can be empty or a JSON array. +The third argument contains parameters that will be available in the execution context. #### Get a definition ```sh -./bin/console etl:get-definition demo +./bin/console etl:get-definition customer-import ``` +This displays the chain configuration and all its operations. + #### Get definition graph ```sh -./bin/console etl:definition:graph demo +./bin/console etl:definition:graph customer-import ``` -This will return a mermaid graph. Adding a `-u` will return the url to the mermaid graph image. \ No newline at end of file +This returns a Mermaid graph visualization of your ETL chain. Adding `-u` will return the URL to the Mermaid graph image. diff --git a/docs/_includes/menu.html b/docs/_includes/menu.html index e72357b..7a18fbb 100644 --- a/docs/_includes/menu.html +++ b/docs/_includes/menu.html @@ -111,6 +111,9 @@ Api to CSV N°1 Api to CSV N°2 Sub chains + Api with Pagination + Error handling + Merge multiple source of data