├── .gitignore ├── README.md ├── bootstrap.php ├── cli-config.php ├── composer.json ├── composer.lock ├── example ├── example.php └── example.sql ├── phpunit.xml ├── src └── DoctrineEventStore │ ├── EntityWithEvents.php │ ├── EventCollector.php │ ├── Transaction.php │ └── UnitOfWork.php └── tests ├── Fixtures ├── MockAggregate.php ├── OtherThingHappened.php ├── ThingHappened.php └── YetAnotherThingHappened.php └── UnitOfWorkTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | example/config.php 2 | vendor/ 3 | .idea 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Doctrine Event Store 2 | 3 | Proof of concept for retrieving Domain Events raised from Doctrine 2 entities and storing them safely with Broadway as an Event Store. 4 | 5 | There's some valid use cases but mostly I wanted to horrify some people. 6 | 7 | ### Prior Art 8 | 9 | - The Unit of Work is a shameless knockoff of [boekkooi/DoctrineEventStoreBundle](https://github.com/boekkooi/DoctrineEventStoreBundle). You should totally check it out. 10 | - Broadway and Doctrine 2, obviously. 11 | 12 | ### Notes 13 | 14 | - Needs some more tests 15 | - Need to add support for auto-incremented entities 16 | - To use it safely, you need to open an EntityManager transaction explicitly around both the EventUoW and Doctrine UoW flush. This could be automated using onFlush/postFlush but there's no onError event to trigger an evenly stacked rollback in the event of an error. 17 | - The EventCollector iterates over the entire identity map rather than just those being updated. This takes a few extra iterations but is more predictable than relying on the change detection. 18 | -------------------------------------------------------------------------------- /bootstrap.php: -------------------------------------------------------------------------------- 1 | =5.3" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "@stable" 30 | }, 31 | "type": "library", 32 | "extra": { 33 | "branch-alias": { 34 | "dev-master": "2.3-dev" 35 | } 36 | }, 37 | "autoload": { 38 | "psr-0": { 39 | "Assert": "lib/" 40 | }, 41 | "files": [ 42 | "lib/Assert/functions.php" 43 | ] 44 | }, 45 | "notification-url": "https://packagist.org/downloads/", 46 | "license": [ 47 | "BSD-2-Clause" 48 | ], 49 | "authors": [ 50 | { 51 | "name": "Benjamin Eberlei", 52 | "email": "kontakt@beberlei.de" 53 | } 54 | ], 55 | "description": "Thin assertion library for input validation in business models.", 56 | "keywords": [ 57 | "assert", 58 | "assertion", 59 | "validation" 60 | ], 61 | "time": "2015-08-21 16:50:17" 62 | }, 63 | { 64 | "name": "broadway/broadway", 65 | "version": "0.5.2", 66 | "source": { 67 | "type": "git", 68 | "url": "https://github.com/qandidate-labs/broadway.git", 69 | "reference": "0fce8c532086ad249a46fec179759c1fa089e35d" 70 | }, 71 | "dist": { 72 | "type": "zip", 73 | "url": "https://api.github.com/repos/qandidate-labs/broadway/zipball/0fce8c532086ad249a46fec179759c1fa089e35d", 74 | "reference": "0fce8c532086ad249a46fec179759c1fa089e35d", 75 | "shasum": "" 76 | }, 77 | "require": { 78 | "beberlei/assert": "~2.0", 79 | "broadway/uuid-generator": "~0.1.0", 80 | "php": ">=5.3.3", 81 | "rhumsaa/uuid": "~2.4" 82 | }, 83 | "require-dev": { 84 | "doctrine/dbal": "~2.4", 85 | "doctrine/mongodb": "~1.0", 86 | "elasticsearch/elasticsearch": "~1.0", 87 | "instaclick/base-test-bundle": "~0.5", 88 | "monolog/monolog": "~1.8", 89 | "symfony/console": "~2.4", 90 | "symfony/proxy-manager-bridge": "~2.4" 91 | }, 92 | "suggest": { 93 | "doctrine/dbal": "For the BroadwayBundle (to persist events)", 94 | "doctrine/mongodb": "For persisting saga states (required for BroadwayBundle)", 95 | "elasticsearch/elasticsearch": "For persisting read models (required for BroadwayBundle)", 96 | "psr/log-implementation": "Implementation for PSR3, LoggerInterface", 97 | "symfony/console": "For the BroadwayBundle", 98 | "symfony/proxy-manager-bridge": "For the BroadwayBundle" 99 | }, 100 | "type": "library", 101 | "extra": { 102 | "branch-alias": { 103 | "dev-master": "0.6.x-dev" 104 | } 105 | }, 106 | "autoload": { 107 | "psr-0": { 108 | "Broadway\\": "src/" 109 | } 110 | }, 111 | "notification-url": "https://packagist.org/downloads/", 112 | "license": [ 113 | "MIT" 114 | ], 115 | "authors": [ 116 | { 117 | "name": "othillo", 118 | "email": "othillo@othillo.nl" 119 | }, 120 | { 121 | "name": "Alexander", 122 | "email": "iam.asm89@gmail.com" 123 | }, 124 | { 125 | "name": "Qandidate.com", 126 | "homepage": "http://labs.qandidate.com/" 127 | }, 128 | { 129 | "name": "Willem-Jan", 130 | "email": "wjzijderveld@gmail.com" 131 | }, 132 | { 133 | "name": "Emil", 134 | "email": "emil@broekmeulen.com" 135 | }, 136 | { 137 | "name": "Fritsjan", 138 | "email": "fritsjan@qandidate.com" 139 | } 140 | ], 141 | "description": "Infrastructure and testing helpers for creating CQRS and event sourced applications.", 142 | "keywords": [ 143 | "cqrs", 144 | "ddd", 145 | "domain-driven design", 146 | "event sourcing" 147 | ], 148 | "time": "2015-03-31 18:15:05" 149 | }, 150 | { 151 | "name": "broadway/uuid-generator", 152 | "version": "0.1.0", 153 | "source": { 154 | "type": "git", 155 | "url": "https://github.com/qandidate-labs/broadway-uuid-generator.git", 156 | "reference": "58380b723c671807131fe9ac0ff077f162be47bb" 157 | }, 158 | "dist": { 159 | "type": "zip", 160 | "url": "https://api.github.com/repos/qandidate-labs/broadway-uuid-generator/zipball/58380b723c671807131fe9ac0ff077f162be47bb", 161 | "reference": "58380b723c671807131fe9ac0ff077f162be47bb", 162 | "shasum": "" 163 | }, 164 | "require-dev": { 165 | "rhumsaa/uuid": "~2.4" 166 | }, 167 | "suggest": { 168 | "rhumsaa/uuid": "Allows creating UUIDs" 169 | }, 170 | "type": "library", 171 | "autoload": { 172 | "psr-0": { 173 | "Broadway\\UuidGenerator\\": "src/" 174 | } 175 | }, 176 | "notification-url": "https://packagist.org/downloads/", 177 | "license": [ 178 | "MIT" 179 | ], 180 | "description": "UUID generator for broadway/broadway.", 181 | "time": "2014-09-12 14:14:07" 182 | }, 183 | { 184 | "name": "doctrine/annotations", 185 | "version": "v1.2.7", 186 | "source": { 187 | "type": "git", 188 | "url": "https://github.com/doctrine/annotations.git", 189 | "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535" 190 | }, 191 | "dist": { 192 | "type": "zip", 193 | "url": "https://api.github.com/repos/doctrine/annotations/zipball/f25c8aab83e0c3e976fd7d19875f198ccf2f7535", 194 | "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535", 195 | "shasum": "" 196 | }, 197 | "require": { 198 | "doctrine/lexer": "1.*", 199 | "php": ">=5.3.2" 200 | }, 201 | "require-dev": { 202 | "doctrine/cache": "1.*", 203 | "phpunit/phpunit": "4.*" 204 | }, 205 | "type": "library", 206 | "extra": { 207 | "branch-alias": { 208 | "dev-master": "1.3.x-dev" 209 | } 210 | }, 211 | "autoload": { 212 | "psr-0": { 213 | "Doctrine\\Common\\Annotations\\": "lib/" 214 | } 215 | }, 216 | "notification-url": "https://packagist.org/downloads/", 217 | "license": [ 218 | "MIT" 219 | ], 220 | "authors": [ 221 | { 222 | "name": "Roman Borschel", 223 | "email": "roman@code-factory.org" 224 | }, 225 | { 226 | "name": "Benjamin Eberlei", 227 | "email": "kontakt@beberlei.de" 228 | }, 229 | { 230 | "name": "Guilherme Blanco", 231 | "email": "guilhermeblanco@gmail.com" 232 | }, 233 | { 234 | "name": "Jonathan Wage", 235 | "email": "jonwage@gmail.com" 236 | }, 237 | { 238 | "name": "Johannes Schmitt", 239 | "email": "schmittjoh@gmail.com" 240 | } 241 | ], 242 | "description": "Docblock Annotations Parser", 243 | "homepage": "http://www.doctrine-project.org", 244 | "keywords": [ 245 | "annotations", 246 | "docblock", 247 | "parser" 248 | ], 249 | "time": "2015-08-31 12:32:49" 250 | }, 251 | { 252 | "name": "doctrine/cache", 253 | "version": "v1.5.1", 254 | "source": { 255 | "type": "git", 256 | "url": "https://github.com/doctrine/cache.git", 257 | "reference": "2b9cec5a5e722010cbebc91713d4c11eaa064d5e" 258 | }, 259 | "dist": { 260 | "type": "zip", 261 | "url": "https://api.github.com/repos/doctrine/cache/zipball/2b9cec5a5e722010cbebc91713d4c11eaa064d5e", 262 | "reference": "2b9cec5a5e722010cbebc91713d4c11eaa064d5e", 263 | "shasum": "" 264 | }, 265 | "require": { 266 | "php": ">=5.3.2" 267 | }, 268 | "conflict": { 269 | "doctrine/common": ">2.2,<2.4" 270 | }, 271 | "require-dev": { 272 | "phpunit/phpunit": ">=3.7", 273 | "predis/predis": "~1.0", 274 | "satooshi/php-coveralls": "~0.6" 275 | }, 276 | "type": "library", 277 | "extra": { 278 | "branch-alias": { 279 | "dev-master": "1.5.x-dev" 280 | } 281 | }, 282 | "autoload": { 283 | "psr-4": { 284 | "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" 285 | } 286 | }, 287 | "notification-url": "https://packagist.org/downloads/", 288 | "license": [ 289 | "MIT" 290 | ], 291 | "authors": [ 292 | { 293 | "name": "Roman Borschel", 294 | "email": "roman@code-factory.org" 295 | }, 296 | { 297 | "name": "Benjamin Eberlei", 298 | "email": "kontakt@beberlei.de" 299 | }, 300 | { 301 | "name": "Guilherme Blanco", 302 | "email": "guilhermeblanco@gmail.com" 303 | }, 304 | { 305 | "name": "Jonathan Wage", 306 | "email": "jonwage@gmail.com" 307 | }, 308 | { 309 | "name": "Johannes Schmitt", 310 | "email": "schmittjoh@gmail.com" 311 | } 312 | ], 313 | "description": "Caching library offering an object-oriented API for many cache backends", 314 | "homepage": "http://www.doctrine-project.org", 315 | "keywords": [ 316 | "cache", 317 | "caching" 318 | ], 319 | "time": "2015-11-02 18:35:48" 320 | }, 321 | { 322 | "name": "doctrine/collections", 323 | "version": "v1.3.0", 324 | "source": { 325 | "type": "git", 326 | "url": "https://github.com/doctrine/collections.git", 327 | "reference": "6c1e4eef75f310ea1b3e30945e9f06e652128b8a" 328 | }, 329 | "dist": { 330 | "type": "zip", 331 | "url": "https://api.github.com/repos/doctrine/collections/zipball/6c1e4eef75f310ea1b3e30945e9f06e652128b8a", 332 | "reference": "6c1e4eef75f310ea1b3e30945e9f06e652128b8a", 333 | "shasum": "" 334 | }, 335 | "require": { 336 | "php": ">=5.3.2" 337 | }, 338 | "require-dev": { 339 | "phpunit/phpunit": "~4.0" 340 | }, 341 | "type": "library", 342 | "extra": { 343 | "branch-alias": { 344 | "dev-master": "1.2.x-dev" 345 | } 346 | }, 347 | "autoload": { 348 | "psr-0": { 349 | "Doctrine\\Common\\Collections\\": "lib/" 350 | } 351 | }, 352 | "notification-url": "https://packagist.org/downloads/", 353 | "license": [ 354 | "MIT" 355 | ], 356 | "authors": [ 357 | { 358 | "name": "Roman Borschel", 359 | "email": "roman@code-factory.org" 360 | }, 361 | { 362 | "name": "Benjamin Eberlei", 363 | "email": "kontakt@beberlei.de" 364 | }, 365 | { 366 | "name": "Guilherme Blanco", 367 | "email": "guilhermeblanco@gmail.com" 368 | }, 369 | { 370 | "name": "Jonathan Wage", 371 | "email": "jonwage@gmail.com" 372 | }, 373 | { 374 | "name": "Johannes Schmitt", 375 | "email": "schmittjoh@gmail.com" 376 | } 377 | ], 378 | "description": "Collections Abstraction library", 379 | "homepage": "http://www.doctrine-project.org", 380 | "keywords": [ 381 | "array", 382 | "collections", 383 | "iterator" 384 | ], 385 | "time": "2015-04-14 22:21:58" 386 | }, 387 | { 388 | "name": "doctrine/common", 389 | "version": "v2.5.1", 390 | "source": { 391 | "type": "git", 392 | "url": "https://github.com/doctrine/common.git", 393 | "reference": "0009b8f0d4a917aabc971fb089eba80e872f83f9" 394 | }, 395 | "dist": { 396 | "type": "zip", 397 | "url": "https://api.github.com/repos/doctrine/common/zipball/0009b8f0d4a917aabc971fb089eba80e872f83f9", 398 | "reference": "0009b8f0d4a917aabc971fb089eba80e872f83f9", 399 | "shasum": "" 400 | }, 401 | "require": { 402 | "doctrine/annotations": "1.*", 403 | "doctrine/cache": "1.*", 404 | "doctrine/collections": "1.*", 405 | "doctrine/inflector": "1.*", 406 | "doctrine/lexer": "1.*", 407 | "php": ">=5.3.2" 408 | }, 409 | "require-dev": { 410 | "phpunit/phpunit": "~3.7" 411 | }, 412 | "type": "library", 413 | "extra": { 414 | "branch-alias": { 415 | "dev-master": "2.6.x-dev" 416 | } 417 | }, 418 | "autoload": { 419 | "psr-0": { 420 | "Doctrine\\Common\\": "lib/" 421 | } 422 | }, 423 | "notification-url": "https://packagist.org/downloads/", 424 | "license": [ 425 | "MIT" 426 | ], 427 | "authors": [ 428 | { 429 | "name": "Roman Borschel", 430 | "email": "roman@code-factory.org" 431 | }, 432 | { 433 | "name": "Benjamin Eberlei", 434 | "email": "kontakt@beberlei.de" 435 | }, 436 | { 437 | "name": "Guilherme Blanco", 438 | "email": "guilhermeblanco@gmail.com" 439 | }, 440 | { 441 | "name": "Jonathan Wage", 442 | "email": "jonwage@gmail.com" 443 | }, 444 | { 445 | "name": "Johannes Schmitt", 446 | "email": "schmittjoh@gmail.com" 447 | } 448 | ], 449 | "description": "Common Library for Doctrine projects", 450 | "homepage": "http://www.doctrine-project.org", 451 | "keywords": [ 452 | "annotations", 453 | "collections", 454 | "eventmanager", 455 | "persistence", 456 | "spl" 457 | ], 458 | "time": "2015-08-31 13:00:22" 459 | }, 460 | { 461 | "name": "doctrine/dbal", 462 | "version": "v2.5.2", 463 | "source": { 464 | "type": "git", 465 | "url": "https://github.com/doctrine/dbal.git", 466 | "reference": "01dbcbc5cd0a913d751418e635434a18a2f2a75c" 467 | }, 468 | "dist": { 469 | "type": "zip", 470 | "url": "https://api.github.com/repos/doctrine/dbal/zipball/01dbcbc5cd0a913d751418e635434a18a2f2a75c", 471 | "reference": "01dbcbc5cd0a913d751418e635434a18a2f2a75c", 472 | "shasum": "" 473 | }, 474 | "require": { 475 | "doctrine/common": ">=2.4,<2.6-dev", 476 | "php": ">=5.3.2" 477 | }, 478 | "require-dev": { 479 | "phpunit/phpunit": "4.*", 480 | "symfony/console": "2.*" 481 | }, 482 | "suggest": { 483 | "symfony/console": "For helpful console commands such as SQL execution and import of files." 484 | }, 485 | "bin": [ 486 | "bin/doctrine-dbal" 487 | ], 488 | "type": "library", 489 | "extra": { 490 | "branch-alias": { 491 | "dev-master": "2.5.x-dev" 492 | } 493 | }, 494 | "autoload": { 495 | "psr-0": { 496 | "Doctrine\\DBAL\\": "lib/" 497 | } 498 | }, 499 | "notification-url": "https://packagist.org/downloads/", 500 | "license": [ 501 | "MIT" 502 | ], 503 | "authors": [ 504 | { 505 | "name": "Roman Borschel", 506 | "email": "roman@code-factory.org" 507 | }, 508 | { 509 | "name": "Benjamin Eberlei", 510 | "email": "kontakt@beberlei.de" 511 | }, 512 | { 513 | "name": "Guilherme Blanco", 514 | "email": "guilhermeblanco@gmail.com" 515 | }, 516 | { 517 | "name": "Jonathan Wage", 518 | "email": "jonwage@gmail.com" 519 | } 520 | ], 521 | "description": "Database Abstraction Layer", 522 | "homepage": "http://www.doctrine-project.org", 523 | "keywords": [ 524 | "database", 525 | "dbal", 526 | "persistence", 527 | "queryobject" 528 | ], 529 | "time": "2015-09-16 16:29:33" 530 | }, 531 | { 532 | "name": "doctrine/inflector", 533 | "version": "v1.1.0", 534 | "source": { 535 | "type": "git", 536 | "url": "https://github.com/doctrine/inflector.git", 537 | "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" 538 | }, 539 | "dist": { 540 | "type": "zip", 541 | "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", 542 | "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", 543 | "shasum": "" 544 | }, 545 | "require": { 546 | "php": ">=5.3.2" 547 | }, 548 | "require-dev": { 549 | "phpunit/phpunit": "4.*" 550 | }, 551 | "type": "library", 552 | "extra": { 553 | "branch-alias": { 554 | "dev-master": "1.1.x-dev" 555 | } 556 | }, 557 | "autoload": { 558 | "psr-0": { 559 | "Doctrine\\Common\\Inflector\\": "lib/" 560 | } 561 | }, 562 | "notification-url": "https://packagist.org/downloads/", 563 | "license": [ 564 | "MIT" 565 | ], 566 | "authors": [ 567 | { 568 | "name": "Roman Borschel", 569 | "email": "roman@code-factory.org" 570 | }, 571 | { 572 | "name": "Benjamin Eberlei", 573 | "email": "kontakt@beberlei.de" 574 | }, 575 | { 576 | "name": "Guilherme Blanco", 577 | "email": "guilhermeblanco@gmail.com" 578 | }, 579 | { 580 | "name": "Jonathan Wage", 581 | "email": "jonwage@gmail.com" 582 | }, 583 | { 584 | "name": "Johannes Schmitt", 585 | "email": "schmittjoh@gmail.com" 586 | } 587 | ], 588 | "description": "Common String Manipulations with regard to casing and singular/plural rules.", 589 | "homepage": "http://www.doctrine-project.org", 590 | "keywords": [ 591 | "inflection", 592 | "pluralize", 593 | "singularize", 594 | "string" 595 | ], 596 | "time": "2015-11-06 14:35:42" 597 | }, 598 | { 599 | "name": "doctrine/instantiator", 600 | "version": "1.0.5", 601 | "source": { 602 | "type": "git", 603 | "url": "https://github.com/doctrine/instantiator.git", 604 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" 605 | }, 606 | "dist": { 607 | "type": "zip", 608 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", 609 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", 610 | "shasum": "" 611 | }, 612 | "require": { 613 | "php": ">=5.3,<8.0-DEV" 614 | }, 615 | "require-dev": { 616 | "athletic/athletic": "~0.1.8", 617 | "ext-pdo": "*", 618 | "ext-phar": "*", 619 | "phpunit/phpunit": "~4.0", 620 | "squizlabs/php_codesniffer": "~2.0" 621 | }, 622 | "type": "library", 623 | "extra": { 624 | "branch-alias": { 625 | "dev-master": "1.0.x-dev" 626 | } 627 | }, 628 | "autoload": { 629 | "psr-4": { 630 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 631 | } 632 | }, 633 | "notification-url": "https://packagist.org/downloads/", 634 | "license": [ 635 | "MIT" 636 | ], 637 | "authors": [ 638 | { 639 | "name": "Marco Pivetta", 640 | "email": "ocramius@gmail.com", 641 | "homepage": "http://ocramius.github.com/" 642 | } 643 | ], 644 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 645 | "homepage": "https://github.com/doctrine/instantiator", 646 | "keywords": [ 647 | "constructor", 648 | "instantiate" 649 | ], 650 | "time": "2015-06-14 21:17:01" 651 | }, 652 | { 653 | "name": "doctrine/lexer", 654 | "version": "v1.0.1", 655 | "source": { 656 | "type": "git", 657 | "url": "https://github.com/doctrine/lexer.git", 658 | "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" 659 | }, 660 | "dist": { 661 | "type": "zip", 662 | "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", 663 | "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", 664 | "shasum": "" 665 | }, 666 | "require": { 667 | "php": ">=5.3.2" 668 | }, 669 | "type": "library", 670 | "extra": { 671 | "branch-alias": { 672 | "dev-master": "1.0.x-dev" 673 | } 674 | }, 675 | "autoload": { 676 | "psr-0": { 677 | "Doctrine\\Common\\Lexer\\": "lib/" 678 | } 679 | }, 680 | "notification-url": "https://packagist.org/downloads/", 681 | "license": [ 682 | "MIT" 683 | ], 684 | "authors": [ 685 | { 686 | "name": "Roman Borschel", 687 | "email": "roman@code-factory.org" 688 | }, 689 | { 690 | "name": "Guilherme Blanco", 691 | "email": "guilhermeblanco@gmail.com" 692 | }, 693 | { 694 | "name": "Johannes Schmitt", 695 | "email": "schmittjoh@gmail.com" 696 | } 697 | ], 698 | "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", 699 | "homepage": "http://www.doctrine-project.org", 700 | "keywords": [ 701 | "lexer", 702 | "parser" 703 | ], 704 | "time": "2014-09-09 13:34:57" 705 | }, 706 | { 707 | "name": "doctrine/orm", 708 | "version": "v2.5.1", 709 | "source": { 710 | "type": "git", 711 | "url": "https://github.com/doctrine/doctrine2.git", 712 | "reference": "e6a83bedbe67579cb0bfb688e982e617943a2945" 713 | }, 714 | "dist": { 715 | "type": "zip", 716 | "url": "https://api.github.com/repos/doctrine/doctrine2/zipball/e6a83bedbe67579cb0bfb688e982e617943a2945", 717 | "reference": "e6a83bedbe67579cb0bfb688e982e617943a2945", 718 | "shasum": "" 719 | }, 720 | "require": { 721 | "doctrine/cache": "~1.4", 722 | "doctrine/collections": "~1.2", 723 | "doctrine/common": ">=2.5-dev,<2.6-dev", 724 | "doctrine/dbal": ">=2.5-dev,<2.6-dev", 725 | "doctrine/instantiator": "~1.0.1", 726 | "ext-pdo": "*", 727 | "php": ">=5.4", 728 | "symfony/console": "~2.5" 729 | }, 730 | "require-dev": { 731 | "phpunit/phpunit": "~4.0", 732 | "satooshi/php-coveralls": "dev-master", 733 | "symfony/yaml": "~2.1" 734 | }, 735 | "suggest": { 736 | "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" 737 | }, 738 | "bin": [ 739 | "bin/doctrine", 740 | "bin/doctrine.php" 741 | ], 742 | "type": "library", 743 | "extra": { 744 | "branch-alias": { 745 | "dev-master": "2.6.x-dev" 746 | } 747 | }, 748 | "autoload": { 749 | "psr-0": { 750 | "Doctrine\\ORM\\": "lib/" 751 | } 752 | }, 753 | "notification-url": "https://packagist.org/downloads/", 754 | "license": [ 755 | "MIT" 756 | ], 757 | "authors": [ 758 | { 759 | "name": "Roman Borschel", 760 | "email": "roman@code-factory.org" 761 | }, 762 | { 763 | "name": "Benjamin Eberlei", 764 | "email": "kontakt@beberlei.de" 765 | }, 766 | { 767 | "name": "Guilherme Blanco", 768 | "email": "guilhermeblanco@gmail.com" 769 | }, 770 | { 771 | "name": "Jonathan Wage", 772 | "email": "jonwage@gmail.com" 773 | } 774 | ], 775 | "description": "Object-Relational-Mapper for PHP", 776 | "homepage": "http://www.doctrine-project.org", 777 | "keywords": [ 778 | "database", 779 | "orm" 780 | ], 781 | "time": "2015-08-31 12:59:39" 782 | }, 783 | { 784 | "name": "rhumsaa/uuid", 785 | "version": "2.8.2", 786 | "source": { 787 | "type": "git", 788 | "url": "https://github.com/ramsey/rhumsaa-uuid.git", 789 | "reference": "9c1e2d34bdefd42608c612e08d6e1da1e13a3530" 790 | }, 791 | "dist": { 792 | "type": "zip", 793 | "url": "https://api.github.com/repos/ramsey/rhumsaa-uuid/zipball/9c1e2d34bdefd42608c612e08d6e1da1e13a3530", 794 | "reference": "9c1e2d34bdefd42608c612e08d6e1da1e13a3530", 795 | "shasum": "" 796 | }, 797 | "require": { 798 | "php": ">=5.3.3" 799 | }, 800 | "replace": { 801 | "rhumsaa/uuid": "self.version" 802 | }, 803 | "require-dev": { 804 | "doctrine/dbal": ">=2.3", 805 | "jakub-onderka/php-parallel-lint": "^0.9.0", 806 | "moontoast/math": "~1.1", 807 | "phpunit/phpunit": "~4.1", 808 | "satooshi/php-coveralls": "~0.6", 809 | "squizlabs/php_codesniffer": "^2.3", 810 | "symfony/console": "~2.3" 811 | }, 812 | "suggest": { 813 | "doctrine/dbal": "Allow the use of a UUID as doctrine field type.", 814 | "moontoast/math": "Support for converting UUID to 128-bit integer (in string form).", 815 | "symfony/console": "Support for use of the bin/uuid command line tool." 816 | }, 817 | "bin": [ 818 | "bin/uuid" 819 | ], 820 | "type": "library", 821 | "autoload": { 822 | "psr-4": { 823 | "Rhumsaa\\Uuid\\": "src/" 824 | } 825 | }, 826 | "notification-url": "https://packagist.org/downloads/", 827 | "license": [ 828 | "MIT" 829 | ], 830 | "authors": [ 831 | { 832 | "name": "Marijn Huizendveld", 833 | "email": "marijn.huizendveld@gmail.com" 834 | }, 835 | { 836 | "name": "Ben Ramsey", 837 | "homepage": "http://benramsey.com" 838 | } 839 | ], 840 | "description": "NO LONGER MAINTAINED. Use ramsey/uuid instead. A PHP 5.3+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", 841 | "homepage": "https://github.com/ramsey/rhumsaa-uuid", 842 | "keywords": [ 843 | "guid", 844 | "identifier", 845 | "uuid" 846 | ], 847 | "abandoned": "ramsey/uuid", 848 | "time": "2015-07-23 19:00:41" 849 | }, 850 | { 851 | "name": "symfony/console", 852 | "version": "v2.7.6", 853 | "source": { 854 | "type": "git", 855 | "url": "https://github.com/symfony/console.git", 856 | "reference": "5efd632294c8320ea52492db22292ff853a43766" 857 | }, 858 | "dist": { 859 | "type": "zip", 860 | "url": "https://api.github.com/repos/symfony/console/zipball/5efd632294c8320ea52492db22292ff853a43766", 861 | "reference": "5efd632294c8320ea52492db22292ff853a43766", 862 | "shasum": "" 863 | }, 864 | "require": { 865 | "php": ">=5.3.9" 866 | }, 867 | "require-dev": { 868 | "psr/log": "~1.0", 869 | "symfony/event-dispatcher": "~2.1", 870 | "symfony/process": "~2.1" 871 | }, 872 | "suggest": { 873 | "psr/log": "For using the console logger", 874 | "symfony/event-dispatcher": "", 875 | "symfony/process": "" 876 | }, 877 | "type": "library", 878 | "extra": { 879 | "branch-alias": { 880 | "dev-master": "2.7-dev" 881 | } 882 | }, 883 | "autoload": { 884 | "psr-4": { 885 | "Symfony\\Component\\Console\\": "" 886 | } 887 | }, 888 | "notification-url": "https://packagist.org/downloads/", 889 | "license": [ 890 | "MIT" 891 | ], 892 | "authors": [ 893 | { 894 | "name": "Fabien Potencier", 895 | "email": "fabien@symfony.com" 896 | }, 897 | { 898 | "name": "Symfony Community", 899 | "homepage": "https://symfony.com/contributors" 900 | } 901 | ], 902 | "description": "Symfony Console Component", 903 | "homepage": "https://symfony.com", 904 | "time": "2015-10-20 14:38:46" 905 | } 906 | ], 907 | "packages-dev": [ 908 | { 909 | "name": "hamcrest/hamcrest-php", 910 | "version": "v1.2.2", 911 | "source": { 912 | "type": "git", 913 | "url": "https://github.com/hamcrest/hamcrest-php.git", 914 | "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c" 915 | }, 916 | "dist": { 917 | "type": "zip", 918 | "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b37020aa976fa52d3de9aa904aa2522dc518f79c", 919 | "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c", 920 | "shasum": "" 921 | }, 922 | "require": { 923 | "php": ">=5.3.2" 924 | }, 925 | "replace": { 926 | "cordoval/hamcrest-php": "*", 927 | "davedevelopment/hamcrest-php": "*", 928 | "kodova/hamcrest-php": "*" 929 | }, 930 | "require-dev": { 931 | "phpunit/php-file-iterator": "1.3.3", 932 | "satooshi/php-coveralls": "dev-master" 933 | }, 934 | "type": "library", 935 | "autoload": { 936 | "classmap": [ 937 | "hamcrest" 938 | ], 939 | "files": [ 940 | "hamcrest/Hamcrest.php" 941 | ] 942 | }, 943 | "notification-url": "https://packagist.org/downloads/", 944 | "license": [ 945 | "BSD" 946 | ], 947 | "description": "This is the PHP port of Hamcrest Matchers", 948 | "keywords": [ 949 | "test" 950 | ], 951 | "time": "2015-05-11 14:41:42" 952 | }, 953 | { 954 | "name": "mockery/mockery", 955 | "version": "0.9.4", 956 | "source": { 957 | "type": "git", 958 | "url": "https://github.com/padraic/mockery.git", 959 | "reference": "70bba85e4aabc9449626651f48b9018ede04f86b" 960 | }, 961 | "dist": { 962 | "type": "zip", 963 | "url": "https://api.github.com/repos/padraic/mockery/zipball/70bba85e4aabc9449626651f48b9018ede04f86b", 964 | "reference": "70bba85e4aabc9449626651f48b9018ede04f86b", 965 | "shasum": "" 966 | }, 967 | "require": { 968 | "hamcrest/hamcrest-php": "~1.1", 969 | "lib-pcre": ">=7.0", 970 | "php": ">=5.3.2" 971 | }, 972 | "require-dev": { 973 | "phpunit/phpunit": "~4.0" 974 | }, 975 | "type": "library", 976 | "extra": { 977 | "branch-alias": { 978 | "dev-master": "0.9.x-dev" 979 | } 980 | }, 981 | "autoload": { 982 | "psr-0": { 983 | "Mockery": "library/" 984 | } 985 | }, 986 | "notification-url": "https://packagist.org/downloads/", 987 | "license": [ 988 | "BSD-3-Clause" 989 | ], 990 | "authors": [ 991 | { 992 | "name": "Pádraic Brady", 993 | "email": "padraic.brady@gmail.com", 994 | "homepage": "http://blog.astrumfutura.com" 995 | }, 996 | { 997 | "name": "Dave Marshall", 998 | "email": "dave.marshall@atstsolutions.co.uk", 999 | "homepage": "http://davedevelopment.co.uk" 1000 | } 1001 | ], 1002 | "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", 1003 | "homepage": "http://github.com/padraic/mockery", 1004 | "keywords": [ 1005 | "BDD", 1006 | "TDD", 1007 | "library", 1008 | "mock", 1009 | "mock objects", 1010 | "mockery", 1011 | "stub", 1012 | "test", 1013 | "test double", 1014 | "testing" 1015 | ], 1016 | "time": "2015-04-02 19:54:00" 1017 | }, 1018 | { 1019 | "name": "phpdocumentor/reflection-docblock", 1020 | "version": "2.0.4", 1021 | "source": { 1022 | "type": "git", 1023 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 1024 | "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" 1025 | }, 1026 | "dist": { 1027 | "type": "zip", 1028 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", 1029 | "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", 1030 | "shasum": "" 1031 | }, 1032 | "require": { 1033 | "php": ">=5.3.3" 1034 | }, 1035 | "require-dev": { 1036 | "phpunit/phpunit": "~4.0" 1037 | }, 1038 | "suggest": { 1039 | "dflydev/markdown": "~1.0", 1040 | "erusev/parsedown": "~1.0" 1041 | }, 1042 | "type": "library", 1043 | "extra": { 1044 | "branch-alias": { 1045 | "dev-master": "2.0.x-dev" 1046 | } 1047 | }, 1048 | "autoload": { 1049 | "psr-0": { 1050 | "phpDocumentor": [ 1051 | "src/" 1052 | ] 1053 | } 1054 | }, 1055 | "notification-url": "https://packagist.org/downloads/", 1056 | "license": [ 1057 | "MIT" 1058 | ], 1059 | "authors": [ 1060 | { 1061 | "name": "Mike van Riel", 1062 | "email": "mike.vanriel@naenius.com" 1063 | } 1064 | ], 1065 | "time": "2015-02-03 12:10:50" 1066 | }, 1067 | { 1068 | "name": "phpspec/prophecy", 1069 | "version": "v1.5.0", 1070 | "source": { 1071 | "type": "git", 1072 | "url": "https://github.com/phpspec/prophecy.git", 1073 | "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7" 1074 | }, 1075 | "dist": { 1076 | "type": "zip", 1077 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7", 1078 | "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7", 1079 | "shasum": "" 1080 | }, 1081 | "require": { 1082 | "doctrine/instantiator": "^1.0.2", 1083 | "phpdocumentor/reflection-docblock": "~2.0", 1084 | "sebastian/comparator": "~1.1" 1085 | }, 1086 | "require-dev": { 1087 | "phpspec/phpspec": "~2.0" 1088 | }, 1089 | "type": "library", 1090 | "extra": { 1091 | "branch-alias": { 1092 | "dev-master": "1.4.x-dev" 1093 | } 1094 | }, 1095 | "autoload": { 1096 | "psr-0": { 1097 | "Prophecy\\": "src/" 1098 | } 1099 | }, 1100 | "notification-url": "https://packagist.org/downloads/", 1101 | "license": [ 1102 | "MIT" 1103 | ], 1104 | "authors": [ 1105 | { 1106 | "name": "Konstantin Kudryashov", 1107 | "email": "ever.zet@gmail.com", 1108 | "homepage": "http://everzet.com" 1109 | }, 1110 | { 1111 | "name": "Marcello Duarte", 1112 | "email": "marcello.duarte@gmail.com" 1113 | } 1114 | ], 1115 | "description": "Highly opinionated mocking framework for PHP 5.3+", 1116 | "homepage": "https://github.com/phpspec/prophecy", 1117 | "keywords": [ 1118 | "Double", 1119 | "Dummy", 1120 | "fake", 1121 | "mock", 1122 | "spy", 1123 | "stub" 1124 | ], 1125 | "time": "2015-08-13 10:07:40" 1126 | }, 1127 | { 1128 | "name": "phpunit/php-code-coverage", 1129 | "version": "2.2.4", 1130 | "source": { 1131 | "type": "git", 1132 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 1133 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" 1134 | }, 1135 | "dist": { 1136 | "type": "zip", 1137 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", 1138 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", 1139 | "shasum": "" 1140 | }, 1141 | "require": { 1142 | "php": ">=5.3.3", 1143 | "phpunit/php-file-iterator": "~1.3", 1144 | "phpunit/php-text-template": "~1.2", 1145 | "phpunit/php-token-stream": "~1.3", 1146 | "sebastian/environment": "^1.3.2", 1147 | "sebastian/version": "~1.0" 1148 | }, 1149 | "require-dev": { 1150 | "ext-xdebug": ">=2.1.4", 1151 | "phpunit/phpunit": "~4" 1152 | }, 1153 | "suggest": { 1154 | "ext-dom": "*", 1155 | "ext-xdebug": ">=2.2.1", 1156 | "ext-xmlwriter": "*" 1157 | }, 1158 | "type": "library", 1159 | "extra": { 1160 | "branch-alias": { 1161 | "dev-master": "2.2.x-dev" 1162 | } 1163 | }, 1164 | "autoload": { 1165 | "classmap": [ 1166 | "src/" 1167 | ] 1168 | }, 1169 | "notification-url": "https://packagist.org/downloads/", 1170 | "license": [ 1171 | "BSD-3-Clause" 1172 | ], 1173 | "authors": [ 1174 | { 1175 | "name": "Sebastian Bergmann", 1176 | "email": "sb@sebastian-bergmann.de", 1177 | "role": "lead" 1178 | } 1179 | ], 1180 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 1181 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 1182 | "keywords": [ 1183 | "coverage", 1184 | "testing", 1185 | "xunit" 1186 | ], 1187 | "time": "2015-10-06 15:47:00" 1188 | }, 1189 | { 1190 | "name": "phpunit/php-file-iterator", 1191 | "version": "1.4.1", 1192 | "source": { 1193 | "type": "git", 1194 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 1195 | "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" 1196 | }, 1197 | "dist": { 1198 | "type": "zip", 1199 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", 1200 | "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", 1201 | "shasum": "" 1202 | }, 1203 | "require": { 1204 | "php": ">=5.3.3" 1205 | }, 1206 | "type": "library", 1207 | "extra": { 1208 | "branch-alias": { 1209 | "dev-master": "1.4.x-dev" 1210 | } 1211 | }, 1212 | "autoload": { 1213 | "classmap": [ 1214 | "src/" 1215 | ] 1216 | }, 1217 | "notification-url": "https://packagist.org/downloads/", 1218 | "license": [ 1219 | "BSD-3-Clause" 1220 | ], 1221 | "authors": [ 1222 | { 1223 | "name": "Sebastian Bergmann", 1224 | "email": "sb@sebastian-bergmann.de", 1225 | "role": "lead" 1226 | } 1227 | ], 1228 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 1229 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 1230 | "keywords": [ 1231 | "filesystem", 1232 | "iterator" 1233 | ], 1234 | "time": "2015-06-21 13:08:43" 1235 | }, 1236 | { 1237 | "name": "phpunit/php-text-template", 1238 | "version": "1.2.1", 1239 | "source": { 1240 | "type": "git", 1241 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 1242 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 1243 | }, 1244 | "dist": { 1245 | "type": "zip", 1246 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 1247 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 1248 | "shasum": "" 1249 | }, 1250 | "require": { 1251 | "php": ">=5.3.3" 1252 | }, 1253 | "type": "library", 1254 | "autoload": { 1255 | "classmap": [ 1256 | "src/" 1257 | ] 1258 | }, 1259 | "notification-url": "https://packagist.org/downloads/", 1260 | "license": [ 1261 | "BSD-3-Clause" 1262 | ], 1263 | "authors": [ 1264 | { 1265 | "name": "Sebastian Bergmann", 1266 | "email": "sebastian@phpunit.de", 1267 | "role": "lead" 1268 | } 1269 | ], 1270 | "description": "Simple template engine.", 1271 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 1272 | "keywords": [ 1273 | "template" 1274 | ], 1275 | "time": "2015-06-21 13:50:34" 1276 | }, 1277 | { 1278 | "name": "phpunit/php-timer", 1279 | "version": "1.0.7", 1280 | "source": { 1281 | "type": "git", 1282 | "url": "https://github.com/sebastianbergmann/php-timer.git", 1283 | "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" 1284 | }, 1285 | "dist": { 1286 | "type": "zip", 1287 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", 1288 | "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", 1289 | "shasum": "" 1290 | }, 1291 | "require": { 1292 | "php": ">=5.3.3" 1293 | }, 1294 | "type": "library", 1295 | "autoload": { 1296 | "classmap": [ 1297 | "src/" 1298 | ] 1299 | }, 1300 | "notification-url": "https://packagist.org/downloads/", 1301 | "license": [ 1302 | "BSD-3-Clause" 1303 | ], 1304 | "authors": [ 1305 | { 1306 | "name": "Sebastian Bergmann", 1307 | "email": "sb@sebastian-bergmann.de", 1308 | "role": "lead" 1309 | } 1310 | ], 1311 | "description": "Utility class for timing", 1312 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 1313 | "keywords": [ 1314 | "timer" 1315 | ], 1316 | "time": "2015-06-21 08:01:12" 1317 | }, 1318 | { 1319 | "name": "phpunit/php-token-stream", 1320 | "version": "1.4.8", 1321 | "source": { 1322 | "type": "git", 1323 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 1324 | "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" 1325 | }, 1326 | "dist": { 1327 | "type": "zip", 1328 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", 1329 | "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", 1330 | "shasum": "" 1331 | }, 1332 | "require": { 1333 | "ext-tokenizer": "*", 1334 | "php": ">=5.3.3" 1335 | }, 1336 | "require-dev": { 1337 | "phpunit/phpunit": "~4.2" 1338 | }, 1339 | "type": "library", 1340 | "extra": { 1341 | "branch-alias": { 1342 | "dev-master": "1.4-dev" 1343 | } 1344 | }, 1345 | "autoload": { 1346 | "classmap": [ 1347 | "src/" 1348 | ] 1349 | }, 1350 | "notification-url": "https://packagist.org/downloads/", 1351 | "license": [ 1352 | "BSD-3-Clause" 1353 | ], 1354 | "authors": [ 1355 | { 1356 | "name": "Sebastian Bergmann", 1357 | "email": "sebastian@phpunit.de" 1358 | } 1359 | ], 1360 | "description": "Wrapper around PHP's tokenizer extension.", 1361 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 1362 | "keywords": [ 1363 | "tokenizer" 1364 | ], 1365 | "time": "2015-09-15 10:49:45" 1366 | }, 1367 | { 1368 | "name": "phpunit/phpunit", 1369 | "version": "4.8.18", 1370 | "source": { 1371 | "type": "git", 1372 | "url": "https://github.com/sebastianbergmann/phpunit.git", 1373 | "reference": "fa33d4ad96481b91df343d83e8c8aabed6b1dfd3" 1374 | }, 1375 | "dist": { 1376 | "type": "zip", 1377 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fa33d4ad96481b91df343d83e8c8aabed6b1dfd3", 1378 | "reference": "fa33d4ad96481b91df343d83e8c8aabed6b1dfd3", 1379 | "shasum": "" 1380 | }, 1381 | "require": { 1382 | "ext-dom": "*", 1383 | "ext-json": "*", 1384 | "ext-pcre": "*", 1385 | "ext-reflection": "*", 1386 | "ext-spl": "*", 1387 | "php": ">=5.3.3", 1388 | "phpspec/prophecy": "^1.3.1", 1389 | "phpunit/php-code-coverage": "~2.1", 1390 | "phpunit/php-file-iterator": "~1.4", 1391 | "phpunit/php-text-template": "~1.2", 1392 | "phpunit/php-timer": ">=1.0.6", 1393 | "phpunit/phpunit-mock-objects": "~2.3", 1394 | "sebastian/comparator": "~1.1", 1395 | "sebastian/diff": "~1.2", 1396 | "sebastian/environment": "~1.3", 1397 | "sebastian/exporter": "~1.2", 1398 | "sebastian/global-state": "~1.0", 1399 | "sebastian/version": "~1.0", 1400 | "symfony/yaml": "~2.1|~3.0" 1401 | }, 1402 | "suggest": { 1403 | "phpunit/php-invoker": "~1.1" 1404 | }, 1405 | "bin": [ 1406 | "phpunit" 1407 | ], 1408 | "type": "library", 1409 | "extra": { 1410 | "branch-alias": { 1411 | "dev-master": "4.8.x-dev" 1412 | } 1413 | }, 1414 | "autoload": { 1415 | "classmap": [ 1416 | "src/" 1417 | ] 1418 | }, 1419 | "notification-url": "https://packagist.org/downloads/", 1420 | "license": [ 1421 | "BSD-3-Clause" 1422 | ], 1423 | "authors": [ 1424 | { 1425 | "name": "Sebastian Bergmann", 1426 | "email": "sebastian@phpunit.de", 1427 | "role": "lead" 1428 | } 1429 | ], 1430 | "description": "The PHP Unit Testing framework.", 1431 | "homepage": "https://phpunit.de/", 1432 | "keywords": [ 1433 | "phpunit", 1434 | "testing", 1435 | "xunit" 1436 | ], 1437 | "time": "2015-11-11 11:32:49" 1438 | }, 1439 | { 1440 | "name": "phpunit/phpunit-mock-objects", 1441 | "version": "2.3.8", 1442 | "source": { 1443 | "type": "git", 1444 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 1445 | "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" 1446 | }, 1447 | "dist": { 1448 | "type": "zip", 1449 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", 1450 | "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", 1451 | "shasum": "" 1452 | }, 1453 | "require": { 1454 | "doctrine/instantiator": "^1.0.2", 1455 | "php": ">=5.3.3", 1456 | "phpunit/php-text-template": "~1.2", 1457 | "sebastian/exporter": "~1.2" 1458 | }, 1459 | "require-dev": { 1460 | "phpunit/phpunit": "~4.4" 1461 | }, 1462 | "suggest": { 1463 | "ext-soap": "*" 1464 | }, 1465 | "type": "library", 1466 | "extra": { 1467 | "branch-alias": { 1468 | "dev-master": "2.3.x-dev" 1469 | } 1470 | }, 1471 | "autoload": { 1472 | "classmap": [ 1473 | "src/" 1474 | ] 1475 | }, 1476 | "notification-url": "https://packagist.org/downloads/", 1477 | "license": [ 1478 | "BSD-3-Clause" 1479 | ], 1480 | "authors": [ 1481 | { 1482 | "name": "Sebastian Bergmann", 1483 | "email": "sb@sebastian-bergmann.de", 1484 | "role": "lead" 1485 | } 1486 | ], 1487 | "description": "Mock Object library for PHPUnit", 1488 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 1489 | "keywords": [ 1490 | "mock", 1491 | "xunit" 1492 | ], 1493 | "time": "2015-10-02 06:51:40" 1494 | }, 1495 | { 1496 | "name": "sebastian/comparator", 1497 | "version": "1.2.0", 1498 | "source": { 1499 | "type": "git", 1500 | "url": "https://github.com/sebastianbergmann/comparator.git", 1501 | "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" 1502 | }, 1503 | "dist": { 1504 | "type": "zip", 1505 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", 1506 | "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", 1507 | "shasum": "" 1508 | }, 1509 | "require": { 1510 | "php": ">=5.3.3", 1511 | "sebastian/diff": "~1.2", 1512 | "sebastian/exporter": "~1.2" 1513 | }, 1514 | "require-dev": { 1515 | "phpunit/phpunit": "~4.4" 1516 | }, 1517 | "type": "library", 1518 | "extra": { 1519 | "branch-alias": { 1520 | "dev-master": "1.2.x-dev" 1521 | } 1522 | }, 1523 | "autoload": { 1524 | "classmap": [ 1525 | "src/" 1526 | ] 1527 | }, 1528 | "notification-url": "https://packagist.org/downloads/", 1529 | "license": [ 1530 | "BSD-3-Clause" 1531 | ], 1532 | "authors": [ 1533 | { 1534 | "name": "Jeff Welch", 1535 | "email": "whatthejeff@gmail.com" 1536 | }, 1537 | { 1538 | "name": "Volker Dusch", 1539 | "email": "github@wallbash.com" 1540 | }, 1541 | { 1542 | "name": "Bernhard Schussek", 1543 | "email": "bschussek@2bepublished.at" 1544 | }, 1545 | { 1546 | "name": "Sebastian Bergmann", 1547 | "email": "sebastian@phpunit.de" 1548 | } 1549 | ], 1550 | "description": "Provides the functionality to compare PHP values for equality", 1551 | "homepage": "http://www.github.com/sebastianbergmann/comparator", 1552 | "keywords": [ 1553 | "comparator", 1554 | "compare", 1555 | "equality" 1556 | ], 1557 | "time": "2015-07-26 15:48:44" 1558 | }, 1559 | { 1560 | "name": "sebastian/diff", 1561 | "version": "1.3.0", 1562 | "source": { 1563 | "type": "git", 1564 | "url": "https://github.com/sebastianbergmann/diff.git", 1565 | "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3" 1566 | }, 1567 | "dist": { 1568 | "type": "zip", 1569 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/863df9687835c62aa423a22412d26fa2ebde3fd3", 1570 | "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3", 1571 | "shasum": "" 1572 | }, 1573 | "require": { 1574 | "php": ">=5.3.3" 1575 | }, 1576 | "require-dev": { 1577 | "phpunit/phpunit": "~4.2" 1578 | }, 1579 | "type": "library", 1580 | "extra": { 1581 | "branch-alias": { 1582 | "dev-master": "1.3-dev" 1583 | } 1584 | }, 1585 | "autoload": { 1586 | "classmap": [ 1587 | "src/" 1588 | ] 1589 | }, 1590 | "notification-url": "https://packagist.org/downloads/", 1591 | "license": [ 1592 | "BSD-3-Clause" 1593 | ], 1594 | "authors": [ 1595 | { 1596 | "name": "Kore Nordmann", 1597 | "email": "mail@kore-nordmann.de" 1598 | }, 1599 | { 1600 | "name": "Sebastian Bergmann", 1601 | "email": "sebastian@phpunit.de" 1602 | } 1603 | ], 1604 | "description": "Diff implementation", 1605 | "homepage": "http://www.github.com/sebastianbergmann/diff", 1606 | "keywords": [ 1607 | "diff" 1608 | ], 1609 | "time": "2015-02-22 15:13:53" 1610 | }, 1611 | { 1612 | "name": "sebastian/environment", 1613 | "version": "1.3.2", 1614 | "source": { 1615 | "type": "git", 1616 | "url": "https://github.com/sebastianbergmann/environment.git", 1617 | "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44" 1618 | }, 1619 | "dist": { 1620 | "type": "zip", 1621 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6324c907ce7a52478eeeaede764f48733ef5ae44", 1622 | "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44", 1623 | "shasum": "" 1624 | }, 1625 | "require": { 1626 | "php": ">=5.3.3" 1627 | }, 1628 | "require-dev": { 1629 | "phpunit/phpunit": "~4.4" 1630 | }, 1631 | "type": "library", 1632 | "extra": { 1633 | "branch-alias": { 1634 | "dev-master": "1.3.x-dev" 1635 | } 1636 | }, 1637 | "autoload": { 1638 | "classmap": [ 1639 | "src/" 1640 | ] 1641 | }, 1642 | "notification-url": "https://packagist.org/downloads/", 1643 | "license": [ 1644 | "BSD-3-Clause" 1645 | ], 1646 | "authors": [ 1647 | { 1648 | "name": "Sebastian Bergmann", 1649 | "email": "sebastian@phpunit.de" 1650 | } 1651 | ], 1652 | "description": "Provides functionality to handle HHVM/PHP environments", 1653 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1654 | "keywords": [ 1655 | "Xdebug", 1656 | "environment", 1657 | "hhvm" 1658 | ], 1659 | "time": "2015-08-03 06:14:51" 1660 | }, 1661 | { 1662 | "name": "sebastian/exporter", 1663 | "version": "1.2.1", 1664 | "source": { 1665 | "type": "git", 1666 | "url": "https://github.com/sebastianbergmann/exporter.git", 1667 | "reference": "7ae5513327cb536431847bcc0c10edba2701064e" 1668 | }, 1669 | "dist": { 1670 | "type": "zip", 1671 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", 1672 | "reference": "7ae5513327cb536431847bcc0c10edba2701064e", 1673 | "shasum": "" 1674 | }, 1675 | "require": { 1676 | "php": ">=5.3.3", 1677 | "sebastian/recursion-context": "~1.0" 1678 | }, 1679 | "require-dev": { 1680 | "phpunit/phpunit": "~4.4" 1681 | }, 1682 | "type": "library", 1683 | "extra": { 1684 | "branch-alias": { 1685 | "dev-master": "1.2.x-dev" 1686 | } 1687 | }, 1688 | "autoload": { 1689 | "classmap": [ 1690 | "src/" 1691 | ] 1692 | }, 1693 | "notification-url": "https://packagist.org/downloads/", 1694 | "license": [ 1695 | "BSD-3-Clause" 1696 | ], 1697 | "authors": [ 1698 | { 1699 | "name": "Jeff Welch", 1700 | "email": "whatthejeff@gmail.com" 1701 | }, 1702 | { 1703 | "name": "Volker Dusch", 1704 | "email": "github@wallbash.com" 1705 | }, 1706 | { 1707 | "name": "Bernhard Schussek", 1708 | "email": "bschussek@2bepublished.at" 1709 | }, 1710 | { 1711 | "name": "Sebastian Bergmann", 1712 | "email": "sebastian@phpunit.de" 1713 | }, 1714 | { 1715 | "name": "Adam Harvey", 1716 | "email": "aharvey@php.net" 1717 | } 1718 | ], 1719 | "description": "Provides the functionality to export PHP variables for visualization", 1720 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1721 | "keywords": [ 1722 | "export", 1723 | "exporter" 1724 | ], 1725 | "time": "2015-06-21 07:55:53" 1726 | }, 1727 | { 1728 | "name": "sebastian/global-state", 1729 | "version": "1.1.1", 1730 | "source": { 1731 | "type": "git", 1732 | "url": "https://github.com/sebastianbergmann/global-state.git", 1733 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" 1734 | }, 1735 | "dist": { 1736 | "type": "zip", 1737 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", 1738 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", 1739 | "shasum": "" 1740 | }, 1741 | "require": { 1742 | "php": ">=5.3.3" 1743 | }, 1744 | "require-dev": { 1745 | "phpunit/phpunit": "~4.2" 1746 | }, 1747 | "suggest": { 1748 | "ext-uopz": "*" 1749 | }, 1750 | "type": "library", 1751 | "extra": { 1752 | "branch-alias": { 1753 | "dev-master": "1.0-dev" 1754 | } 1755 | }, 1756 | "autoload": { 1757 | "classmap": [ 1758 | "src/" 1759 | ] 1760 | }, 1761 | "notification-url": "https://packagist.org/downloads/", 1762 | "license": [ 1763 | "BSD-3-Clause" 1764 | ], 1765 | "authors": [ 1766 | { 1767 | "name": "Sebastian Bergmann", 1768 | "email": "sebastian@phpunit.de" 1769 | } 1770 | ], 1771 | "description": "Snapshotting of global state", 1772 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1773 | "keywords": [ 1774 | "global state" 1775 | ], 1776 | "time": "2015-10-12 03:26:01" 1777 | }, 1778 | { 1779 | "name": "sebastian/recursion-context", 1780 | "version": "1.0.1", 1781 | "source": { 1782 | "type": "git", 1783 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1784 | "reference": "994d4a811bafe801fb06dccbee797863ba2792ba" 1785 | }, 1786 | "dist": { 1787 | "type": "zip", 1788 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/994d4a811bafe801fb06dccbee797863ba2792ba", 1789 | "reference": "994d4a811bafe801fb06dccbee797863ba2792ba", 1790 | "shasum": "" 1791 | }, 1792 | "require": { 1793 | "php": ">=5.3.3" 1794 | }, 1795 | "require-dev": { 1796 | "phpunit/phpunit": "~4.4" 1797 | }, 1798 | "type": "library", 1799 | "extra": { 1800 | "branch-alias": { 1801 | "dev-master": "1.0.x-dev" 1802 | } 1803 | }, 1804 | "autoload": { 1805 | "classmap": [ 1806 | "src/" 1807 | ] 1808 | }, 1809 | "notification-url": "https://packagist.org/downloads/", 1810 | "license": [ 1811 | "BSD-3-Clause" 1812 | ], 1813 | "authors": [ 1814 | { 1815 | "name": "Jeff Welch", 1816 | "email": "whatthejeff@gmail.com" 1817 | }, 1818 | { 1819 | "name": "Sebastian Bergmann", 1820 | "email": "sebastian@phpunit.de" 1821 | }, 1822 | { 1823 | "name": "Adam Harvey", 1824 | "email": "aharvey@php.net" 1825 | } 1826 | ], 1827 | "description": "Provides functionality to recursively process PHP variables", 1828 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1829 | "time": "2015-06-21 08:04:50" 1830 | }, 1831 | { 1832 | "name": "sebastian/version", 1833 | "version": "1.0.6", 1834 | "source": { 1835 | "type": "git", 1836 | "url": "https://github.com/sebastianbergmann/version.git", 1837 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" 1838 | }, 1839 | "dist": { 1840 | "type": "zip", 1841 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 1842 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 1843 | "shasum": "" 1844 | }, 1845 | "type": "library", 1846 | "autoload": { 1847 | "classmap": [ 1848 | "src/" 1849 | ] 1850 | }, 1851 | "notification-url": "https://packagist.org/downloads/", 1852 | "license": [ 1853 | "BSD-3-Clause" 1854 | ], 1855 | "authors": [ 1856 | { 1857 | "name": "Sebastian Bergmann", 1858 | "email": "sebastian@phpunit.de", 1859 | "role": "lead" 1860 | } 1861 | ], 1862 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1863 | "homepage": "https://github.com/sebastianbergmann/version", 1864 | "time": "2015-06-21 13:59:46" 1865 | }, 1866 | { 1867 | "name": "symfony/yaml", 1868 | "version": "v2.7.6", 1869 | "source": { 1870 | "type": "git", 1871 | "url": "https://github.com/symfony/yaml.git", 1872 | "reference": "eca9019c88fbe250164affd107bc8057771f3f4d" 1873 | }, 1874 | "dist": { 1875 | "type": "zip", 1876 | "url": "https://api.github.com/repos/symfony/yaml/zipball/eca9019c88fbe250164affd107bc8057771f3f4d", 1877 | "reference": "eca9019c88fbe250164affd107bc8057771f3f4d", 1878 | "shasum": "" 1879 | }, 1880 | "require": { 1881 | "php": ">=5.3.9" 1882 | }, 1883 | "type": "library", 1884 | "extra": { 1885 | "branch-alias": { 1886 | "dev-master": "2.7-dev" 1887 | } 1888 | }, 1889 | "autoload": { 1890 | "psr-4": { 1891 | "Symfony\\Component\\Yaml\\": "" 1892 | } 1893 | }, 1894 | "notification-url": "https://packagist.org/downloads/", 1895 | "license": [ 1896 | "MIT" 1897 | ], 1898 | "authors": [ 1899 | { 1900 | "name": "Fabien Potencier", 1901 | "email": "fabien@symfony.com" 1902 | }, 1903 | { 1904 | "name": "Symfony Community", 1905 | "homepage": "https://symfony.com/contributors" 1906 | } 1907 | ], 1908 | "description": "Symfony Yaml Component", 1909 | "homepage": "https://symfony.com", 1910 | "time": "2015-10-11 09:39:48" 1911 | } 1912 | ], 1913 | "aliases": [], 1914 | "minimum-stability": "stable", 1915 | "stability-flags": [], 1916 | "prefer-stable": false, 1917 | "prefer-lowest": false, 1918 | "platform": [], 1919 | "platform-dev": [] 1920 | } 1921 | -------------------------------------------------------------------------------- /example/example.php: -------------------------------------------------------------------------------- 1 | getConnection(); 16 | 17 | $eventStore = new Broadway\EventStore\DBALEventStore( 18 | $connection, 19 | new \Broadway\Serializer\SimpleInterfaceSerializer(), 20 | new \Broadway\Serializer\SimpleInterfaceSerializer(), 21 | 'Events' 22 | ); 23 | 24 | $eventDispatcher = new BufferedEventDispatcher(new SimpleEventDispatcher()); 25 | $eventUoW = new \Tuck\DoctrineEventStore\UnitOfWork($eventStore, $eventDispatcher); 26 | 27 | $entityManager->getEventManager()->addEventSubscriber( 28 | new Tuck\DoctrineEventStore\EventCollector($eventUoW) 29 | ); 30 | 31 | 32 | /** 33 | * @ORM\Entity() 34 | */ 35 | class Book implements \Broadway\Domain\AggregateRoot 36 | { 37 | use \Tuck\DoctrineEventStore\EntityWithEvents; 38 | 39 | /** 40 | * @ORM\Id() 41 | * @ORM\Column(type="string", length=64) 42 | */ 43 | private $id; 44 | 45 | /** 46 | * @ORM\Column(type="string") 47 | */ 48 | private $title; 49 | 50 | private function __construct() 51 | { 52 | } 53 | 54 | public static function purchase($title) 55 | { 56 | $book = new Book(); 57 | $book->id = (string)Uuid::uuid4(); 58 | $book->title = $title; 59 | 60 | $book->raise(new BookPurchased($book->id, $title)); 61 | 62 | return $book; 63 | } 64 | 65 | public function loan() 66 | { 67 | // do stuff 68 | $this->raise(new BookLoanedOut($this->id, $this->title)); 69 | } 70 | 71 | public function getAggregateRootId() 72 | { 73 | return $this->id; 74 | } 75 | } 76 | 77 | 78 | // Simplified domain part 79 | $commandHandler = function () use ($entityManager) { 80 | $book = Book::purchase('Moby Dick'); 81 | $book->loan(); 82 | $entityManager->persist($book); 83 | }; 84 | 85 | // Hang some listeners in there 86 | $eventDispatcher->addListener( 87 | BookPurchased::class, 88 | function (BookPurchased $event) { 89 | echo "Bought new book: " . $event->getTitle() . "\n"; 90 | } 91 | ); 92 | $eventDispatcher->addListener( 93 | BookLoanedOut::class, 94 | function (BookLoanedOut $event) { 95 | echo "Loaned out book: " . $event->getTitle() . "\n"; 96 | } 97 | ); 98 | 99 | 100 | // This part would be split into a couple of command bus decorators. 101 | // The important part is that we widen the transaction to include both 102 | // the Doctrine UoW flush and our own UoW flush. 103 | $entityManager->beginTransaction(); 104 | try { 105 | $commandHandler(); 106 | $entityManager->flush(); 107 | 108 | $entityManager->commit(); 109 | } catch (Exception $e) { 110 | echo 'ERROR: ' . $e->getMessage() . '. Rolling back!'; 111 | 112 | $eventUoW->clear(); 113 | $entityManager->rollBack(); 114 | 115 | echo 'Rolled back'; 116 | } 117 | $eventDispatcher->dispatchPendingEvents(); 118 | -------------------------------------------------------------------------------- /example/example.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `Book` ( 2 | `id` varchar(64) COLLATE utf8_unicode_ci NOT NULL, 3 | `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 4 | `playhead` int(11) NOT NULL, 5 | PRIMARY KEY (`id`) 6 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 7 | 8 | CREATE TABLE `Events` ( 9 | `id` int(11) NOT NULL AUTO_INCREMENT, 10 | `uuid` char(36) COLLATE utf8_unicode_ci NOT NULL COMMENT '(DC2Type:guid)', 11 | `playhead` int(10) unsigned NOT NULL, 12 | `payload` longtext COLLATE utf8_unicode_ci NOT NULL, 13 | `metadata` longtext COLLATE utf8_unicode_ci NOT NULL, 14 | `recorded_on` varchar(32) COLLATE utf8_unicode_ci NOT NULL, 15 | `type` longtext COLLATE utf8_unicode_ci NOT NULL, 16 | PRIMARY KEY (`id`), 17 | UNIQUE KEY `UNIQ_542B527CD17F50A634B91FA9` (`uuid`,`playhead`) 18 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 19 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/DoctrineEventStore/EntityWithEvents.php: -------------------------------------------------------------------------------- 1 | playhead++; 33 | 34 | $this->uncommittedEvents[] = DomainMessage::recordNow( 35 | $this->getAggregateRootId(), 36 | $this->playhead, 37 | new Metadata([]), 38 | $event 39 | ); 40 | } 41 | 42 | /** 43 | * {@inheritDoc} 44 | */ 45 | public function getUncommittedEvents() 46 | { 47 | $stream = new DomainEventStream($this->uncommittedEvents); 48 | 49 | $this->uncommittedEvents = []; 50 | 51 | return $stream; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/DoctrineEventStore/EventCollector.php: -------------------------------------------------------------------------------- 1 | eventUnitOfWork = $eventUnitOfWork; 23 | } 24 | 25 | /** 26 | * @param OnFlushEventArgs $event 27 | */ 28 | public function onFlush(OnFlushEventArgs $event) 29 | { 30 | $event->getEntityManager()->beginTransaction(); 31 | 32 | $doctrineUnitOfWork = $event 33 | ->getEntityManager() 34 | ->getUnitOfWork(); 35 | 36 | foreach ($doctrineUnitOfWork->getIdentityMap() as $className => $entities) { 37 | foreach ($entities as $entity) { 38 | $this->collectEventsFromEntity($entity); 39 | } 40 | } 41 | 42 | foreach ($doctrineUnitOfWork->getScheduledEntityDeletions() as $entity) { 43 | $this->collectEventsFromEntity($entity); 44 | } 45 | } 46 | 47 | public function postFlush(PostFlushEventArgs $event) 48 | { 49 | $this->eventUnitOfWork->flush(); 50 | $event->getEntityManager()->commit(); 51 | } 52 | 53 | /** 54 | * @param object $entity 55 | */ 56 | protected function collectEventsFromEntity($entity) 57 | { 58 | if (!$entity instanceof AggregateRoot) { 59 | return; 60 | } 61 | 62 | $this->eventUnitOfWork->persistEventsFromAggregateRoot($entity); 63 | } 64 | 65 | /** 66 | * Returns an array of events this subscriber wants to listen to. 67 | * 68 | * @return array 69 | */ 70 | public function getSubscribedEvents() 71 | { 72 | return [ 73 | Events::onFlush, 74 | Events::postFlush 75 | ]; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/DoctrineEventStore/Transaction.php: -------------------------------------------------------------------------------- 1 | aggregateRootId = $aggregateRootId; 22 | $this->domainEventStream = $domainEventStream; 23 | } 24 | 25 | /** 26 | * @return string 27 | */ 28 | public function getAggregateRootId() 29 | { 30 | return $this->aggregateRootId; 31 | } 32 | 33 | /** 34 | * @return DomainEventStreamInterface 35 | */ 36 | public function getDomainEventStream() 37 | { 38 | return $this->domainEventStream; 39 | } 40 | 41 | /** 42 | * @return object[] 43 | */ 44 | public function getDomainEvents() 45 | { 46 | return array_map( 47 | function (DomainMessage $message) { 48 | return $message->getPayload(); 49 | }, 50 | iterator_to_array($this->domainEventStream) 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/DoctrineEventStore/UnitOfWork.php: -------------------------------------------------------------------------------- 1 | eventStore = $eventStore; 40 | } 41 | 42 | /** 43 | * @param AggregateRoot $aggregateRoot 44 | */ 45 | public function persistEventsFromAggregateRoot(AggregateRoot $aggregateRoot) 46 | { 47 | $this->pendingTransactions[] = new Transaction( 48 | $aggregateRoot->getAggregateRootId(), 49 | $aggregateRoot->getUncommittedEvents() 50 | ); 51 | } 52 | 53 | /** 54 | * @throws \Exception 55 | */ 56 | public function flush() 57 | { 58 | /** @var Transaction $transaction */ 59 | while ($transaction = array_shift($this->pendingTransactions)) { 60 | try { 61 | $this->eventStore->append( 62 | $transaction->getAggregateRootId(), 63 | $transaction->getDomainEventStream() 64 | ); 65 | } catch (\Exception $e) { 66 | $this->failedTransactions[] = $transaction; 67 | throw $e; 68 | } 69 | 70 | $this->committedTransactions[] = $transaction; 71 | } 72 | } 73 | 74 | /** 75 | * @return DomainEventStream 76 | */ 77 | public function releaseCommittedEvents() 78 | { 79 | $committedEvents = $this->peekAtCommittedEvents(); 80 | $this->clear(); 81 | 82 | return $committedEvents; 83 | } 84 | 85 | /** 86 | * @return DomainEventStream 87 | */ 88 | public function peekAtCommittedEvents() 89 | { 90 | // Collect and merge all Domain Events 91 | $messages = []; 92 | foreach ($this->committedTransactions as $transaction) { 93 | $messages = array_merge($messages, iterator_to_array($transaction->getDomainEventStream())); 94 | } 95 | 96 | // Sort DomainMessages based on their timestamps, so they always come 97 | // out in the same order they were generated internally. 98 | usort( 99 | $messages, 100 | function (DomainMessage $message1, DomainMessage $message2) { 101 | return strcmp($message1->getRecordedOn()->toString(), $message2->getRecordedOn()->toString()); 102 | } 103 | ); 104 | 105 | return new DomainEventStream($messages); 106 | } 107 | 108 | /** 109 | * @return Transaction[] 110 | */ 111 | public function getPendingTransactions() 112 | { 113 | return $this->pendingTransactions; 114 | } 115 | 116 | /** 117 | * @return Transaction[] 118 | */ 119 | public function getFailedTransactions() 120 | { 121 | return $this->failedTransactions; 122 | } 123 | 124 | /** 125 | * @return Transaction[] 126 | */ 127 | public function getCommittedTransactions() 128 | { 129 | return $this->committedTransactions; 130 | } 131 | 132 | public function clear() 133 | { 134 | $this->pendingTransactions = []; 135 | $this->committedTransactions = []; 136 | $this->failedTransactions = []; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /tests/Fixtures/MockAggregate.php: -------------------------------------------------------------------------------- 1 | id = (string)$id; 44 | 45 | array_map([$this, 'raise'], $events); 46 | } 47 | 48 | /** 49 | * @return string 50 | */ 51 | public function getAggregateRootId() 52 | { 53 | return $this->id; 54 | } 55 | 56 | public function andId($newId) 57 | { 58 | $this->id = $newId; 59 | return $this; 60 | } 61 | 62 | public function forceEvent($event) 63 | { 64 | $this->raise($event); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/Fixtures/OtherThingHappened.php: -------------------------------------------------------------------------------- 1 | uow = new UnitOfWork( 30 | $this->eventStore = m::mock(EventStoreInterface::class) 31 | ); 32 | 33 | $this->eventStore->shouldReceive('append')->byDefault(); 34 | } 35 | 36 | public function testCanFlushEventsToEventStore() 37 | { 38 | $event1 = new ThingHappened(); 39 | $event2 = new OtherThingHappened(); 40 | 41 | $this->uow->persistEventsFromAggregateRoot(MockAggregate::withEvents($event1)); 42 | $this->uow->persistEventsFromAggregateRoot(MockAggregate::withEvents($event2)); 43 | 44 | $this->eventStore->shouldReceive('append')->with(1, $this->eventStreamMatcher($event1)); 45 | $this->eventStore->shouldReceive('append')->with(2, $this->eventStreamMatcher($event2)); 46 | 47 | $this->uow->flush(); 48 | } 49 | 50 | public function testRethrowsFailingException() 51 | { 52 | $expectedException = new OutOfBoundsException(); 53 | $this->eventStore->shouldReceive('append')->andThrow($expectedException); 54 | 55 | $this->uow->persistEventsFromAggregateRoot(MockAggregate::withEvents(new ThingHappened())); 56 | 57 | $caughtException = null; 58 | try { 59 | $this->uow->flush(); 60 | } catch (\Exception $e) { 61 | $caughtException = $e; 62 | } 63 | 64 | $this->assertSame($expectedException, $caughtException); 65 | } 66 | 67 | public function testManagesTransactionsPerStateInCaseOfFailure() 68 | { 69 | $event1 = new ThingHappened(); 70 | $event2 = new OtherThingHappened(); 71 | $event3 = new YetAnotherThingHappened(); 72 | 73 | $this->uow->persistEventsFromAggregateRoot(MockAggregate::withEvents($event1)->andId(1)); 74 | $this->uow->persistEventsFromAggregateRoot(MockAggregate::withEvents($event2)->andId(2)); 75 | $this->uow->persistEventsFromAggregateRoot(MockAggregate::withEvents($event3)->andId(3)); 76 | 77 | $this->eventStore->shouldReceive('append')->with(1, m::any()); 78 | $this->eventStore->shouldReceive('append')->with(2, m::any())->andThrow(new OutOfBoundsException()); 79 | $this->eventStore->shouldReceive('append')->with(3, m::any())->never(); 80 | 81 | try { 82 | $this->uow->flush(); 83 | } catch (OutOfBoundsException $e) { 84 | } 85 | 86 | $this->assertCount(1, $this->uow->getCommittedTransactions(), 'expected 1 committed transaction'); 87 | $this->assertCount(1, $this->uow->getFailedTransactions(), 'expected 1 failed transaction'); 88 | $this->assertCount(1, $this->uow->getPendingTransactions(), 'expected 1 pending transaction'); 89 | 90 | $this->assertEquals(1, $this->uow->getCommittedTransactions()[0]->getAggregateRootId()); 91 | $this->assertEquals(2, $this->uow->getFailedTransactions()[0]->getAggregateRootId()); 92 | $this->assertEquals(3, $this->uow->getPendingTransactions()[0]->getAggregateRootId()); 93 | } 94 | 95 | public function testCanRetrieveEventsAsSingleArrayEvenFromMultipleTransactions() 96 | { 97 | $event1 = new ThingHappened(); 98 | $event2 = new OtherThingHappened(); 99 | $event3 = new YetAnotherThingHappened(); 100 | 101 | $this->uow->persistEventsFromAggregateRoot(MockAggregate::withEvents($event1, $event2)); 102 | $this->uow->persistEventsFromAggregateRoot(MockAggregate::withEvents($event3)); 103 | 104 | $this->uow->flush(); 105 | 106 | $this->assertSameEvents( 107 | [$event1, $event2, $event3], 108 | $this->uow->peekAtCommittedEvents() 109 | ); 110 | } 111 | 112 | public function testAlwaysReturnsEventsInTimeOrder() 113 | { 114 | $event1 = new ThingHappened(); 115 | $event2 = new OtherThingHappened(); 116 | $event3 = new YetAnotherThingHappened(); 117 | 118 | $aggregate1 = MockAggregate::withEvents($event1); 119 | $aggregate2 = MockAggregate::withEvents($event2); 120 | $aggregate1->forceEvent($event3); 121 | 122 | $this->uow->persistEventsFromAggregateRoot($aggregate2); 123 | $this->uow->persistEventsFromAggregateRoot($aggregate1); 124 | $this->uow->flush(); 125 | 126 | $this->assertSameEvents( 127 | [$event1, $event2, $event3], 128 | $this->uow->peekAtCommittedEvents() 129 | ); 130 | } 131 | 132 | public function testCanReturnNoEventsWithoutError() 133 | { 134 | $this->assertSameEvents( 135 | [], 136 | $this->uow->peekAtCommittedEvents() 137 | ); 138 | } 139 | 140 | public function testCanClearAnyPendingTransactions() 141 | { 142 | $this->uow->persistEventsFromAggregateRoot(MockAggregate::withEvents(new ThingHappened())); 143 | 144 | $this->assertCount(1, $this->uow->getPendingTransactions()); 145 | $this->uow->clear(); 146 | $this->assertCount(0, $this->uow->getPendingTransactions()); 147 | } 148 | 149 | public function testCanClearPreviouslyCommittedTransactions() 150 | { 151 | $this->uow->persistEventsFromAggregateRoot(MockAggregate::withEvents(new ThingHappened())); 152 | $this->uow->flush(); 153 | 154 | $this->assertCount(1, $this->uow->peekAtCommittedEvents()); 155 | $this->uow->clear(); 156 | $this->assertCount(0, $this->uow->peekAtCommittedEvents()); 157 | } 158 | 159 | public function testCanClearFailedTransactions() 160 | { 161 | $this->uow->persistEventsFromAggregateRoot(MockAggregate::withEvents(new ThingHappened())); 162 | 163 | $this->eventStore->shouldReceive('append')->andThrow(new OutOfBoundsException()); 164 | 165 | try { 166 | $this->uow->flush(); 167 | } catch (OutOfBoundsException $e) { 168 | } 169 | 170 | $this->assertCount(1, $this->uow->getFailedTransactions()); 171 | $this->uow->clear(); 172 | $this->assertCount(0, $this->uow->getFailedTransactions()); 173 | } 174 | 175 | public function testPeekingAtCommittedEventsDoesNotClearThem() 176 | { 177 | $this->uow->persistEventsFromAggregateRoot( 178 | MockAggregate::withEvents(new ThingHappened(), new OtherThingHappened()) 179 | ); 180 | $this->uow->flush(); 181 | 182 | $this->uow->peekAtCommittedEvents(); 183 | $this->assertCount(2, $this->uow->peekAtCommittedEvents()); 184 | } 185 | 186 | public function testReleasingCommittedEventsDoesClearThem() 187 | { 188 | $this->uow->persistEventsFromAggregateRoot( 189 | MockAggregate::withEvents(new ThingHappened(), new OtherThingHappened()) 190 | ); 191 | $this->uow->flush(); 192 | 193 | $this->assertCount(2, $this->uow->releaseCommittedEvents()); 194 | $this->assertCount(0, $this->uow->releaseCommittedEvents()); 195 | } 196 | 197 | private function assertSameEvents($expectedEvents, $receivedMessages) 198 | { 199 | $receivedMessages = iterator_to_array($receivedMessages); 200 | $this->assertCount(count($expectedEvents), $receivedMessages); 201 | 202 | foreach ($expectedEvents as $key => $expectedEvent) { 203 | $this->assertSame($expectedEvent, $receivedMessages[$key]->getPayload()); 204 | } 205 | } 206 | 207 | private function eventStreamMatcher($expectedEvents) 208 | { 209 | if (!is_array($expectedEvents)) { 210 | $expectedEvents = [$expectedEvents]; 211 | } 212 | 213 | return m::on( 214 | function (DomainEventStream $stream) use ($expectedEvents) { 215 | $receivedEvents = array_map( 216 | function (DomainMessage $message) { 217 | return $message->getPayload(); 218 | }, 219 | iterator_to_array($stream) 220 | ); 221 | 222 | return $receivedEvents === $expectedEvents; 223 | } 224 | ); 225 | } 226 | 227 | } 228 | --------------------------------------------------------------------------------