├── .github └── workflows │ └── continuous-integration.yml ├── .laminas-ci.json ├── .php-cs-fixer.php ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── docs ├── bookdown.json └── projections.md ├── phpunit.xml.dist └── src ├── AllStreamProjectionRunner.php ├── CategoryStreamProjectionRunner.php └── MessageNameStreamProjectionRunner.php /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | name: "Continuous Integration" 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | tags: 8 | 9 | jobs: 10 | ci: 11 | uses: laminas/workflow-continuous-integration/.github/workflows/continuous-integration.yml@1.x 12 | -------------------------------------------------------------------------------- /.laminas-ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "additional_composer_arguments": [ 3 | "--prefer-stable" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | getFinder()->in(__DIR__); 5 | 6 | $config->setCacheFile(__DIR__ . '/.php_cs.cache'); 7 | 8 | return $config; 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v1.3.0](https://github.com/prooph/standard-projections/tree/v1.3.0) 4 | 5 | [Full Changelog](https://github.com/prooph/standard-projections/compare/v1.2.0...v1.3.0) 6 | 7 | **Implemented enhancements:** 8 | 9 | - Php81 compat [\#17](https://github.com/prooph/standard-projections/pull/17) ([basz](https://github.com/basz)) 10 | 11 | ## [v1.2.0](https://github.com/prooph/standard-projections/tree/v1.2.0) (2022-10-03) 12 | 13 | [Full Changelog](https://github.com/prooph/standard-projections/compare/v1.1.1...v1.2.0) 14 | 15 | **Implemented enhancements:** 16 | 17 | - Laminas continuous integration [\#16](https://github.com/prooph/standard-projections/pull/16) ([basz](https://github.com/basz)) 18 | 19 | **Closed issues:** 20 | 21 | - test won't run Fatal error: Class 'ProophTest\EventStore\Mock\TestDomainEvent' not found [\#12](https://github.com/prooph/standard-projections/issues/12) 22 | 23 | **Merged pull requests:** 24 | 25 | - php8 compatibility [\#15](https://github.com/prooph/standard-projections/pull/15) ([basz](https://github.com/basz)) 26 | - Fixed typo in docs [\#14](https://github.com/prooph/standard-projections/pull/14) ([mikemilano](https://github.com/mikemilano)) 27 | - Update cs headers [\#13](https://github.com/prooph/standard-projections/pull/13) ([basz](https://github.com/basz)) 28 | 29 | ## [v1.1.1](https://github.com/prooph/standard-projections/tree/v1.1.1) (2018-02-03) 30 | 31 | [Full Changelog](https://github.com/prooph/standard-projections/compare/v1.1.0...v1.1.1) 32 | 33 | ## [v1.1.0](https://github.com/prooph/standard-projections/tree/v1.1.0) (2017-12-17) 34 | 35 | [Full Changelog](https://github.com/prooph/standard-projections/compare/v1.0.0...v1.1.0) 36 | 37 | **Implemented enhancements:** 38 | 39 | - test php 7.2 on travis [\#11](https://github.com/prooph/standard-projections/pull/11) ([prolic](https://github.com/prolic)) 40 | 41 | **Merged pull requests:** 42 | 43 | - Restructure docs [\#10](https://github.com/prooph/standard-projections/pull/10) ([codeliner](https://github.com/codeliner)) 44 | 45 | ## [v1.0.0](https://github.com/prooph/standard-projections/tree/v1.0.0) (2017-03-30) 46 | 47 | [Full Changelog](https://github.com/prooph/standard-projections/compare/v1.0.0-beta1...v1.0.0) 48 | 49 | **Implemented enhancements:** 50 | 51 | - phpspec/prophecy\#332 [\#7](https://github.com/prooph/standard-projections/pull/7) ([basz](https://github.com/basz)) 52 | - update standard projections [\#5](https://github.com/prooph/standard-projections/pull/5) ([prolic](https://github.com/prolic)) 53 | - update projection runners [\#3](https://github.com/prooph/standard-projections/pull/3) ([prolic](https://github.com/prolic)) 54 | - add standard stream projections [\#1](https://github.com/prooph/standard-projections/pull/1) ([prolic](https://github.com/prolic)) 55 | 56 | **Closed issues:** 57 | 58 | - Standard ReadModels [\#6](https://github.com/prooph/standard-projections/issues/6) 59 | - Use new event store api for projection creation [\#2](https://github.com/prooph/standard-projections/issues/2) 60 | 61 | **Merged pull requests:** 62 | 63 | - do not require dev versions [\#9](https://github.com/prooph/standard-projections/pull/9) ([basz](https://github.com/basz)) 64 | 65 | ## [v1.0.0-beta1](https://github.com/prooph/standard-projections/tree/v1.0.0-beta1) (2016-12-13) 66 | 67 | [Full Changelog](https://github.com/prooph/standard-projections/compare/989c8fbf4e6f1fb37a01a3989cbc74e1e875317d...v1.0.0-beta1) 68 | 69 | 70 | 71 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2018, prooph software GmbH 2 | Copyright (c) 2016-2018, Sascha-Oliver Prolic 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of the prooph software GmbH nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Standard Projections for Prooph EventStore 2 | 3 | [![Continuous Integration](https://github.com/prooph/standard-projections/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/prooph/standard-projections/actions/workflows/continuous-integration.yml) 4 | [![Coverage Status](https://coveralls.io/repos/prooph/standard-projections/badge.svg?branch=master&service=github)](https://coveralls.io/github/prooph/standard-projections?branch=master) 5 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/prooph/improoph) 6 | 7 | ## Overview 8 | 9 | The standard projections are some kind of event-indexing, so you can retrieve events from 10 | all streams at once (`AllStreamProjectionRunner`), by category (`CategoryStreamProjectionRunner`) 11 | or by message name (`MessageNameStreamProjectionRunner`). See docs for more information. 12 | 13 | ## Requirements 14 | 15 | - PHP >= 7.4 16 | - Prooph EventStore v7 17 | 18 | ## Documentation 19 | 20 | Documentation is [in the doc tree](docs/), and can be compiled using [bookdown](http://bookdown.io). 21 | 22 | ```console 23 | $ php ./vendor/bin/bookdown docs/bookdown.json 24 | $ php -S 0.0.0.0:8080 -t docs/html/ 25 | ``` 26 | 27 | Then browse to [http://localhost:8080/](http://localhost:8080/) 28 | 29 | ## Support 30 | 31 | - Ask questions on Stack Overflow tagged with [#prooph](https://stackoverflow.com/questions/tagged/prooph). 32 | - File issues at [https://github.com/prooph/event-store/issues](https://github.com/prooph/event-store/issues). 33 | - Say hello in the [prooph gitter](https://gitter.im/prooph/improoph) chat. 34 | 35 | ## Contribute 36 | 37 | Please feel free to fork and extend existing or add new plugins and send a pull request with your changes! 38 | To establish a consistent code quality, please provide unit tests for all your changes and may adapt the documentation. 39 | 40 | ## License 41 | 42 | Released under the [New BSD License](LICENSE). 43 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prooph/standard-projections", 3 | "description": "Standard Projections for Prooph EventStore", 4 | "homepage": "http://getprooph.org/", 5 | "license": "BSD-3-Clause", 6 | "authors": [ 7 | { 8 | "name": "Alexander Miertsch", 9 | "email": "contact@prooph.de", 10 | "homepage": "http://www.prooph.de" 11 | }, 12 | { 13 | "name": "Sascha-Oliver Prolic", 14 | "email": "saschaprolic@googlemail.com" 15 | } 16 | ], 17 | "minimum-stability": "dev", 18 | "prefer-stable": true, 19 | "require": { 20 | "php": "^7.4 || ^8.0, <8.4", 21 | "prooph/event-store": "^7.8" 22 | }, 23 | "require-dev": { 24 | "php-coveralls/php-coveralls": "^2.2", 25 | "phpspec/prophecy": "^1.10.3", 26 | "phpunit/phpunit": "^9.5.5", 27 | "prooph/bookdown-template": "^0.2.3", 28 | "prooph/pdo-event-store": "^1.11", 29 | "prooph/php-cs-fixer-config": "^0.5" 30 | }, 31 | "suggest": { 32 | "prooph/pdo-event-store": "^1.6 for usage with MariaDB, MySQL or Postgres as event store" 33 | }, 34 | "conflict": { 35 | "sandrokeil/interop-config": "<1.0" 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "Prooph\\StandardProjections\\": "src/" 40 | } 41 | }, 42 | "autoload-dev": { 43 | "psr-4": { 44 | "ProophTest\\StandardProjections\\": "tests/", 45 | "ProophTest\\EventStore\\": "vendor/prooph/event-store/tests/" 46 | } 47 | }, 48 | "config": { 49 | "sort-packages": true, 50 | "preferred-install": { 51 | "prooph/*": "source" 52 | } 53 | }, 54 | "scripts": { 55 | "check": [ 56 | "@cs-check", 57 | "@test" 58 | ], 59 | "cs-check": "php-cs-fixer fix -v --diff --dry-run", 60 | "cs-fix": "php-cs-fixer fix -v --diff", 61 | "test": "phpunit --colors=always", 62 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /docs/bookdown.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Standard Projections", 3 | "content": [ 4 | {"overview": "projections.md"} 5 | ], 6 | "tocDepth": 1, 7 | "numbering": false, 8 | "target": "./html", 9 | "template": "../vendor/prooph/bookdown-template/templates/main.php" 10 | } 11 | -------------------------------------------------------------------------------- /docs/projections.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | The standard projections are some kind of event-indexing, so you can retrieve events from 4 | all streams at once (`AllStreamProjectionRunner`), by category (`CategoryStreamProjectionRunner`) 5 | or by message name (`MessageNameStreamProjectionRunner`). 6 | 7 | ## Installation 8 | 9 | ```bash 10 | composer require prooph/standard-projections 11 | ``` 12 | 13 | ## Requirements 14 | 15 | - PHP >= 7.1 16 | - Prooph EventStore v7 17 | 18 | ## AllStreamProjectionRunner 19 | 20 | Imagine you have two streams, a stream called `user` and a stream called `blogposts`. If you are 21 | interested in all events coming from both streams, you can use an EventStoreQuery like this: 22 | 23 | ```php 24 | $eventStore 25 | ->createQuery() 26 | ->fromAll() 27 | ``` 28 | 29 | This is slightly unperformant, especially when you have one stream per aggregate, so that you have 30 | to query thousands of streams. This is where the `AllStreamProjectionRunner` comes handy. It projects 31 | all events from all streams into a single large stream, so you can run queries like: 32 | 33 | ```php 34 | $eventStore 35 | ->createQuery() 36 | ->fromStream('$all') 37 | ``` 38 | 39 | ## CategoryStreamProjectionRunner 40 | 41 | Let's say you use one stream per aggregate for users. So you have event streams with names: `user-1`, `user-2` and so on. 42 | You are interested in the events from all user-streams, so your query looks like: 43 | 44 | ```php 45 | $eventStore 46 | ->createQuery() 47 | ->fromCategory('user') 48 | ``` 49 | 50 | With the `CategoryStreamProjectionRunner` you create a single stream for all those events. You can query it like: 51 | 52 | ```php 53 | $eventStore 54 | ->createQuery() 55 | ->fromStream('$ct-user') 56 | ``` 57 | 58 | ## MessageNameStreamProjectionRunner 59 | 60 | The `MessageNameStreamProjectionRunner` creates a stream for each occurring message name. Let's say you 61 | have user-streams with one stream per aggregate again, and streams like `user-1`, `user-2`, and so on. 62 | You are interested in all `UserWasRegistered` events, so your query looks like: 63 | 64 | ```php 65 | $eventStore 66 | ->createQuery() 67 | ->fromCategory('user') 68 | ->when( 69 | [ 70 | UserWasRegistered::class => function (array $state, UserWasRegistered $event): void { 71 | // do something 72 | } 73 | ] 74 | ) 75 | ``` 76 | 77 | This is unperformant in two ways: First we need to query all user-streams and then we need to iterate 78 | over events, we are not interested in. With the `MessageNameStreamProjectionRunner` your query would look like: 79 | 80 | ```php 81 | $eventStore 82 | ->createQuery() 83 | ->fromStream('$mn-UserRegistered') 84 | ``` 85 | 86 | ## Usage 87 | 88 | The runners are expected to run in a simple CLI script. As it's framework agnostic, you have to 89 | provide these cli-scripts yourself. This is how they basically look like: 90 | 91 | ```php 92 | get(\Prooph\EventStore\Projection\ProjectionManager::class); 97 | 98 | $runner = new \Prooph\StandardProjections\AllStreamProjectionRunner($projectionManager); 99 | $runner(); 100 | ``` 101 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./src 6 | 7 | 8 | 9 | 10 | 11 | ./tests 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/AllStreamProjectionRunner.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2024 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\StandardProjections; 15 | 16 | use Prooph\EventStore\Projection\ProjectionManager; 17 | 18 | class AllStreamProjectionRunner 19 | { 20 | /** 21 | * @var ProjectionManager 22 | */ 23 | private $projectionManager; 24 | 25 | public function __construct(ProjectionManager $projectionManager) 26 | { 27 | $this->projectionManager = $projectionManager; 28 | } 29 | 30 | public function __invoke(bool $keepRunning = true): void 31 | { 32 | $this->projectionManager 33 | ->createProjection('$all') 34 | ->fromAll() 35 | ->whenAny(function ($state, $event): void { 36 | $this->emit($event); 37 | }) 38 | ->run($keepRunning); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/CategoryStreamProjectionRunner.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2024 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\StandardProjections; 15 | 16 | use Prooph\EventStore\Projection\ProjectionManager; 17 | 18 | class CategoryStreamProjectionRunner 19 | { 20 | /** 21 | * @var ProjectionManager 22 | */ 23 | private $projectionManager; 24 | 25 | public function __construct(ProjectionManager $projectionManager) 26 | { 27 | $this->projectionManager = $projectionManager; 28 | } 29 | 30 | public function __invoke(bool $keepRunning = true): void 31 | { 32 | $this->projectionManager 33 | ->createProjection('$by_category') 34 | ->fromAll() 35 | ->whenAny(function ($state, $event): void { 36 | $streamName = $this->streamName(); 37 | $pos = \strpos($streamName, '-'); 38 | 39 | if (false === $pos) { 40 | return; 41 | } 42 | 43 | $category = \substr($streamName, 0, $pos); 44 | 45 | $this->linkTo('$ct-' . $category, $event); 46 | }) 47 | ->run($keepRunning); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/MessageNameStreamProjectionRunner.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2024 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\StandardProjections; 15 | 16 | use Prooph\Common\Messaging\Message; 17 | use Prooph\EventStore\Projection\ProjectionManager; 18 | 19 | class MessageNameStreamProjectionRunner 20 | { 21 | /** 22 | * @var ProjectionManager 23 | */ 24 | private $projectionManager; 25 | 26 | public function __construct(ProjectionManager $projectionManager) 27 | { 28 | $this->projectionManager = $projectionManager; 29 | } 30 | 31 | public function __invoke(bool $keepRunning = true): void 32 | { 33 | $this->projectionManager 34 | ->createProjection('$by_message_name') 35 | ->fromAll() 36 | ->whenAny(function ($state, Message $event): void { 37 | $messageName = $event->messageName(); 38 | 39 | $this->linkTo('$mn-' . $messageName, $event); 40 | }) 41 | ->run($keepRunning); 42 | } 43 | } 44 | --------------------------------------------------------------------------------