├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin ├── etl └── pimple-etl ├── composer.json ├── composer.lock ├── config.sample.php ├── spec └── Knp │ └── ETL │ ├── Extractor │ ├── CsvExtractorSpec.php │ ├── ExcelExtractorSpec.php │ ├── Fixture │ │ ├── TransformableEntity.csv │ │ ├── TransformableEntity.json │ │ └── TransformableEntity.xls │ └── JsonExtractorSpec.php │ ├── Loader │ ├── CsvLoaderSpec.php │ ├── Doctrine │ │ ├── DBALLoaderSpec.php │ │ └── ORMLoaderSpec.php │ └── FileLoaderSpec.php │ └── Transformer │ ├── CallbackTransformerSpec.php │ ├── DataMapSpec.php │ └── Doctrine │ ├── Fixture │ └── TransformableEntity.php │ └── ObjectTransformerSpec.php └── src └── Knp └── ETL ├── Context ├── Context.php └── Doctrine │ ├── DBALContext.php │ └── ORMContext.php ├── ContextInterface.php ├── Extractor ├── CachedExtractor.php ├── CsvExtractor.php ├── Doctrine │ ├── ORMExtractor.php │ └── SliceableORMExtractor.php ├── ExcelExtractor.php ├── JsonExtractor.php ├── OrderedCsvExtractor.php └── RegexFilenameCsvExtractor.php ├── ExtractorInterface.php ├── Loader ├── CsvLoader.php ├── Doctrine │ ├── DBALLoader.php │ ├── ORMLoader.php │ └── Registry.php └── FileLoader.php ├── LoaderInterface.php ├── Transformer ├── CallbackTransformer.php ├── DataMap.php └── Doctrine │ └── ObjectTransformer.php └── TransformerInterface.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | bin 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.5 5 | - 5.6 6 | - 7.0 7 | - hhvm 8 | 9 | before_script: 10 | - composer install 11 | 12 | script: bin/phpspec run -fpretty 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 (c) Contributors at https://github.com/KnpLabs/php-etl/contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | php-etl 2 | ======= 3 | 4 | [![Build Status](https://travis-ci.org/docteurklein/php-etl.png?branch=master)](https://travis-ci.org/docteurklein/php-etl) 5 | 6 | php-etl is a PHP 5.5+ library that follows the well-known `Extract | Transform | Load` pattern. 7 | 8 | It provides a few extractors, a few transformers and a few loaders to import csv data into a RDBM for example. 9 | 10 | ## Usage 11 | 12 | ``` shell 13 | 14 | # get composer 15 | 16 | php composer.phar install --prefer-dist 17 | bin/pimple-etl config.sample.php # example usage 18 | 19 | ``` 20 | 21 | ## Contribute 22 | 23 | ``` shell 24 | 25 | # get composer 26 | 27 | php composer.phar install --prefer-dist --dev 28 | bin/phpspec desc Knp\ETL\Feature\Class 29 | vim src/Knp/ETL/Feature/Class.php 30 | 31 | bin/phpspec run -f pretty 32 | 33 | ``` 34 | -------------------------------------------------------------------------------- /bin/etl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getOption('extract'))) as $key) { 19 | $extractor = $c[$input->getOption('extract')[$key]]; 20 | $transformer = $c[$input->getOption('transform')[$key]]; 21 | $loader = $c[$input->getOption('load')[$key]]; 22 | 23 | foreach ($extractor as $input) { 24 | $output = $transformer->transform($input); 25 | $loader->load($input); 26 | } 27 | 28 | $loader->flush(); 29 | } 30 | 31 | $loader->flush(); 32 | -------------------------------------------------------------------------------- /bin/pimple-etl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getArgument('path'); 15 | 16 | foreach ($c['etl'] as $etl) { 17 | $extractor = $etl['e']; 18 | $transformer = $etl['t']; 19 | $loader = $etl['l']; 20 | $context = $etl['c']; 21 | 22 | while (null !== $input = $extractor->extract($context)) { 23 | try { 24 | $output = $transformer->transform($input, $context); 25 | $loader->load($output, $context); 26 | } 27 | catch (\LogicException $e) { 28 | } 29 | } 30 | 31 | $loader->flush($context); 32 | } 33 | 34 | $loader->flush($context); 35 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "knplabs/etl", 3 | "type": "library", 4 | "description": "Extract - Transform - Load", 5 | "keywords": [ 6 | "etl", 7 | "extract", 8 | "transform", 9 | "load", 10 | "import" 11 | ], 12 | "homepage": "http://knplabs.com", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Knplabs", 17 | "homepage": "http://knplabs.com" 18 | }, 19 | { 20 | "name": "Florain Klein", 21 | "email": "florian.klein@free.fr" 22 | } 23 | ], 24 | "require": { 25 | "php": ">=5.5.0", 26 | "symfony/finder": "^3.0", 27 | "symfony/property-access": "^3.0", 28 | "psr/log": "^1.0" 29 | }, 30 | "require-dev": { 31 | "phpspec/phpspec": "^2.4", 32 | "doctrine/orm": "^2.5", 33 | "symfony/finder": "^3.0", 34 | "symfony/console": "^3.0", 35 | "pimple/pimple": "^3.0", 36 | "phpoffice/phpexcel": "~1.8" 37 | }, 38 | "suggest": { 39 | "doctrine/orm": "To use ORM loader", 40 | "doctrine/dbal": "To use DBAL loader", 41 | "symfony/finder": "To use Regex-filename CSV extractor", 42 | "symfony/console": "To use cli import command", 43 | "pimple/pimple": "To configure your ETL process", 44 | "phpoffice/phpexcel": "To use Excel files extractor" 45 | }, 46 | "autoload": { 47 | "psr-0": { "Knp\\ETL": "src/", "Entity": "" } 48 | }, 49 | "config": { 50 | "bin-dir": "bin" 51 | }, 52 | "extra": { 53 | "branch-alias": { 54 | "dev-master": "0.1.x-dev" 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "f9c607ed8f0c6a4a5f3e035bbfd19609", 8 | "content-hash": "26cff0333d30217d2f40fb59f3bc377e", 9 | "packages": [ 10 | { 11 | "name": "psr/log", 12 | "version": "1.0.0", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/php-fig/log.git", 16 | "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", 21 | "reference": "1.0.0", 22 | "shasum": "" 23 | }, 24 | "type": "library", 25 | "autoload": { 26 | "psr-0": { 27 | "Psr\\Log\\": "" 28 | } 29 | }, 30 | "notification-url": "https://packagist.org/downloads/", 31 | "license": [ 32 | "MIT" 33 | ], 34 | "authors": [ 35 | { 36 | "name": "PHP-FIG", 37 | "homepage": "http://www.php-fig.org/" 38 | } 39 | ], 40 | "description": "Common interface for logging libraries", 41 | "keywords": [ 42 | "log", 43 | "psr", 44 | "psr-3" 45 | ], 46 | "time": "2012-12-21 11:40:51" 47 | }, 48 | { 49 | "name": "symfony/finder", 50 | "version": "v3.0.1", 51 | "source": { 52 | "type": "git", 53 | "url": "https://github.com/symfony/finder.git", 54 | "reference": "8617895eb798b6bdb338321ce19453dc113e5675" 55 | }, 56 | "dist": { 57 | "type": "zip", 58 | "url": "https://api.github.com/repos/symfony/finder/zipball/8617895eb798b6bdb338321ce19453dc113e5675", 59 | "reference": "8617895eb798b6bdb338321ce19453dc113e5675", 60 | "shasum": "" 61 | }, 62 | "require": { 63 | "php": ">=5.5.9" 64 | }, 65 | "type": "library", 66 | "extra": { 67 | "branch-alias": { 68 | "dev-master": "3.0-dev" 69 | } 70 | }, 71 | "autoload": { 72 | "psr-4": { 73 | "Symfony\\Component\\Finder\\": "" 74 | }, 75 | "exclude-from-classmap": [ 76 | "/Tests/" 77 | ] 78 | }, 79 | "notification-url": "https://packagist.org/downloads/", 80 | "license": [ 81 | "MIT" 82 | ], 83 | "authors": [ 84 | { 85 | "name": "Fabien Potencier", 86 | "email": "fabien@symfony.com" 87 | }, 88 | { 89 | "name": "Symfony Community", 90 | "homepage": "https://symfony.com/contributors" 91 | } 92 | ], 93 | "description": "Symfony Finder Component", 94 | "homepage": "https://symfony.com", 95 | "time": "2015-12-05 11:13:14" 96 | }, 97 | { 98 | "name": "symfony/property-access", 99 | "version": "v3.0.1", 100 | "source": { 101 | "type": "git", 102 | "url": "https://github.com/symfony/property-access.git", 103 | "reference": "9bb9f79ade13196fadd0b98504117f117d8221ad" 104 | }, 105 | "dist": { 106 | "type": "zip", 107 | "url": "https://api.github.com/repos/symfony/property-access/zipball/9bb9f79ade13196fadd0b98504117f117d8221ad", 108 | "reference": "9bb9f79ade13196fadd0b98504117f117d8221ad", 109 | "shasum": "" 110 | }, 111 | "require": { 112 | "php": ">=5.5.9" 113 | }, 114 | "type": "library", 115 | "extra": { 116 | "branch-alias": { 117 | "dev-master": "3.0-dev" 118 | } 119 | }, 120 | "autoload": { 121 | "psr-4": { 122 | "Symfony\\Component\\PropertyAccess\\": "" 123 | }, 124 | "exclude-from-classmap": [ 125 | "/Tests/" 126 | ] 127 | }, 128 | "notification-url": "https://packagist.org/downloads/", 129 | "license": [ 130 | "MIT" 131 | ], 132 | "authors": [ 133 | { 134 | "name": "Fabien Potencier", 135 | "email": "fabien@symfony.com" 136 | }, 137 | { 138 | "name": "Symfony Community", 139 | "homepage": "https://symfony.com/contributors" 140 | } 141 | ], 142 | "description": "Symfony PropertyAccess Component", 143 | "homepage": "https://symfony.com", 144 | "keywords": [ 145 | "access", 146 | "array", 147 | "extraction", 148 | "index", 149 | "injection", 150 | "object", 151 | "property", 152 | "property path", 153 | "reflection" 154 | ], 155 | "time": "2015-12-23 08:00:11" 156 | } 157 | ], 158 | "packages-dev": [ 159 | { 160 | "name": "doctrine/annotations", 161 | "version": "v1.2.7", 162 | "source": { 163 | "type": "git", 164 | "url": "https://github.com/doctrine/annotations.git", 165 | "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535" 166 | }, 167 | "dist": { 168 | "type": "zip", 169 | "url": "https://api.github.com/repos/doctrine/annotations/zipball/f25c8aab83e0c3e976fd7d19875f198ccf2f7535", 170 | "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535", 171 | "shasum": "" 172 | }, 173 | "require": { 174 | "doctrine/lexer": "1.*", 175 | "php": ">=5.3.2" 176 | }, 177 | "require-dev": { 178 | "doctrine/cache": "1.*", 179 | "phpunit/phpunit": "4.*" 180 | }, 181 | "type": "library", 182 | "extra": { 183 | "branch-alias": { 184 | "dev-master": "1.3.x-dev" 185 | } 186 | }, 187 | "autoload": { 188 | "psr-0": { 189 | "Doctrine\\Common\\Annotations\\": "lib/" 190 | } 191 | }, 192 | "notification-url": "https://packagist.org/downloads/", 193 | "license": [ 194 | "MIT" 195 | ], 196 | "authors": [ 197 | { 198 | "name": "Roman Borschel", 199 | "email": "roman@code-factory.org" 200 | }, 201 | { 202 | "name": "Benjamin Eberlei", 203 | "email": "kontakt@beberlei.de" 204 | }, 205 | { 206 | "name": "Guilherme Blanco", 207 | "email": "guilhermeblanco@gmail.com" 208 | }, 209 | { 210 | "name": "Jonathan Wage", 211 | "email": "jonwage@gmail.com" 212 | }, 213 | { 214 | "name": "Johannes Schmitt", 215 | "email": "schmittjoh@gmail.com" 216 | } 217 | ], 218 | "description": "Docblock Annotations Parser", 219 | "homepage": "http://www.doctrine-project.org", 220 | "keywords": [ 221 | "annotations", 222 | "docblock", 223 | "parser" 224 | ], 225 | "time": "2015-08-31 12:32:49" 226 | }, 227 | { 228 | "name": "doctrine/cache", 229 | "version": "v1.6.0", 230 | "source": { 231 | "type": "git", 232 | "url": "https://github.com/doctrine/cache.git", 233 | "reference": "f8af318d14bdb0eff0336795b428b547bd39ccb6" 234 | }, 235 | "dist": { 236 | "type": "zip", 237 | "url": "https://api.github.com/repos/doctrine/cache/zipball/f8af318d14bdb0eff0336795b428b547bd39ccb6", 238 | "reference": "f8af318d14bdb0eff0336795b428b547bd39ccb6", 239 | "shasum": "" 240 | }, 241 | "require": { 242 | "php": "~5.5|~7.0" 243 | }, 244 | "conflict": { 245 | "doctrine/common": ">2.2,<2.4" 246 | }, 247 | "require-dev": { 248 | "phpunit/phpunit": "~4.8|~5.0", 249 | "predis/predis": "~1.0", 250 | "satooshi/php-coveralls": "~0.6" 251 | }, 252 | "type": "library", 253 | "extra": { 254 | "branch-alias": { 255 | "dev-master": "1.6.x-dev" 256 | } 257 | }, 258 | "autoload": { 259 | "psr-4": { 260 | "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" 261 | } 262 | }, 263 | "notification-url": "https://packagist.org/downloads/", 264 | "license": [ 265 | "MIT" 266 | ], 267 | "authors": [ 268 | { 269 | "name": "Roman Borschel", 270 | "email": "roman@code-factory.org" 271 | }, 272 | { 273 | "name": "Benjamin Eberlei", 274 | "email": "kontakt@beberlei.de" 275 | }, 276 | { 277 | "name": "Guilherme Blanco", 278 | "email": "guilhermeblanco@gmail.com" 279 | }, 280 | { 281 | "name": "Jonathan Wage", 282 | "email": "jonwage@gmail.com" 283 | }, 284 | { 285 | "name": "Johannes Schmitt", 286 | "email": "schmittjoh@gmail.com" 287 | } 288 | ], 289 | "description": "Caching library offering an object-oriented API for many cache backends", 290 | "homepage": "http://www.doctrine-project.org", 291 | "keywords": [ 292 | "cache", 293 | "caching" 294 | ], 295 | "time": "2015-12-31 16:37:02" 296 | }, 297 | { 298 | "name": "doctrine/collections", 299 | "version": "v1.3.0", 300 | "source": { 301 | "type": "git", 302 | "url": "https://github.com/doctrine/collections.git", 303 | "reference": "6c1e4eef75f310ea1b3e30945e9f06e652128b8a" 304 | }, 305 | "dist": { 306 | "type": "zip", 307 | "url": "https://api.github.com/repos/doctrine/collections/zipball/6c1e4eef75f310ea1b3e30945e9f06e652128b8a", 308 | "reference": "6c1e4eef75f310ea1b3e30945e9f06e652128b8a", 309 | "shasum": "" 310 | }, 311 | "require": { 312 | "php": ">=5.3.2" 313 | }, 314 | "require-dev": { 315 | "phpunit/phpunit": "~4.0" 316 | }, 317 | "type": "library", 318 | "extra": { 319 | "branch-alias": { 320 | "dev-master": "1.2.x-dev" 321 | } 322 | }, 323 | "autoload": { 324 | "psr-0": { 325 | "Doctrine\\Common\\Collections\\": "lib/" 326 | } 327 | }, 328 | "notification-url": "https://packagist.org/downloads/", 329 | "license": [ 330 | "MIT" 331 | ], 332 | "authors": [ 333 | { 334 | "name": "Roman Borschel", 335 | "email": "roman@code-factory.org" 336 | }, 337 | { 338 | "name": "Benjamin Eberlei", 339 | "email": "kontakt@beberlei.de" 340 | }, 341 | { 342 | "name": "Guilherme Blanco", 343 | "email": "guilhermeblanco@gmail.com" 344 | }, 345 | { 346 | "name": "Jonathan Wage", 347 | "email": "jonwage@gmail.com" 348 | }, 349 | { 350 | "name": "Johannes Schmitt", 351 | "email": "schmittjoh@gmail.com" 352 | } 353 | ], 354 | "description": "Collections Abstraction library", 355 | "homepage": "http://www.doctrine-project.org", 356 | "keywords": [ 357 | "array", 358 | "collections", 359 | "iterator" 360 | ], 361 | "time": "2015-04-14 22:21:58" 362 | }, 363 | { 364 | "name": "doctrine/common", 365 | "version": "v2.6.1", 366 | "source": { 367 | "type": "git", 368 | "url": "https://github.com/doctrine/common.git", 369 | "reference": "a579557bc689580c19fee4e27487a67fe60defc0" 370 | }, 371 | "dist": { 372 | "type": "zip", 373 | "url": "https://api.github.com/repos/doctrine/common/zipball/a579557bc689580c19fee4e27487a67fe60defc0", 374 | "reference": "a579557bc689580c19fee4e27487a67fe60defc0", 375 | "shasum": "" 376 | }, 377 | "require": { 378 | "doctrine/annotations": "1.*", 379 | "doctrine/cache": "1.*", 380 | "doctrine/collections": "1.*", 381 | "doctrine/inflector": "1.*", 382 | "doctrine/lexer": "1.*", 383 | "php": "~5.5|~7.0" 384 | }, 385 | "require-dev": { 386 | "phpunit/phpunit": "~4.8|~5.0" 387 | }, 388 | "type": "library", 389 | "extra": { 390 | "branch-alias": { 391 | "dev-master": "2.7.x-dev" 392 | } 393 | }, 394 | "autoload": { 395 | "psr-4": { 396 | "Doctrine\\Common\\": "lib/Doctrine/Common" 397 | } 398 | }, 399 | "notification-url": "https://packagist.org/downloads/", 400 | "license": [ 401 | "MIT" 402 | ], 403 | "authors": [ 404 | { 405 | "name": "Roman Borschel", 406 | "email": "roman@code-factory.org" 407 | }, 408 | { 409 | "name": "Benjamin Eberlei", 410 | "email": "kontakt@beberlei.de" 411 | }, 412 | { 413 | "name": "Guilherme Blanco", 414 | "email": "guilhermeblanco@gmail.com" 415 | }, 416 | { 417 | "name": "Jonathan Wage", 418 | "email": "jonwage@gmail.com" 419 | }, 420 | { 421 | "name": "Johannes Schmitt", 422 | "email": "schmittjoh@gmail.com" 423 | } 424 | ], 425 | "description": "Common Library for Doctrine projects", 426 | "homepage": "http://www.doctrine-project.org", 427 | "keywords": [ 428 | "annotations", 429 | "collections", 430 | "eventmanager", 431 | "persistence", 432 | "spl" 433 | ], 434 | "time": "2015-12-25 13:18:31" 435 | }, 436 | { 437 | "name": "doctrine/dbal", 438 | "version": "v2.5.4", 439 | "source": { 440 | "type": "git", 441 | "url": "https://github.com/doctrine/dbal.git", 442 | "reference": "abbdfd1cff43a7b99d027af3be709bc8fc7d4769" 443 | }, 444 | "dist": { 445 | "type": "zip", 446 | "url": "https://api.github.com/repos/doctrine/dbal/zipball/abbdfd1cff43a7b99d027af3be709bc8fc7d4769", 447 | "reference": "abbdfd1cff43a7b99d027af3be709bc8fc7d4769", 448 | "shasum": "" 449 | }, 450 | "require": { 451 | "doctrine/common": ">=2.4,<2.7-dev", 452 | "php": ">=5.3.2" 453 | }, 454 | "require-dev": { 455 | "phpunit/phpunit": "4.*", 456 | "symfony/console": "2.*" 457 | }, 458 | "suggest": { 459 | "symfony/console": "For helpful console commands such as SQL execution and import of files." 460 | }, 461 | "bin": [ 462 | "bin/doctrine-dbal" 463 | ], 464 | "type": "library", 465 | "extra": { 466 | "branch-alias": { 467 | "dev-master": "2.5.x-dev" 468 | } 469 | }, 470 | "autoload": { 471 | "psr-0": { 472 | "Doctrine\\DBAL\\": "lib/" 473 | } 474 | }, 475 | "notification-url": "https://packagist.org/downloads/", 476 | "license": [ 477 | "MIT" 478 | ], 479 | "authors": [ 480 | { 481 | "name": "Roman Borschel", 482 | "email": "roman@code-factory.org" 483 | }, 484 | { 485 | "name": "Benjamin Eberlei", 486 | "email": "kontakt@beberlei.de" 487 | }, 488 | { 489 | "name": "Guilherme Blanco", 490 | "email": "guilhermeblanco@gmail.com" 491 | }, 492 | { 493 | "name": "Jonathan Wage", 494 | "email": "jonwage@gmail.com" 495 | } 496 | ], 497 | "description": "Database Abstraction Layer", 498 | "homepage": "http://www.doctrine-project.org", 499 | "keywords": [ 500 | "database", 501 | "dbal", 502 | "persistence", 503 | "queryobject" 504 | ], 505 | "time": "2016-01-05 22:11:12" 506 | }, 507 | { 508 | "name": "doctrine/inflector", 509 | "version": "v1.1.0", 510 | "source": { 511 | "type": "git", 512 | "url": "https://github.com/doctrine/inflector.git", 513 | "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" 514 | }, 515 | "dist": { 516 | "type": "zip", 517 | "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", 518 | "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", 519 | "shasum": "" 520 | }, 521 | "require": { 522 | "php": ">=5.3.2" 523 | }, 524 | "require-dev": { 525 | "phpunit/phpunit": "4.*" 526 | }, 527 | "type": "library", 528 | "extra": { 529 | "branch-alias": { 530 | "dev-master": "1.1.x-dev" 531 | } 532 | }, 533 | "autoload": { 534 | "psr-0": { 535 | "Doctrine\\Common\\Inflector\\": "lib/" 536 | } 537 | }, 538 | "notification-url": "https://packagist.org/downloads/", 539 | "license": [ 540 | "MIT" 541 | ], 542 | "authors": [ 543 | { 544 | "name": "Roman Borschel", 545 | "email": "roman@code-factory.org" 546 | }, 547 | { 548 | "name": "Benjamin Eberlei", 549 | "email": "kontakt@beberlei.de" 550 | }, 551 | { 552 | "name": "Guilherme Blanco", 553 | "email": "guilhermeblanco@gmail.com" 554 | }, 555 | { 556 | "name": "Jonathan Wage", 557 | "email": "jonwage@gmail.com" 558 | }, 559 | { 560 | "name": "Johannes Schmitt", 561 | "email": "schmittjoh@gmail.com" 562 | } 563 | ], 564 | "description": "Common String Manipulations with regard to casing and singular/plural rules.", 565 | "homepage": "http://www.doctrine-project.org", 566 | "keywords": [ 567 | "inflection", 568 | "pluralize", 569 | "singularize", 570 | "string" 571 | ], 572 | "time": "2015-11-06 14:35:42" 573 | }, 574 | { 575 | "name": "doctrine/instantiator", 576 | "version": "1.0.5", 577 | "source": { 578 | "type": "git", 579 | "url": "https://github.com/doctrine/instantiator.git", 580 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" 581 | }, 582 | "dist": { 583 | "type": "zip", 584 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", 585 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", 586 | "shasum": "" 587 | }, 588 | "require": { 589 | "php": ">=5.3,<8.0-DEV" 590 | }, 591 | "require-dev": { 592 | "athletic/athletic": "~0.1.8", 593 | "ext-pdo": "*", 594 | "ext-phar": "*", 595 | "phpunit/phpunit": "~4.0", 596 | "squizlabs/php_codesniffer": "~2.0" 597 | }, 598 | "type": "library", 599 | "extra": { 600 | "branch-alias": { 601 | "dev-master": "1.0.x-dev" 602 | } 603 | }, 604 | "autoload": { 605 | "psr-4": { 606 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 607 | } 608 | }, 609 | "notification-url": "https://packagist.org/downloads/", 610 | "license": [ 611 | "MIT" 612 | ], 613 | "authors": [ 614 | { 615 | "name": "Marco Pivetta", 616 | "email": "ocramius@gmail.com", 617 | "homepage": "http://ocramius.github.com/" 618 | } 619 | ], 620 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 621 | "homepage": "https://github.com/doctrine/instantiator", 622 | "keywords": [ 623 | "constructor", 624 | "instantiate" 625 | ], 626 | "time": "2015-06-14 21:17:01" 627 | }, 628 | { 629 | "name": "doctrine/lexer", 630 | "version": "v1.0.1", 631 | "source": { 632 | "type": "git", 633 | "url": "https://github.com/doctrine/lexer.git", 634 | "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" 635 | }, 636 | "dist": { 637 | "type": "zip", 638 | "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", 639 | "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", 640 | "shasum": "" 641 | }, 642 | "require": { 643 | "php": ">=5.3.2" 644 | }, 645 | "type": "library", 646 | "extra": { 647 | "branch-alias": { 648 | "dev-master": "1.0.x-dev" 649 | } 650 | }, 651 | "autoload": { 652 | "psr-0": { 653 | "Doctrine\\Common\\Lexer\\": "lib/" 654 | } 655 | }, 656 | "notification-url": "https://packagist.org/downloads/", 657 | "license": [ 658 | "MIT" 659 | ], 660 | "authors": [ 661 | { 662 | "name": "Roman Borschel", 663 | "email": "roman@code-factory.org" 664 | }, 665 | { 666 | "name": "Guilherme Blanco", 667 | "email": "guilhermeblanco@gmail.com" 668 | }, 669 | { 670 | "name": "Johannes Schmitt", 671 | "email": "schmittjoh@gmail.com" 672 | } 673 | ], 674 | "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", 675 | "homepage": "http://www.doctrine-project.org", 676 | "keywords": [ 677 | "lexer", 678 | "parser" 679 | ], 680 | "time": "2014-09-09 13:34:57" 681 | }, 682 | { 683 | "name": "doctrine/orm", 684 | "version": "v2.5.4", 685 | "source": { 686 | "type": "git", 687 | "url": "https://github.com/doctrine/doctrine2.git", 688 | "reference": "bc4ddbfb0114cb33438cc811c9a740d8aa304aab" 689 | }, 690 | "dist": { 691 | "type": "zip", 692 | "url": "https://api.github.com/repos/doctrine/doctrine2/zipball/bc4ddbfb0114cb33438cc811c9a740d8aa304aab", 693 | "reference": "bc4ddbfb0114cb33438cc811c9a740d8aa304aab", 694 | "shasum": "" 695 | }, 696 | "require": { 697 | "doctrine/cache": "~1.4", 698 | "doctrine/collections": "~1.2", 699 | "doctrine/common": ">=2.5-dev,<2.7-dev", 700 | "doctrine/dbal": ">=2.5-dev,<2.6-dev", 701 | "doctrine/instantiator": "~1.0.1", 702 | "ext-pdo": "*", 703 | "php": ">=5.4", 704 | "symfony/console": "~2.5|~3.0" 705 | }, 706 | "require-dev": { 707 | "phpunit/phpunit": "~4.0", 708 | "symfony/yaml": "~2.3|~3.0" 709 | }, 710 | "suggest": { 711 | "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" 712 | }, 713 | "bin": [ 714 | "bin/doctrine", 715 | "bin/doctrine.php" 716 | ], 717 | "type": "library", 718 | "extra": { 719 | "branch-alias": { 720 | "dev-master": "2.6.x-dev" 721 | } 722 | }, 723 | "autoload": { 724 | "psr-0": { 725 | "Doctrine\\ORM\\": "lib/" 726 | } 727 | }, 728 | "notification-url": "https://packagist.org/downloads/", 729 | "license": [ 730 | "MIT" 731 | ], 732 | "authors": [ 733 | { 734 | "name": "Roman Borschel", 735 | "email": "roman@code-factory.org" 736 | }, 737 | { 738 | "name": "Benjamin Eberlei", 739 | "email": "kontakt@beberlei.de" 740 | }, 741 | { 742 | "name": "Guilherme Blanco", 743 | "email": "guilhermeblanco@gmail.com" 744 | }, 745 | { 746 | "name": "Jonathan Wage", 747 | "email": "jonwage@gmail.com" 748 | } 749 | ], 750 | "description": "Object-Relational-Mapper for PHP", 751 | "homepage": "http://www.doctrine-project.org", 752 | "keywords": [ 753 | "database", 754 | "orm" 755 | ], 756 | "time": "2016-01-05 21:34:58" 757 | }, 758 | { 759 | "name": "phpdocumentor/reflection-docblock", 760 | "version": "2.0.4", 761 | "source": { 762 | "type": "git", 763 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 764 | "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" 765 | }, 766 | "dist": { 767 | "type": "zip", 768 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", 769 | "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", 770 | "shasum": "" 771 | }, 772 | "require": { 773 | "php": ">=5.3.3" 774 | }, 775 | "require-dev": { 776 | "phpunit/phpunit": "~4.0" 777 | }, 778 | "suggest": { 779 | "dflydev/markdown": "~1.0", 780 | "erusev/parsedown": "~1.0" 781 | }, 782 | "type": "library", 783 | "extra": { 784 | "branch-alias": { 785 | "dev-master": "2.0.x-dev" 786 | } 787 | }, 788 | "autoload": { 789 | "psr-0": { 790 | "phpDocumentor": [ 791 | "src/" 792 | ] 793 | } 794 | }, 795 | "notification-url": "https://packagist.org/downloads/", 796 | "license": [ 797 | "MIT" 798 | ], 799 | "authors": [ 800 | { 801 | "name": "Mike van Riel", 802 | "email": "mike.vanriel@naenius.com" 803 | } 804 | ], 805 | "time": "2015-02-03 12:10:50" 806 | }, 807 | { 808 | "name": "phpoffice/phpexcel", 809 | "version": "1.8.1", 810 | "source": { 811 | "type": "git", 812 | "url": "https://github.com/PHPOffice/PHPExcel.git", 813 | "reference": "372c7cbb695a6f6f1e62649381aeaa37e7e70b32" 814 | }, 815 | "dist": { 816 | "type": "zip", 817 | "url": "https://api.github.com/repos/PHPOffice/PHPExcel/zipball/372c7cbb695a6f6f1e62649381aeaa37e7e70b32", 818 | "reference": "372c7cbb695a6f6f1e62649381aeaa37e7e70b32", 819 | "shasum": "" 820 | }, 821 | "require": { 822 | "ext-xml": "*", 823 | "ext-xmlwriter": "*", 824 | "php": ">=5.2.0" 825 | }, 826 | "type": "library", 827 | "autoload": { 828 | "psr-0": { 829 | "PHPExcel": "Classes/" 830 | } 831 | }, 832 | "notification-url": "https://packagist.org/downloads/", 833 | "license": [ 834 | "LGPL" 835 | ], 836 | "authors": [ 837 | { 838 | "name": "Maarten Balliauw", 839 | "homepage": "http://blog.maartenballiauw.be" 840 | }, 841 | { 842 | "name": "Mark Baker" 843 | }, 844 | { 845 | "name": "Franck Lefevre", 846 | "homepage": "http://blog.rootslabs.net" 847 | }, 848 | { 849 | "name": "Erik Tilt" 850 | } 851 | ], 852 | "description": "PHPExcel - OpenXML - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", 853 | "homepage": "http://phpexcel.codeplex.com", 854 | "keywords": [ 855 | "OpenXML", 856 | "excel", 857 | "php", 858 | "spreadsheet", 859 | "xls", 860 | "xlsx" 861 | ], 862 | "time": "2015-05-01 07:00:55" 863 | }, 864 | { 865 | "name": "phpspec/php-diff", 866 | "version": "v1.0.2", 867 | "source": { 868 | "type": "git", 869 | "url": "https://github.com/phpspec/php-diff.git", 870 | "reference": "30e103d19519fe678ae64a60d77884ef3d71b28a" 871 | }, 872 | "dist": { 873 | "type": "zip", 874 | "url": "https://api.github.com/repos/phpspec/php-diff/zipball/30e103d19519fe678ae64a60d77884ef3d71b28a", 875 | "reference": "30e103d19519fe678ae64a60d77884ef3d71b28a", 876 | "shasum": "" 877 | }, 878 | "type": "library", 879 | "autoload": { 880 | "psr-0": { 881 | "Diff": "lib/" 882 | } 883 | }, 884 | "notification-url": "https://packagist.org/downloads/", 885 | "license": [ 886 | "BSD-3-Clause" 887 | ], 888 | "authors": [ 889 | { 890 | "name": "Chris Boulton", 891 | "homepage": "http://github.com/chrisboulton", 892 | "role": "Original developer" 893 | } 894 | ], 895 | "description": "A comprehensive library for generating differences between two hashable objects (strings or arrays).", 896 | "time": "2013-11-01 13:02:21" 897 | }, 898 | { 899 | "name": "phpspec/phpspec", 900 | "version": "2.4.1", 901 | "source": { 902 | "type": "git", 903 | "url": "https://github.com/phpspec/phpspec.git", 904 | "reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed" 905 | }, 906 | "dist": { 907 | "type": "zip", 908 | "url": "https://api.github.com/repos/phpspec/phpspec/zipball/5528ce1e93a1efa090c9404aba3395c329b4e6ed", 909 | "reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed", 910 | "shasum": "" 911 | }, 912 | "require": { 913 | "doctrine/instantiator": "^1.0.1", 914 | "ext-tokenizer": "*", 915 | "php": ">=5.3.3", 916 | "phpspec/php-diff": "~1.0.0", 917 | "phpspec/prophecy": "~1.4", 918 | "sebastian/exporter": "~1.0", 919 | "symfony/console": "~2.3|~3.0", 920 | "symfony/event-dispatcher": "~2.1|~3.0", 921 | "symfony/finder": "~2.1|~3.0", 922 | "symfony/process": "^2.6|~3.0", 923 | "symfony/yaml": "~2.1|~3.0" 924 | }, 925 | "require-dev": { 926 | "behat/behat": "^3.0.11", 927 | "bossa/phpspec2-expect": "~1.0", 928 | "phpunit/phpunit": "~4.4", 929 | "symfony/filesystem": "~2.1|~3.0" 930 | }, 931 | "suggest": { 932 | "phpspec/nyan-formatters": "~1.0 – Adds Nyan formatters" 933 | }, 934 | "bin": [ 935 | "bin/phpspec" 936 | ], 937 | "type": "library", 938 | "extra": { 939 | "branch-alias": { 940 | "dev-master": "2.2.x-dev" 941 | } 942 | }, 943 | "autoload": { 944 | "psr-0": { 945 | "PhpSpec": "src/" 946 | } 947 | }, 948 | "notification-url": "https://packagist.org/downloads/", 949 | "license": [ 950 | "MIT" 951 | ], 952 | "authors": [ 953 | { 954 | "name": "Konstantin Kudryashov", 955 | "email": "ever.zet@gmail.com", 956 | "homepage": "http://everzet.com" 957 | }, 958 | { 959 | "name": "Marcello Duarte", 960 | "homepage": "http://marcelloduarte.net/" 961 | } 962 | ], 963 | "description": "Specification-oriented BDD framework for PHP 5.3+", 964 | "homepage": "http://phpspec.net/", 965 | "keywords": [ 966 | "BDD", 967 | "SpecBDD", 968 | "TDD", 969 | "spec", 970 | "specification", 971 | "testing", 972 | "tests" 973 | ], 974 | "time": "2016-01-01 10:17:54" 975 | }, 976 | { 977 | "name": "phpspec/prophecy", 978 | "version": "v1.5.0", 979 | "source": { 980 | "type": "git", 981 | "url": "https://github.com/phpspec/prophecy.git", 982 | "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7" 983 | }, 984 | "dist": { 985 | "type": "zip", 986 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7", 987 | "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7", 988 | "shasum": "" 989 | }, 990 | "require": { 991 | "doctrine/instantiator": "^1.0.2", 992 | "phpdocumentor/reflection-docblock": "~2.0", 993 | "sebastian/comparator": "~1.1" 994 | }, 995 | "require-dev": { 996 | "phpspec/phpspec": "~2.0" 997 | }, 998 | "type": "library", 999 | "extra": { 1000 | "branch-alias": { 1001 | "dev-master": "1.4.x-dev" 1002 | } 1003 | }, 1004 | "autoload": { 1005 | "psr-0": { 1006 | "Prophecy\\": "src/" 1007 | } 1008 | }, 1009 | "notification-url": "https://packagist.org/downloads/", 1010 | "license": [ 1011 | "MIT" 1012 | ], 1013 | "authors": [ 1014 | { 1015 | "name": "Konstantin Kudryashov", 1016 | "email": "ever.zet@gmail.com", 1017 | "homepage": "http://everzet.com" 1018 | }, 1019 | { 1020 | "name": "Marcello Duarte", 1021 | "email": "marcello.duarte@gmail.com" 1022 | } 1023 | ], 1024 | "description": "Highly opinionated mocking framework for PHP 5.3+", 1025 | "homepage": "https://github.com/phpspec/prophecy", 1026 | "keywords": [ 1027 | "Double", 1028 | "Dummy", 1029 | "fake", 1030 | "mock", 1031 | "spy", 1032 | "stub" 1033 | ], 1034 | "time": "2015-08-13 10:07:40" 1035 | }, 1036 | { 1037 | "name": "pimple/pimple", 1038 | "version": "v3.0.2", 1039 | "source": { 1040 | "type": "git", 1041 | "url": "https://github.com/silexphp/Pimple.git", 1042 | "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a" 1043 | }, 1044 | "dist": { 1045 | "type": "zip", 1046 | "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a", 1047 | "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a", 1048 | "shasum": "" 1049 | }, 1050 | "require": { 1051 | "php": ">=5.3.0" 1052 | }, 1053 | "type": "library", 1054 | "extra": { 1055 | "branch-alias": { 1056 | "dev-master": "3.0.x-dev" 1057 | } 1058 | }, 1059 | "autoload": { 1060 | "psr-0": { 1061 | "Pimple": "src/" 1062 | } 1063 | }, 1064 | "notification-url": "https://packagist.org/downloads/", 1065 | "license": [ 1066 | "MIT" 1067 | ], 1068 | "authors": [ 1069 | { 1070 | "name": "Fabien Potencier", 1071 | "email": "fabien@symfony.com" 1072 | } 1073 | ], 1074 | "description": "Pimple, a simple Dependency Injection Container", 1075 | "homepage": "http://pimple.sensiolabs.org", 1076 | "keywords": [ 1077 | "container", 1078 | "dependency injection" 1079 | ], 1080 | "time": "2015-09-11 15:10:35" 1081 | }, 1082 | { 1083 | "name": "sebastian/comparator", 1084 | "version": "1.2.0", 1085 | "source": { 1086 | "type": "git", 1087 | "url": "https://github.com/sebastianbergmann/comparator.git", 1088 | "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" 1089 | }, 1090 | "dist": { 1091 | "type": "zip", 1092 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", 1093 | "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", 1094 | "shasum": "" 1095 | }, 1096 | "require": { 1097 | "php": ">=5.3.3", 1098 | "sebastian/diff": "~1.2", 1099 | "sebastian/exporter": "~1.2" 1100 | }, 1101 | "require-dev": { 1102 | "phpunit/phpunit": "~4.4" 1103 | }, 1104 | "type": "library", 1105 | "extra": { 1106 | "branch-alias": { 1107 | "dev-master": "1.2.x-dev" 1108 | } 1109 | }, 1110 | "autoload": { 1111 | "classmap": [ 1112 | "src/" 1113 | ] 1114 | }, 1115 | "notification-url": "https://packagist.org/downloads/", 1116 | "license": [ 1117 | "BSD-3-Clause" 1118 | ], 1119 | "authors": [ 1120 | { 1121 | "name": "Jeff Welch", 1122 | "email": "whatthejeff@gmail.com" 1123 | }, 1124 | { 1125 | "name": "Volker Dusch", 1126 | "email": "github@wallbash.com" 1127 | }, 1128 | { 1129 | "name": "Bernhard Schussek", 1130 | "email": "bschussek@2bepublished.at" 1131 | }, 1132 | { 1133 | "name": "Sebastian Bergmann", 1134 | "email": "sebastian@phpunit.de" 1135 | } 1136 | ], 1137 | "description": "Provides the functionality to compare PHP values for equality", 1138 | "homepage": "http://www.github.com/sebastianbergmann/comparator", 1139 | "keywords": [ 1140 | "comparator", 1141 | "compare", 1142 | "equality" 1143 | ], 1144 | "time": "2015-07-26 15:48:44" 1145 | }, 1146 | { 1147 | "name": "sebastian/diff", 1148 | "version": "1.4.1", 1149 | "source": { 1150 | "type": "git", 1151 | "url": "https://github.com/sebastianbergmann/diff.git", 1152 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" 1153 | }, 1154 | "dist": { 1155 | "type": "zip", 1156 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", 1157 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", 1158 | "shasum": "" 1159 | }, 1160 | "require": { 1161 | "php": ">=5.3.3" 1162 | }, 1163 | "require-dev": { 1164 | "phpunit/phpunit": "~4.8" 1165 | }, 1166 | "type": "library", 1167 | "extra": { 1168 | "branch-alias": { 1169 | "dev-master": "1.4-dev" 1170 | } 1171 | }, 1172 | "autoload": { 1173 | "classmap": [ 1174 | "src/" 1175 | ] 1176 | }, 1177 | "notification-url": "https://packagist.org/downloads/", 1178 | "license": [ 1179 | "BSD-3-Clause" 1180 | ], 1181 | "authors": [ 1182 | { 1183 | "name": "Kore Nordmann", 1184 | "email": "mail@kore-nordmann.de" 1185 | }, 1186 | { 1187 | "name": "Sebastian Bergmann", 1188 | "email": "sebastian@phpunit.de" 1189 | } 1190 | ], 1191 | "description": "Diff implementation", 1192 | "homepage": "https://github.com/sebastianbergmann/diff", 1193 | "keywords": [ 1194 | "diff" 1195 | ], 1196 | "time": "2015-12-08 07:14:41" 1197 | }, 1198 | { 1199 | "name": "sebastian/exporter", 1200 | "version": "1.2.1", 1201 | "source": { 1202 | "type": "git", 1203 | "url": "https://github.com/sebastianbergmann/exporter.git", 1204 | "reference": "7ae5513327cb536431847bcc0c10edba2701064e" 1205 | }, 1206 | "dist": { 1207 | "type": "zip", 1208 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", 1209 | "reference": "7ae5513327cb536431847bcc0c10edba2701064e", 1210 | "shasum": "" 1211 | }, 1212 | "require": { 1213 | "php": ">=5.3.3", 1214 | "sebastian/recursion-context": "~1.0" 1215 | }, 1216 | "require-dev": { 1217 | "phpunit/phpunit": "~4.4" 1218 | }, 1219 | "type": "library", 1220 | "extra": { 1221 | "branch-alias": { 1222 | "dev-master": "1.2.x-dev" 1223 | } 1224 | }, 1225 | "autoload": { 1226 | "classmap": [ 1227 | "src/" 1228 | ] 1229 | }, 1230 | "notification-url": "https://packagist.org/downloads/", 1231 | "license": [ 1232 | "BSD-3-Clause" 1233 | ], 1234 | "authors": [ 1235 | { 1236 | "name": "Jeff Welch", 1237 | "email": "whatthejeff@gmail.com" 1238 | }, 1239 | { 1240 | "name": "Volker Dusch", 1241 | "email": "github@wallbash.com" 1242 | }, 1243 | { 1244 | "name": "Bernhard Schussek", 1245 | "email": "bschussek@2bepublished.at" 1246 | }, 1247 | { 1248 | "name": "Sebastian Bergmann", 1249 | "email": "sebastian@phpunit.de" 1250 | }, 1251 | { 1252 | "name": "Adam Harvey", 1253 | "email": "aharvey@php.net" 1254 | } 1255 | ], 1256 | "description": "Provides the functionality to export PHP variables for visualization", 1257 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1258 | "keywords": [ 1259 | "export", 1260 | "exporter" 1261 | ], 1262 | "time": "2015-06-21 07:55:53" 1263 | }, 1264 | { 1265 | "name": "sebastian/recursion-context", 1266 | "version": "1.0.2", 1267 | "source": { 1268 | "type": "git", 1269 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1270 | "reference": "913401df809e99e4f47b27cdd781f4a258d58791" 1271 | }, 1272 | "dist": { 1273 | "type": "zip", 1274 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", 1275 | "reference": "913401df809e99e4f47b27cdd781f4a258d58791", 1276 | "shasum": "" 1277 | }, 1278 | "require": { 1279 | "php": ">=5.3.3" 1280 | }, 1281 | "require-dev": { 1282 | "phpunit/phpunit": "~4.4" 1283 | }, 1284 | "type": "library", 1285 | "extra": { 1286 | "branch-alias": { 1287 | "dev-master": "1.0.x-dev" 1288 | } 1289 | }, 1290 | "autoload": { 1291 | "classmap": [ 1292 | "src/" 1293 | ] 1294 | }, 1295 | "notification-url": "https://packagist.org/downloads/", 1296 | "license": [ 1297 | "BSD-3-Clause" 1298 | ], 1299 | "authors": [ 1300 | { 1301 | "name": "Jeff Welch", 1302 | "email": "whatthejeff@gmail.com" 1303 | }, 1304 | { 1305 | "name": "Sebastian Bergmann", 1306 | "email": "sebastian@phpunit.de" 1307 | }, 1308 | { 1309 | "name": "Adam Harvey", 1310 | "email": "aharvey@php.net" 1311 | } 1312 | ], 1313 | "description": "Provides functionality to recursively process PHP variables", 1314 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1315 | "time": "2015-11-11 19:50:13" 1316 | }, 1317 | { 1318 | "name": "symfony/console", 1319 | "version": "v3.0.1", 1320 | "source": { 1321 | "type": "git", 1322 | "url": "https://github.com/symfony/console.git", 1323 | "reference": "ebcdc507829df915f4ca23067bd59ee4ef61f6c3" 1324 | }, 1325 | "dist": { 1326 | "type": "zip", 1327 | "url": "https://api.github.com/repos/symfony/console/zipball/ebcdc507829df915f4ca23067bd59ee4ef61f6c3", 1328 | "reference": "ebcdc507829df915f4ca23067bd59ee4ef61f6c3", 1329 | "shasum": "" 1330 | }, 1331 | "require": { 1332 | "php": ">=5.5.9", 1333 | "symfony/polyfill-mbstring": "~1.0" 1334 | }, 1335 | "require-dev": { 1336 | "psr/log": "~1.0", 1337 | "symfony/event-dispatcher": "~2.8|~3.0", 1338 | "symfony/process": "~2.8|~3.0" 1339 | }, 1340 | "suggest": { 1341 | "psr/log": "For using the console logger", 1342 | "symfony/event-dispatcher": "", 1343 | "symfony/process": "" 1344 | }, 1345 | "type": "library", 1346 | "extra": { 1347 | "branch-alias": { 1348 | "dev-master": "3.0-dev" 1349 | } 1350 | }, 1351 | "autoload": { 1352 | "psr-4": { 1353 | "Symfony\\Component\\Console\\": "" 1354 | }, 1355 | "exclude-from-classmap": [ 1356 | "/Tests/" 1357 | ] 1358 | }, 1359 | "notification-url": "https://packagist.org/downloads/", 1360 | "license": [ 1361 | "MIT" 1362 | ], 1363 | "authors": [ 1364 | { 1365 | "name": "Fabien Potencier", 1366 | "email": "fabien@symfony.com" 1367 | }, 1368 | { 1369 | "name": "Symfony Community", 1370 | "homepage": "https://symfony.com/contributors" 1371 | } 1372 | ], 1373 | "description": "Symfony Console Component", 1374 | "homepage": "https://symfony.com", 1375 | "time": "2015-12-22 10:39:06" 1376 | }, 1377 | { 1378 | "name": "symfony/event-dispatcher", 1379 | "version": "v3.0.1", 1380 | "source": { 1381 | "type": "git", 1382 | "url": "https://github.com/symfony/event-dispatcher.git", 1383 | "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf" 1384 | }, 1385 | "dist": { 1386 | "type": "zip", 1387 | "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d36355e026905fa5229e1ed7b4e9eda2e67adfcf", 1388 | "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf", 1389 | "shasum": "" 1390 | }, 1391 | "require": { 1392 | "php": ">=5.5.9" 1393 | }, 1394 | "require-dev": { 1395 | "psr/log": "~1.0", 1396 | "symfony/config": "~2.8|~3.0", 1397 | "symfony/dependency-injection": "~2.8|~3.0", 1398 | "symfony/expression-language": "~2.8|~3.0", 1399 | "symfony/stopwatch": "~2.8|~3.0" 1400 | }, 1401 | "suggest": { 1402 | "symfony/dependency-injection": "", 1403 | "symfony/http-kernel": "" 1404 | }, 1405 | "type": "library", 1406 | "extra": { 1407 | "branch-alias": { 1408 | "dev-master": "3.0-dev" 1409 | } 1410 | }, 1411 | "autoload": { 1412 | "psr-4": { 1413 | "Symfony\\Component\\EventDispatcher\\": "" 1414 | }, 1415 | "exclude-from-classmap": [ 1416 | "/Tests/" 1417 | ] 1418 | }, 1419 | "notification-url": "https://packagist.org/downloads/", 1420 | "license": [ 1421 | "MIT" 1422 | ], 1423 | "authors": [ 1424 | { 1425 | "name": "Fabien Potencier", 1426 | "email": "fabien@symfony.com" 1427 | }, 1428 | { 1429 | "name": "Symfony Community", 1430 | "homepage": "https://symfony.com/contributors" 1431 | } 1432 | ], 1433 | "description": "Symfony EventDispatcher Component", 1434 | "homepage": "https://symfony.com", 1435 | "time": "2015-10-30 23:35:59" 1436 | }, 1437 | { 1438 | "name": "symfony/polyfill-mbstring", 1439 | "version": "v1.0.1", 1440 | "source": { 1441 | "type": "git", 1442 | "url": "https://github.com/symfony/polyfill-mbstring.git", 1443 | "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25" 1444 | }, 1445 | "dist": { 1446 | "type": "zip", 1447 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/49ff736bd5d41f45240cec77b44967d76e0c3d25", 1448 | "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25", 1449 | "shasum": "" 1450 | }, 1451 | "require": { 1452 | "php": ">=5.3.3" 1453 | }, 1454 | "suggest": { 1455 | "ext-mbstring": "For best performance" 1456 | }, 1457 | "type": "library", 1458 | "extra": { 1459 | "branch-alias": { 1460 | "dev-master": "1.0-dev" 1461 | } 1462 | }, 1463 | "autoload": { 1464 | "psr-4": { 1465 | "Symfony\\Polyfill\\Mbstring\\": "" 1466 | }, 1467 | "files": [ 1468 | "bootstrap.php" 1469 | ] 1470 | }, 1471 | "notification-url": "https://packagist.org/downloads/", 1472 | "license": [ 1473 | "MIT" 1474 | ], 1475 | "authors": [ 1476 | { 1477 | "name": "Nicolas Grekas", 1478 | "email": "p@tchwork.com" 1479 | }, 1480 | { 1481 | "name": "Symfony Community", 1482 | "homepage": "https://symfony.com/contributors" 1483 | } 1484 | ], 1485 | "description": "Symfony polyfill for the Mbstring extension", 1486 | "homepage": "https://symfony.com", 1487 | "keywords": [ 1488 | "compatibility", 1489 | "mbstring", 1490 | "polyfill", 1491 | "portable", 1492 | "shim" 1493 | ], 1494 | "time": "2015-11-20 09:19:13" 1495 | }, 1496 | { 1497 | "name": "symfony/process", 1498 | "version": "v3.0.1", 1499 | "source": { 1500 | "type": "git", 1501 | "url": "https://github.com/symfony/process.git", 1502 | "reference": "f4794f1d00f0746621be3020ffbd8c5e0b217ee3" 1503 | }, 1504 | "dist": { 1505 | "type": "zip", 1506 | "url": "https://api.github.com/repos/symfony/process/zipball/f4794f1d00f0746621be3020ffbd8c5e0b217ee3", 1507 | "reference": "f4794f1d00f0746621be3020ffbd8c5e0b217ee3", 1508 | "shasum": "" 1509 | }, 1510 | "require": { 1511 | "php": ">=5.5.9" 1512 | }, 1513 | "type": "library", 1514 | "extra": { 1515 | "branch-alias": { 1516 | "dev-master": "3.0-dev" 1517 | } 1518 | }, 1519 | "autoload": { 1520 | "psr-4": { 1521 | "Symfony\\Component\\Process\\": "" 1522 | }, 1523 | "exclude-from-classmap": [ 1524 | "/Tests/" 1525 | ] 1526 | }, 1527 | "notification-url": "https://packagist.org/downloads/", 1528 | "license": [ 1529 | "MIT" 1530 | ], 1531 | "authors": [ 1532 | { 1533 | "name": "Fabien Potencier", 1534 | "email": "fabien@symfony.com" 1535 | }, 1536 | { 1537 | "name": "Symfony Community", 1538 | "homepage": "https://symfony.com/contributors" 1539 | } 1540 | ], 1541 | "description": "Symfony Process Component", 1542 | "homepage": "https://symfony.com", 1543 | "time": "2015-12-23 11:04:02" 1544 | }, 1545 | { 1546 | "name": "symfony/yaml", 1547 | "version": "v3.0.1", 1548 | "source": { 1549 | "type": "git", 1550 | "url": "https://github.com/symfony/yaml.git", 1551 | "reference": "3df409958a646dad2bc5046c3fb671ee24a1a691" 1552 | }, 1553 | "dist": { 1554 | "type": "zip", 1555 | "url": "https://api.github.com/repos/symfony/yaml/zipball/3df409958a646dad2bc5046c3fb671ee24a1a691", 1556 | "reference": "3df409958a646dad2bc5046c3fb671ee24a1a691", 1557 | "shasum": "" 1558 | }, 1559 | "require": { 1560 | "php": ">=5.5.9" 1561 | }, 1562 | "type": "library", 1563 | "extra": { 1564 | "branch-alias": { 1565 | "dev-master": "3.0-dev" 1566 | } 1567 | }, 1568 | "autoload": { 1569 | "psr-4": { 1570 | "Symfony\\Component\\Yaml\\": "" 1571 | }, 1572 | "exclude-from-classmap": [ 1573 | "/Tests/" 1574 | ] 1575 | }, 1576 | "notification-url": "https://packagist.org/downloads/", 1577 | "license": [ 1578 | "MIT" 1579 | ], 1580 | "authors": [ 1581 | { 1582 | "name": "Fabien Potencier", 1583 | "email": "fabien@symfony.com" 1584 | }, 1585 | { 1586 | "name": "Symfony Community", 1587 | "homepage": "https://symfony.com/contributors" 1588 | } 1589 | ], 1590 | "description": "Symfony Yaml Component", 1591 | "homepage": "https://symfony.com", 1592 | "time": "2015-12-26 13:39:53" 1593 | } 1594 | ], 1595 | "aliases": [], 1596 | "minimum-stability": "stable", 1597 | "stability-flags": [], 1598 | "prefer-stable": false, 1599 | "prefer-lowest": false, 1600 | "platform": { 1601 | "php": ">=5.4.0" 1602 | }, 1603 | "platform-dev": [] 1604 | } 1605 | -------------------------------------------------------------------------------- /config.sample.php: -------------------------------------------------------------------------------- 1 | __DIR__.'/csv', 17 | ]); 18 | $c['doctrine'] = function($c) { 19 | $config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), true); 20 | $conn = array( 21 | 'driver' => 'pdo_sqlite', 22 | 'in_memory' => true, 23 | ); 24 | 25 | $em = \Doctrine\ORM\EntityManager::create($conn, $config); 26 | $c['em'] = $em; 27 | 28 | $tool = new Doctrine\ORM\Tools\SchemaTool($em); 29 | $tool->createSchema([$em->getClassMetadata('Entity\User')]); 30 | 31 | return new Registry($c, 'ORM', ['default' => $conn], ['default' => $c['em']], 'default', 'default', 'Doctrine\ORM\Proxy\Proxy'); 32 | }; 33 | $c['etl'] = [ 34 | 'users' => new Container([ 35 | 'parent' => $c, 36 | 'e' => function($c) { return new CsvExtractor($c['parent']['path'].'/users.csv', ';', '"', 0); }, 37 | 't' => function($c) { 38 | return new ObjectTransformer('Entity\User', new DataMap([ 39 | 0 => 'setId', 40 | 1 => 'setUsername', 41 | 2 => 'setEmail', 42 | ]), $c['parent']['doctrine']); 43 | }, 44 | 'l' => function($c) { 45 | return new ORMLoader($c['parent']['doctrine']); 46 | }, 47 | 'c' => function($c) { return new ORMContext; }, 48 | ]) 49 | ]; 50 | 51 | return $c; 52 | -------------------------------------------------------------------------------- /spec/Knp/ETL/Extractor/CsvExtractorSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith(__DIR__.'/Fixture/TransformableEntity.csv'); 14 | 15 | $this->extract(new Context)->shouldBe(['1', 'a', 'b']); 16 | $this->extract(new Context)->shouldBe(['2', 'c', 'd']); 17 | } 18 | 19 | function it_should_count_lines() 20 | { 21 | $this->beConstructedWith(__DIR__.'/Fixture/TransformableEntity.csv'); 22 | 23 | $this->count()->shouldBe(8); 24 | } 25 | 26 | function it_should_move_to_given_position() 27 | { 28 | $this->beConstructedWith(__DIR__.'/Fixture/TransformableEntity.csv'); 29 | 30 | $this->seek(1); 31 | $this->extract(new Context)->shouldBe(['2', 'c', 'd']); 32 | 33 | $this->seek(0); 34 | $this->extract(new Context)->shouldBe(['1', 'a', 'b']); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /spec/Knp/ETL/Extractor/ExcelExtractorSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith(__DIR__.'/Fixture/TransformableEntity.xls'); 15 | 16 | $this->extract(new Context)->shouldBe([(double) 1, 'a', 'b']); 17 | $this->extract(new Context)->shouldBe([(double) 2, 'c', 'd']); 18 | } 19 | 20 | function it_should_count_lines() 21 | { 22 | $this->beConstructedWith(__DIR__.'/Fixture/TransformableEntity.csv'); 23 | 24 | $this->count()->shouldBe(7); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spec/Knp/ETL/Extractor/Fixture/TransformableEntity.csv: -------------------------------------------------------------------------------- 1 | 1;a;b 2 | 2;c;d 3 | 3;e;f 4 | 4;i;j 5 | 5;k;l 6 | 6;m;n 7 | 2;o;p 8 | -------------------------------------------------------------------------------- /spec/Knp/ETL/Extractor/Fixture/TransformableEntity.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": 123, 3 | "name": "John", 4 | "surname": "Smith" 5 | }, 6 | { 7 | "id": 145, 8 | "name": "Chuck", 9 | "surname": "Norris" 10 | }, 11 | { 12 | "id": 753, 13 | "name": "Alfred", 14 | "surname": "Hitchcock" 15 | }] -------------------------------------------------------------------------------- /spec/Knp/ETL/Extractor/Fixture/TransformableEntity.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docteurklein/php-etl/ffabd6f1b82404d548ed0bcdcde83ec20bbd0035/spec/Knp/ETL/Extractor/Fixture/TransformableEntity.xls -------------------------------------------------------------------------------- /spec/Knp/ETL/Extractor/JsonExtractorSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith(__DIR__.'/Fixture/TransformableEntity.json'); 14 | 15 | $entity = $this->extract(new Context); 16 | $entity->id->shouldBe(123); 17 | $entity->name->shouldBe('John'); 18 | $entity->surname->shouldBe('Smith'); 19 | 20 | $entity = $this->extract(new Context); 21 | $entity->id->shouldBe(145); 22 | $entity->name->shouldBe('Chuck'); 23 | $entity->surname->shouldBe('Norris'); 24 | } 25 | 26 | function it_should_throw_an_exception_if_not_json() 27 | { 28 | $this->beConstructedWith(__DIR__.'/Fixture/TransformableEntity.csv'); 29 | 30 | $this->shouldThrow('\RuntimeException')->duringExtract(new Context); 31 | } 32 | 33 | function it_should_count_lines() 34 | { 35 | $this->beConstructedWith(__DIR__.'/Fixture/TransformableEntity.json'); 36 | 37 | $this->count()->shouldBe(3); 38 | } 39 | 40 | function it_should_move_to_given_position() 41 | { 42 | $this->beConstructedWith(__DIR__.'/Fixture/TransformableEntity.json'); 43 | 44 | $this->seek(1); 45 | $entity = $this->current(); 46 | $entity->id->shouldBe(145); 47 | $entity->name->shouldBe('Chuck'); 48 | $entity->surname->shouldBe('Norris'); 49 | 50 | $this->seek(0); 51 | $entity = $this->current(); 52 | $entity->id->shouldBe(123); 53 | $entity->name->shouldBe('John'); 54 | $entity->surname->shouldBe('Smith'); 55 | } 56 | 57 | function it_should_returns_only_name() 58 | { 59 | $this->beConstructedWith(__DIR__.'/Fixture/TransformableEntity.json', '*.name'); 60 | 61 | $e = $this->extract(new Context); 62 | $e->shouldBe('John'); 63 | 64 | $e = $this->extract(new Context); 65 | $e->shouldBe('Chuck'); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /spec/Knp/ETL/Loader/CsvLoaderSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith(new \SplFileObject(sys_get_temp_dir().'/etl-test.csv', 'w+')); 13 | } 14 | 15 | function it_should_load_existing_data(\stdClass $entity) 16 | { 17 | $this->load(['Cell1', 'Cell2'], new Context)->shouldBe(12); 18 | } 19 | 20 | function letgo() 21 | { 22 | unlink(sys_get_temp_dir().'/etl-test.csv'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /spec/Knp/ETL/Loader/Doctrine/DBALLoaderSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($conn); 15 | } 16 | 17 | function it_should_load_existing_data() 18 | { 19 | $data = []; 20 | $this->load($data, new DBALContext); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spec/Knp/ETL/Loader/Doctrine/ORMLoaderSpec.php: -------------------------------------------------------------------------------- 1 | getManager()->willReturn($manager); 16 | $this->beConstructedWith($doctrine); 17 | } 18 | 19 | function it_should_load_existing_data(\stdClass $entity) 20 | { 21 | $this->load($entity, new Context); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spec/Knp/ETL/Loader/FileLoaderSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith(new \SplFileObject(sys_get_temp_dir().'/etl-test', 'w+')); 13 | } 14 | 15 | function it_should_load_existing_data(\stdClass $entity) 16 | { 17 | $this->load('hey you!', new Context)->shouldBe(8); 18 | } 19 | 20 | function letgo() 21 | { 22 | unlink(sys_get_temp_dir().'/etl-test'); 23 | } 24 | } -------------------------------------------------------------------------------- /spec/Knp/ETL/Transformer/CallbackTransformerSpec.php: -------------------------------------------------------------------------------- 1 | getTransformedData(); 17 | 18 | foreach ($data as $d) { 19 | $target[] = gettype($d); 20 | } 21 | 22 | return $target; 23 | }; 24 | 25 | $this->beConstructedWith($callback); 26 | } 27 | 28 | function it_should_transform_an_array_to_an_array() 29 | { 30 | $data = $this->transform([123, 'John', false], new Context('id')); 31 | 32 | $data->shouldBeArray(); 33 | $data->shouldBe(['integer', 'string', 'boolean']); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /spec/Knp/ETL/Transformer/DataMapSpec.php: -------------------------------------------------------------------------------- 1 | 'id', 18 | 1 => 'name', 19 | 2 => 'surname', 20 | ]; 21 | 22 | $this->beConstructedWith($map); 23 | } 24 | 25 | function it_should_transform_an_array_to_an_array() 26 | { 27 | $data = $this->transform([123, 'John', 'Smith'], new Context('id')); 28 | 29 | $data->shouldBeArray(); 30 | $data->shouldBe(['id' => 123, 'name' => 'John', 'surname' => 'Smith']); 31 | } 32 | 33 | function it_should_transform_an_array_to_an_entity() 34 | { 35 | $context = new Context('id'); 36 | $class = self::className; 37 | $context->setTransformedData(new $class()); 38 | $entity = $this->transform([123, 'John', 'Smith'], $context); 39 | 40 | $entity->shouldHaveType(self::className); 41 | $entity->id->shouldBe(123); 42 | $entity->name->shouldBe('John'); 43 | $entity->surname->shouldBe('Smith'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /spec/Knp/ETL/Transformer/Doctrine/Fixture/TransformableEntity.php: -------------------------------------------------------------------------------- 1 | id = $id; 14 | } 15 | 16 | public function setName($name) 17 | { 18 | $this->name = $name; 19 | } 20 | 21 | public function setSurname($surname) 22 | { 23 | $this->surname = $surname; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /spec/Knp/ETL/Transformer/Doctrine/ObjectTransformerSpec.php: -------------------------------------------------------------------------------- 1 | getRepository(self::className)->willReturn($repository); 21 | 22 | $map = new DataMap([ 23 | 0 => 'id', 24 | 1 => 'name', 25 | 2 => 'surname', 26 | ]); 27 | 28 | $this->beConstructedWith(self::className, $map, $doctrine); 29 | } 30 | 31 | function it_should_transform_an_array_to_an_entity() 32 | { 33 | $entity = $this->transform(['id', 'name', 'surname'], new ORMContext('id')); 34 | 35 | $entity->shouldHaveType(self::className); 36 | $entity->id->shouldBe('id'); 37 | $entity->name->shouldBe('name'); 38 | $entity->surname->shouldBe('surname'); 39 | } 40 | 41 | // @TODO move this to DataMap test 42 | function it_should_fail_if_column_count_does_not_match() 43 | { 44 | $this->shouldThrow(new \LogicException('input does not contain expected size 3, 4 given')) 45 | ->duringTransform(['id', 'name', 'surname', 'test'], new ORMContext('id')) 46 | ; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Knp/ETL/Context/Context.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Context implements ContextInterface 11 | { 12 | private $extractedData; 13 | private $transformedData; 14 | private $identifier; 15 | 16 | public function __construct($id = null) 17 | { 18 | $this->identifier = $id; 19 | $this->extractedData = []; 20 | $this->transformedData = []; 21 | } 22 | 23 | /** 24 | * @return array the shared context between extractors, loader and persisters 25 | **/ 26 | public function getExtractedData() 27 | { 28 | return $this->extractedData; 29 | } 30 | 31 | /** 32 | * @param mixed the extracted data 33 | **/ 34 | public function setExtractedData($data) 35 | { 36 | $this->extractedData = $data; 37 | } 38 | 39 | /** 40 | * @return mixed the transformed data 41 | **/ 42 | public function getTransformedData() 43 | { 44 | return $this->transformedData; 45 | } 46 | 47 | /** 48 | * @param mixed the transformed data 49 | **/ 50 | public function setTransformedData($data) 51 | { 52 | $this->transformedData = $data; 53 | } 54 | 55 | /** 56 | * @return mixed the identifier value of current data 57 | **/ 58 | public function getIdentifier() 59 | { 60 | return $this->identifier; 61 | } 62 | 63 | /** 64 | * @param mixed the identifier value of current data 65 | **/ 66 | public function setIdentifier($id) 67 | { 68 | $this->identifier = $id; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Knp/ETL/Context/Doctrine/DBALContext.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class DBALContext extends BaseContext 11 | { 12 | public function __construct($id = null, $tableName = null) 13 | { 14 | parent::__construct($id); 15 | $this->tableName = $tableName; 16 | } 17 | 18 | private $tableName; 19 | 20 | public function getTableName() 21 | { 22 | return $this->tableName; 23 | } 24 | 25 | /** 26 | * @param mixed the extracted data 27 | **/ 28 | public function setTableName($name) 29 | { 30 | $this->tableName = $name; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Knp/ETL/Context/Doctrine/ORMContext.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ORMContext extends BaseContext 11 | { 12 | private $shouldFlush = false; 13 | private $shouldClear = false; 14 | 15 | public function shouldFlush($should = null) 16 | { 17 | if (null === $should) { 18 | return $this->shouldFlush; 19 | } 20 | 21 | $this->shouldFlush = $should; 22 | } 23 | 24 | public function shouldClear($should = null) 25 | { 26 | if (null === $should) { 27 | return $this->shouldClear; 28 | } 29 | 30 | $this->shouldClear = $should; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Knp/ETL/ContextInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface ContextInterface 9 | { 10 | /** 11 | * @return array the shared context between extractors, loader and persisters 12 | **/ 13 | public function getExtractedData(); 14 | 15 | /** 16 | * @param mixed the extracted data 17 | **/ 18 | public function setExtractedData($data); 19 | 20 | /** 21 | * @return mixed the transformed data 22 | **/ 23 | public function getTransformedData(); 24 | 25 | /** 26 | * @param mixed the transformed data 27 | **/ 28 | public function setTransformedData($data); 29 | 30 | /** 31 | * @return mixed the identifier value of current data 32 | **/ 33 | public function getIdentifier(); 34 | 35 | /** 36 | * @param mixed the identifier value of current data 37 | **/ 38 | public function setIdentifier($id); 39 | } 40 | -------------------------------------------------------------------------------- /src/Knp/ETL/Extractor/CachedExtractor.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class CachedExtractor extends \ArrayIterator implements ExtractorInterface 14 | { 15 | public function __construct(\Iterator $extractor) 16 | { 17 | parent::__construct(iterator_to_array($extractor)); 18 | } 19 | 20 | public function extract(ContextInterface $context) 21 | { 22 | return $this->current(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Knp/ETL/Extractor/CsvExtractor.php: -------------------------------------------------------------------------------- 1 | 12 | * @TODO just make a LoggableIterator a composition of \Iterator and Logger ? 13 | */ 14 | class CsvExtractor implements ExtractorInterface, \Iterator, \Countable 15 | { 16 | private $csv; 17 | private $identifierColumn; 18 | private $nbLines; 19 | private $logger; 20 | 21 | public function __construct($filename, $delimiter = ';', $enclosure = '"', $identifierColumn = null, LoggerInterface $logger = null) 22 | { 23 | $this->logger = $logger ?: new NullLogger(); 24 | $this->logger->debug('Extracting CSV', ['filepath' => $filename]); 25 | 26 | $this->csv = new \SplFileObject($filename); 27 | $this->csv->setFlags(\SplFileObject::READ_CSV); 28 | $this->csv->setCsvControl($delimiter, $enclosure); 29 | 30 | $this->identifierColumn = $identifierColumn; 31 | } 32 | 33 | public function extract(ContextInterface $context) 34 | { 35 | $data = $this->csv->current(); 36 | if (null !== $this->identifierColumn) { 37 | $context->setIdentifier($data[$this->identifierColumn]); 38 | } 39 | $this->csv->next(); 40 | 41 | return $data; 42 | } 43 | 44 | public function rewind() 45 | { 46 | return $this->csv->rewind(); 47 | } 48 | 49 | public function current() 50 | { 51 | return $this->csv->current(); 52 | } 53 | 54 | public function key() 55 | { 56 | return $this->csv->key(); 57 | } 58 | 59 | public function next() 60 | { 61 | $next = $this->csv->next(); 62 | $this->logger->debug('Next csv element', ['name' => $this->csv->getBaseName(), 'value' => $this->key()]); 63 | 64 | return $next; 65 | } 66 | 67 | public function valid() 68 | { 69 | return $this->csv->valid(); 70 | } 71 | 72 | public function count() 73 | { 74 | if (null === $this->nbLines) { 75 | // Store flags and position 76 | $flags = $this->csv->getFlags(); 77 | $current = $this->csv->key(); 78 | 79 | // Prepare count by resetting flags as READ_CSV for example make the trick very slow 80 | $this->csv->setFlags(null); 81 | 82 | // Go to the larger INT we can as seek will not throw exception, errors, notice if we go beyond the bottom line 83 | $this->csv->seek(PHP_INT_MAX); 84 | 85 | // We store the key position 86 | // As key starts at 0, we add 1 87 | $this->nbLines = $this->csv->key() + 1; 88 | 89 | // We move to old position 90 | // As seek method is longer with line number < to the max line number, it is better to count at the beginning of iteration 91 | $this->csv->seek($current); 92 | 93 | // Re set flags 94 | $this->csv->setFlags($flags); 95 | } 96 | 97 | return $this->nbLines; 98 | } 99 | 100 | public function seek($position) 101 | { 102 | return $this->csv->seek($position); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Knp/ETL/Extractor/Doctrine/ORMExtractor.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class ORMExtractor implements ExtractorInterface, \Iterator 14 | { 15 | private $query; 16 | private $logger; 17 | protected $iterator; 18 | 19 | /** 20 | * Could be a Query or a QueryBuilder 21 | */ 22 | public function __construct($query, LoggerInterface $logger = null) 23 | { 24 | $this->logger = $logger ?: new NullLogger(); 25 | $this->query = $query; 26 | } 27 | 28 | public function extract(ContextInterface $context) 29 | { 30 | $current = $this->current(); 31 | $this->next(); 32 | 33 | return $current[0]; 34 | } 35 | 36 | public function rewind() 37 | { 38 | return $this->getIterator()->rewind(); 39 | } 40 | 41 | public function current() 42 | { 43 | return $this->getIterator()->current(); 44 | } 45 | 46 | public function key() 47 | { 48 | return $this->getIterator()->key(); 49 | } 50 | 51 | public function next() 52 | { 53 | $next = $this->getIterator()->next(); 54 | $this->logger->debug('Next SQL', ['sql' => $this->getQuery()->getSql()]); 55 | 56 | return $next; 57 | } 58 | 59 | public function valid() 60 | { 61 | return $this->getIterator()->valid(); 62 | } 63 | 64 | public function getIterator() 65 | { 66 | if (null === $this->iterator) { 67 | $this->iterator = $this->getQuery()->iterate(); 68 | } 69 | 70 | return $this->iterator; 71 | } 72 | 73 | public function getQuery() 74 | { 75 | if ($this->query instanceof \Doctrine\ORM\QueryBuilder) { 76 | return $this->query->getQuery(); 77 | } 78 | 79 | return $this->query; 80 | } 81 | } -------------------------------------------------------------------------------- /src/Knp/ETL/Extractor/Doctrine/SliceableORMExtractor.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class SliceableORMExtractor extends ORMExtractor implements \Countable 12 | { 13 | private $paginator; 14 | 15 | public function __construct($query, $fetchJoinCollection = true) 16 | { 17 | parent::__construct($query); 18 | 19 | $this->paginator = new Paginator($query, $fetchJoinCollection); 20 | } 21 | 22 | public function extract(ContextInterface $context) 23 | { 24 | $current = $this->current(); 25 | $this->next(); 26 | 27 | return $current; 28 | } 29 | 30 | public function count() 31 | { 32 | return $this->paginator->count(); 33 | } 34 | 35 | public function slice($offset, $length) 36 | { 37 | $this->paginator 38 | ->getQuery() 39 | ->setFirstResult($offset) 40 | ->setMaxResults($length) 41 | ; 42 | 43 | return $this; 44 | } 45 | 46 | public function getIterator() 47 | { 48 | if (null === $this->iterator) { 49 | $this->iterator = $this->paginator->getIterator(); 50 | } 51 | 52 | return $this->iterator; 53 | } 54 | } -------------------------------------------------------------------------------- /src/Knp/ETL/Extractor/ExcelExtractor.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class ExcelExtractor implements ExtractorInterface, \Iterator, \Countable 15 | { 16 | protected $worksheetIterator; 17 | protected $identifierColumn; 18 | protected $nbLines; 19 | protected $headerRowNumber = 1; 20 | protected $logger; 21 | 22 | public function __construct($filename, $identifierColumn = null, $headerRowNumber = 1, $activeSheet = null, LoggerInterface $logger = null) 23 | { 24 | $this->logger = $logger ?: new NullLogger(); 25 | $this->logger->debug('Extracting Excel', ['filename' => $filename]); 26 | 27 | $excel = PHPExcel_IOFactory::load($filename); 28 | 29 | if (null !== $activeSheet) { 30 | $excel->setActiveSheetIndex($activeSheet); 31 | } 32 | 33 | $this->identifierColumn = $identifierColumn; 34 | $this->headerRowNumber = $headerRowNumber; 35 | $this->worksheetIterator = $excel->getActiveSheet()->getRowIterator(); 36 | $this->rewind(); 37 | } 38 | 39 | public function extract(ContextInterface $context) 40 | { 41 | if (!$this->valid()) { 42 | return false; 43 | } 44 | 45 | $data = $this->current(); 46 | $this->next(); 47 | 48 | if (null !== $this->identifierColumn) { 49 | $context->setIdentifier($data[$this->identifierColumn]); 50 | } 51 | 52 | return $data; 53 | } 54 | 55 | public function rewind() 56 | { 57 | $this->worksheetIterator->rewind(); 58 | $this->worksheetIterator->seek($this->headerRowNumber); 59 | } 60 | 61 | public function current() 62 | { 63 | $row = $this->worksheetIterator->current(); 64 | $cellIterator = $row->getCellIterator(); 65 | $data = array(); 66 | 67 | foreach ($cellIterator as $cell) { 68 | $data[] = $cell->getCalculatedValue(); 69 | } 70 | 71 | return $data; 72 | } 73 | 74 | public function key() 75 | { 76 | return $this->worksheetIterator->key(); 77 | } 78 | 79 | public function next() 80 | { 81 | $next = $this->worksheetIterator->next(); 82 | $this->logger->debug('Next Excel element', ['key' => $this->key()]); 83 | 84 | return $next; 85 | } 86 | 87 | public function valid() 88 | { 89 | return $this->worksheetIterator->valid(); 90 | } 91 | 92 | public function count() 93 | { 94 | if (null === $this->nbLines) { 95 | // Store position 96 | $current = $this->worksheetIterator->key(); 97 | 98 | $count = $current - $this->headerRowNumber; 99 | while ($this->worksheetIterator->valid()) { 100 | $count += 1; 101 | $this->worksheetIterator->next(); 102 | } 103 | 104 | // move back to the old position 105 | $this->worksheetIterator->seek($current); 106 | } 107 | 108 | return $this->nbLines = $count; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Knp/ETL/Extractor/JsonExtractor.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class JsonExtractor implements ExtractorInterface, \Iterator, \Countable 14 | { 15 | private $json; 16 | private $identifierColumn; 17 | private $resource; 18 | private $adapter; 19 | private $path; 20 | private $logger; 21 | 22 | /** 23 | * Regarding the following json structure : 24 | * { 25 | * items: [{ 26 | * 'name': 'Riri' 27 | * }, { 28 | * 'name': 'Fifi' 29 | * }, { 30 | * 'name': 'Loulou' 31 | * }] 32 | * } 33 | * You can extract only all names with $path value : "items.*.name" 34 | * 35 | * @param string $resource Filename or URL for the json file 36 | * @param string $path The path in json file to go to your target nodes. Example : "nodes.*.node" 37 | * @param Closure $adapter A closure which wraps the way to get the content of json file 38 | * @param LoggerInterface $logger The logger instance 39 | */ 40 | public function __construct($resource, $path = null, \Closure $adapter = null, LoggerInterface $logger = null) 41 | { 42 | $this->logger = $logger ?: new NullLogger(); 43 | $this->logger->debug('Extracting JSON', ['path' => $resource]); 44 | 45 | $this->resource = $resource; 46 | $this->adapter = $adapter; 47 | 48 | if (is_string($path)) { 49 | $this->path = explode('.', $path); 50 | } 51 | } 52 | 53 | public function extract(ContextInterface $context) 54 | { 55 | $current = $this->current(); 56 | $this->next(); 57 | 58 | return $current; 59 | } 60 | 61 | public function rewind() 62 | { 63 | return $this->getIterator()->rewind(); 64 | } 65 | 66 | public function current() 67 | { 68 | return $this->getIterator()->current(); 69 | } 70 | 71 | public function key() 72 | { 73 | return $this->getIterator()->key(); 74 | } 75 | 76 | public function next() 77 | { 78 | $next = $this->getIterator()->next(); 79 | $this->logger->debug('Next JSON element', ['path' => $this->resource, 'key' => $this->key()]); 80 | 81 | return $next; 82 | } 83 | 84 | public function valid() 85 | { 86 | return $this->getIterator()->valid(); 87 | } 88 | 89 | public function count() 90 | { 91 | return $this->getIterator()->count(); 92 | } 93 | 94 | public function seek($position) 95 | { 96 | return $this->getIterator()->seek($position); 97 | } 98 | 99 | private function getIterator() 100 | { 101 | if (null === $this->json) { 102 | $json = json_decode($this->getContent()); 103 | 104 | if (null === $json) { 105 | throw new \RuntimeException(sprintf('%s could not be parsed as json file', $this->resource)); 106 | } 107 | 108 | if (is_array($this->path)) { 109 | $json = $this->parseJson(new \RecursiveArrayIterator($json), array(), 0); 110 | } 111 | 112 | // If we gets only an object, put it in array to avoid iterate on its properties 113 | if (!is_array($json)) { 114 | $json = array($json); 115 | } 116 | 117 | $this->json = new \ArrayIterator($json); 118 | } 119 | 120 | return $this->json; 121 | } 122 | 123 | private function getContent() 124 | { 125 | if (null === $this->adapter) { 126 | return file_get_contents($this->resource); 127 | } 128 | 129 | $adapter = $this->adapter; 130 | 131 | return $adapter($this->resource); 132 | } 133 | 134 | private function parseJson(\RecursiveArrayIterator $json, array $data, $level) 135 | { 136 | foreach ($json as $k => $j) { 137 | if (!isset($this->path[$level])) { 138 | return $data; 139 | } 140 | 141 | if ($k !== $this->path[$level] && $this->path[$level] !== '*') { 142 | continue; 143 | } 144 | 145 | if ($k === end($this->path)) { 146 | if (is_array($j)) { 147 | $data = array_merge($data, $j); 148 | } else { 149 | $data[] = $j; 150 | } 151 | } 152 | 153 | if ($json->hasChildren()) { 154 | $data = $this->parseJson($json->getChildren(), $data, $level + 1); 155 | } 156 | } 157 | 158 | return $data; 159 | } 160 | } -------------------------------------------------------------------------------- /src/Knp/ETL/Extractor/OrderedCsvExtractor.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class OrderedExtractor extends \ArrayIterator implements ExtractorInterface 14 | { 15 | public function __construct(\Iterator $extractor, callable $orderer) 16 | { 17 | $rows = $input = $this->reorder($filename, $orderer); 18 | 19 | parent::__construct($rows); 20 | } 21 | 22 | private function reorder(\Iterator $extractor, callable $orderer) 23 | { 24 | $rows = []; 25 | // TODO is this necessary? 26 | foreach ($extractor as $row) { 27 | $rows[] = $row; 28 | } 29 | 30 | usort($rows, $orderer); 31 | 32 | return $rows; 33 | } 34 | 35 | public function extract(ContextInterface $context) 36 | { 37 | return $this->current(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Knp/ETL/Extractor/RegexFilenameCsvExtractor.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class RegexFilenameCsvExtractor extends CsvExtractor 13 | { 14 | public function __construct($csvPath, $regex, $delimiter = ';', $enclosure = '"') 15 | { 16 | $filename = null; 17 | $files = Finder::create() 18 | ->files() 19 | ->followLinks() 20 | ->sortByName() 21 | ->name($regex) 22 | ->in($csvPath) 23 | ->depth('== 0') 24 | ; 25 | 26 | foreach ($files as $csv) { 27 | $filename = (string) $csv; 28 | } 29 | 30 | parent::__construct($filename, $delimiter, $enclosure); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Knp/ETL/ExtractorInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface ExtractorInterface 9 | { 10 | /** 11 | * extract data to be transformed 12 | * 13 | * @return array 14 | */ 15 | function extract(ContextInterface $context); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/Knp/ETL/Loader/CsvLoader.php: -------------------------------------------------------------------------------- 1 | file->fputcsv($data); 12 | $this->logger->debug('Write a field array as a CSV line', ['data' => $data, 'filename' => $this->file->getBasename(), 'bytes' => $r]); 13 | 14 | return $r; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Knp/ETL/Loader/Doctrine/DBALLoader.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class DBALLoader implements LoaderInterface 13 | { 14 | private $conn; 15 | 16 | public function __construct(Connection $conn) 17 | { 18 | $this->conn = $conn; 19 | } 20 | 21 | public function load($data, ContextInterface $context) 22 | { 23 | if ($id = $context->getIdentifier()) { 24 | return $this->conn->update($context->getTableName(), $data, ['id' => $id]); 25 | } 26 | 27 | // @TODO get id 28 | //$context->setIdentifier($id); 29 | //$data['id'] = $this->conn->fetchColumn("SELECT NEXTVAL('fos_user_id_seq')", [], 0); 30 | 31 | return $this->conn->insert($context->getTableName(), $data); 32 | } 33 | 34 | public function flush(ContextInterface $context) 35 | { 36 | } 37 | 38 | public function clear(ContextInterface $context) 39 | { 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/Knp/ETL/Loader/Doctrine/ORMLoader.php: -------------------------------------------------------------------------------- 1 | doctrine = $doctrine; 23 | $this->flushEvery = $flushEvery; 24 | $this->logger = $logger ?: new NullLogger(); 25 | } 26 | 27 | public function load($entity, ContextInterface $context) 28 | { 29 | if (null === $this->entityClass) { 30 | $this->entityClass = get_class($entity); 31 | } 32 | 33 | $this->doctrine->getManager()->persist($entity); 34 | 35 | $shouldFlush = $shouldClear = false; 36 | if ($context instanceof ORMContext) { 37 | $shouldFlush = $context->shouldFlush(); 38 | $context->shouldFlush(false); // TODO really ? 39 | 40 | $shouldClear = $context->shouldClear(); 41 | $context->shouldClear(false); // TODO really ? 42 | } 43 | 44 | $this->counter++; 45 | 46 | if ($this->counter % $this->flushEvery === 0 || $shouldFlush) { 47 | $this->flush($context); 48 | } 49 | 50 | if ($this->counter % $this->flushEvery === 0 || $shouldClear) { 51 | $this->clear($context); 52 | } 53 | } 54 | 55 | public function flush(ContextInterface $context) 56 | { 57 | $this->doctrine->getManager()->flush(); 58 | $this->logger->debug('Doctrine flush', ['persist_hits' => $this->counter]); 59 | } 60 | 61 | public function clear(ContextInterface $context) 62 | { 63 | $this->doctrine->getManager()->clear($this->entityClass); 64 | $this->logger->debug('Doctrine clean', ['persist_hits' => $this->counter]); 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /src/Knp/ETL/Loader/Doctrine/Registry.php: -------------------------------------------------------------------------------- 1 | c = $c; 14 | parent::__construct($name, $connections, $managers, $defaultConnection, $defaultManager, $proxyInterfaceName); 15 | 16 | } 17 | 18 | public function getService($name) 19 | { 20 | return $this->c['em']; 21 | } 22 | 23 | public function resetService($name) 24 | { 25 | $closure = $this->c->raw('em'); 26 | $this->c['em'] = $closure($this->c); 27 | } 28 | 29 | public function getAliasNamespace($alias) 30 | { 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Knp/ETL/Loader/FileLoader.php: -------------------------------------------------------------------------------- 1 | logger = $logger ?: new NullLogger(); 18 | $this->file = $file; 19 | } 20 | 21 | public function load($data, ContextInterface $context) 22 | { 23 | $r = $this->file->fwrite($data); 24 | $this->logger->debug('Write to file', ['data' => $data, 'filename' => $this->file->getBasename(), 'bytes' => $r]); 25 | 26 | return $r; 27 | } 28 | 29 | public function flush(ContextInterface $context) 30 | { 31 | } 32 | 33 | public function clear(ContextInterface $context) 34 | { 35 | } 36 | } -------------------------------------------------------------------------------- /src/Knp/ETL/LoaderInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface LoaderInterface 9 | { 10 | /** 11 | * loads data into some other persistence service 12 | * 13 | * @param mixed $data the data to load 14 | * @param ContextInterface $context the shared context for current iteration / row / whatever 15 | * 16 | * @return mixed 17 | */ 18 | function load($data, ContextInterface $context); 19 | 20 | /** 21 | * Flush the loader 22 | * 23 | * @param ContextInterface $context the shared context for current iteration / row / whatever 24 | **/ 25 | function flush(ContextInterface $context); 26 | 27 | /** 28 | * Reset the loader 29 | * 30 | * @param ContextInterface $context the shared context for current iteration / row / whatever 31 | **/ 32 | function clear(ContextInterface $context); 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/Knp/ETL/Transformer/CallbackTransformer.php: -------------------------------------------------------------------------------- 1 | callback = $callback; 15 | } 16 | 17 | public function transform($data, ContextInterface $context) 18 | { 19 | return call_user_func($this->callback, $data, $context); 20 | } 21 | } -------------------------------------------------------------------------------- /src/Knp/ETL/Transformer/DataMap.php: -------------------------------------------------------------------------------- 1 | map = $map; 16 | } 17 | 18 | public function set($input, &$target) 19 | { 20 | $accessor = PropertyAccess::createPropertyAccessor(); 21 | 22 | foreach ($this->map as $inputPropertyPath => $output) { 23 | if (is_array($input)) { 24 | $path = sprintf('[%s]', $inputPropertyPath); 25 | } elseif (is_object($input)) { 26 | $path = $inputPropertyPath; 27 | } else { 28 | throw new \InvalidArgumentException('Input should be either an object or array'); 29 | } 30 | 31 | $value = $accessor->getValue($input, $path); 32 | 33 | if (is_object($target)) { 34 | $path = $output; 35 | } elseif (is_array($target)) { 36 | $path = sprintf('[%s]', $output); 37 | } else { 38 | throw new \InvalidArgumentException('Target should be either an object or array'); 39 | } 40 | 41 | $accessor->setValue($target, $path, $value); 42 | } 43 | } 44 | 45 | public function transform($data, ContextInterface $context) 46 | { 47 | $target = $context->getTransformedData(); 48 | 49 | $this->set($data, $target); 50 | 51 | return $target; 52 | } 53 | 54 | public function __invoke($input, $target) 55 | { 56 | return $this->set($input, $target); 57 | } 58 | 59 | public function verifyCount($input) 60 | { 61 | if (count($input) !== count($this->map)) { 62 | throw new \LogicException(sprintf( 63 | 'input does not contain expected size %d, %d given', 64 | count($this->map), 65 | count($input) 66 | )); 67 | } 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /src/Knp/ETL/Transformer/Doctrine/ObjectTransformer.php: -------------------------------------------------------------------------------- 1 | className = $className; 22 | $this->mapper = $mapper; 23 | $this->doctrine = $doctrine; 24 | $this->logger = $logger ?: new NullLogger(); 25 | } 26 | 27 | public function transform($data, ContextInterface $context) 28 | { 29 | $this->mapper->verifyCount($data); 30 | 31 | $id = $context->getIdentifier(); 32 | $this->logger->info('Transforming data', ['id' => $id]); 33 | 34 | $object = $this->doctrine->getRepository($this->className)->find($id); 35 | 36 | if (null === $object) { 37 | //TODO use a configurable factory here 38 | $object = new $this->className; 39 | $this->logger->info('Creating new object', ['class' => $this->className]); 40 | } 41 | 42 | $this->mapper->set($data, $object); 43 | 44 | return $object; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Knp/ETL/TransformerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface TransformerInterface 9 | { 10 | /** 11 | * transforms array data into specific representation 12 | * 13 | * @param mixed $data the extracted data to transform 14 | * 15 | * @return mixed the transformed data 16 | */ 17 | function transform($data, ContextInterface $context); 18 | } 19 | 20 | --------------------------------------------------------------------------------