From 60e3758cf2c7ab807044c196a0f1718529152e09 Mon Sep 17 00:00:00 2001 From: Piotr Zajac Date: Wed, 30 Oct 2024 19:09:54 +0100 Subject: [PATCH] GDPR --- composer.json | 10 ++- .../src/Messaging/Config/ModuleClassList.php | 7 ++ .../Messaging/Config/ModulePackageList.php | 3 + packages/GDPR/.gitattributes | 7 ++ packages/GDPR/.gitignore | 9 ++ packages/GDPR/LICENSE | 21 +++++ packages/GDPR/README.md | 50 +++++++++++ packages/GDPR/composer.json | 90 +++++++++++++++++++ packages/GDPR/phpstan.neon | 4 + packages/GDPR/phpunit.xml.dist | 20 +++++ packages/GDPR/src/Attribute/PersonalData.php | 10 +++ .../src/Config/ForgettablePayloadModule.php | 49 ++++++++++ .../GDPR/src/Config/PersonalDataModule.php | 70 +++++++++++++++ .../ForgettablePayloadConfiguration.php | 29 ++++++ .../GDPR/src/Key/EncryptionKeyProvider.php | 12 +++ .../src/Key/StaticEncryptionKeyProvider.php | 29 ++++++ .../PersonalDataEncryptionConfiguration.php | 29 ++++++ .../PersonalDataEncryptionInterceptor.php | 18 ++++ ...rsonalDataEncryptionInterceptorBuilder.php | 17 ++++ .../GDPR/tests/Fixture/User/Configuration.php | 18 ++++ packages/GDPR/tests/Fixture/User/Register.php | 10 +++ packages/GDPR/tests/Fixture/User/User.php | 48 ++++++++++ .../tests/Fixture/User/UserRegistered.php | 21 +++++ packages/GDPR/tests/GDPRTestCase.php | 47 ++++++++++ .../EncryptingAggregateEventsTest.php | 72 +++++++++++++++ .../tests/EventSourcingMessagingTestCase.php | 4 +- packages/local_packages.json | 16 ++-- 27 files changed, 710 insertions(+), 10 deletions(-) create mode 100644 packages/GDPR/.gitattributes create mode 100644 packages/GDPR/.gitignore create mode 100644 packages/GDPR/LICENSE create mode 100644 packages/GDPR/README.md create mode 100644 packages/GDPR/composer.json create mode 100644 packages/GDPR/phpstan.neon create mode 100644 packages/GDPR/phpunit.xml.dist create mode 100644 packages/GDPR/src/Attribute/PersonalData.php create mode 100644 packages/GDPR/src/Config/ForgettablePayloadModule.php create mode 100644 packages/GDPR/src/Config/PersonalDataModule.php create mode 100644 packages/GDPR/src/ForgettablePayload/ForgettablePayloadConfiguration.php create mode 100644 packages/GDPR/src/Key/EncryptionKeyProvider.php create mode 100644 packages/GDPR/src/Key/StaticEncryptionKeyProvider.php create mode 100644 packages/GDPR/src/PersonalData/PersonalDataEncryptionConfiguration.php create mode 100644 packages/GDPR/src/PersonalData/PersonalDataEncryptionInterceptor.php create mode 100644 packages/GDPR/src/PersonalData/PersonalDataEncryptionInterceptorBuilder.php create mode 100644 packages/GDPR/tests/Fixture/User/Configuration.php create mode 100644 packages/GDPR/tests/Fixture/User/Register.php create mode 100644 packages/GDPR/tests/Fixture/User/User.php create mode 100644 packages/GDPR/tests/Fixture/User/UserRegistered.php create mode 100644 packages/GDPR/tests/GDPRTestCase.php create mode 100644 packages/GDPR/tests/Integration/EncryptingAggregateEventsTest.php diff --git a/composer.json b/composer.json index 1d9638c71..964adee84 100644 --- a/composer.json +++ b/composer.json @@ -39,6 +39,7 @@ "packages/PdoEventSourcing/src", "packages/PdoEventSourcing/src" ], + "Ecotone\\GDPR\\": "packages/GDPR/src", "Ecotone\\JMSConverter\\": "packages/JmsConverter/src", "Ecotone\\Redis\\": "packages/Redis/src", "Ecotone\\Sqs\\": "packages/Sqs/src", @@ -84,6 +85,9 @@ "packages/EventSourcing/tests", "packages/PdoEventSourcing/tests" ], + "Test\\Ecotone\\GDPR\\": [ + "packages/GDPR/tests" + ], "Test\\Ecotone\\JMSConverter\\": [ "packages/JmsConverter/tests" ], @@ -107,6 +111,8 @@ } }, "require": { + "ext-amqp": "*", + "ext-openssl": "*", "php": "^8.0", "doctrine/dbal": "^2.12.0|^3.0", "doctrine/persistence": "^2.5", @@ -116,7 +122,6 @@ "enqueue/sqs": "^0.10.15", "enqueue/dsn": "^0.10.4", "enqueue/enqueue": "^0.10.0", - "ext-amqp": "*", "laminas/laminas-code": "^4", "jms/serializer": "^3.17", "laravel/framework": "^9.5.2|^10.0|^11.0", @@ -131,7 +136,8 @@ "wikimedia/composer-merge-plugin": "^2.1", "php-di/php-di": "^7.0.1", "open-telemetry/sdk": "^1.0.0", - "psr/container": "^1.1.1|^2.0.1" + "psr/container": "^1.1.1|^2.0.1", + "defuse/php-encryption": "^2.4" }, "require-dev": { "behat/behat": "^3.10", diff --git a/packages/Ecotone/src/Messaging/Config/ModuleClassList.php b/packages/Ecotone/src/Messaging/Config/ModuleClassList.php index cdf7f50da..5f8a9d504 100644 --- a/packages/Ecotone/src/Messaging/Config/ModuleClassList.php +++ b/packages/Ecotone/src/Messaging/Config/ModuleClassList.php @@ -15,6 +15,8 @@ use Ecotone\Dbal\ObjectManager\ObjectManagerModule; use Ecotone\Dbal\Recoverability\DbalDeadLetterModule; use Ecotone\EventSourcing\Config\EventSourcingModule; +use Ecotone\GDPR\Config\ForgettablePayloadModule; +use Ecotone\GDPR\Config\PersonalDataModule; use Ecotone\JMSConverter\Configuration\JMSConverterConfigurationModule; use Ecotone\JMSConverter\Configuration\JMSDefaultSerialization; use Ecotone\Laravel\Config\LaravelConnectionModule; @@ -157,4 +159,9 @@ class ModuleClassList public const SYMFONY_MODULES = [ SymfonyConnectionModule::class, ]; + + public const GDPR_MODULES = [ + PersonalDataModule::class, + ForgettablePayloadModule::class, + ]; } diff --git a/packages/Ecotone/src/Messaging/Config/ModulePackageList.php b/packages/Ecotone/src/Messaging/Config/ModulePackageList.php index 6b7c287c4..c53780030 100644 --- a/packages/Ecotone/src/Messaging/Config/ModulePackageList.php +++ b/packages/Ecotone/src/Messaging/Config/ModulePackageList.php @@ -20,6 +20,7 @@ final class ModulePackageList public const TRACING_PACKAGE = 'tracing'; public const LARAVEL_PACKAGE = 'laravel'; public const SYMFONY_PACKAGE = 'symfony'; + public const GDPR_PACKAGE = 'gdpr'; public const TEST_PACKAGE = 'test'; public static function getModuleClassesForPackage(string $packageName): array @@ -37,6 +38,7 @@ public static function getModuleClassesForPackage(string $packageName): array ModulePackageList::TEST_PACKAGE => ModuleClassList::TEST_MODULES, ModulePackageList::LARAVEL_PACKAGE => ModuleClassList::LARAVEL_MODULES, ModulePackageList::SYMFONY_PACKAGE => ModuleClassList::SYMFONY_MODULES, + ModulePackageList::GDPR_PACKAGE => ModuleClassList::GDPR_MODULES, default => throw ConfigurationException::create(sprintf('Given unknown package name %s. Available packages name are: %s', $packageName, implode(',', self::allPackages()))) }; } @@ -58,6 +60,7 @@ public static function allPackages(): array self::TRACING_PACKAGE, self::LARAVEL_PACKAGE, self::SYMFONY_PACKAGE, + self::GDPR_PACKAGE, ]; } diff --git a/packages/GDPR/.gitattributes b/packages/GDPR/.gitattributes new file mode 100644 index 000000000..132686fdc --- /dev/null +++ b/packages/GDPR/.gitattributes @@ -0,0 +1,7 @@ +tests/ export-ignore +.coveralls.yml export-ignore +.gitattributes export-ignore +.gitignore export-ignore +behat.yaml export-ignore +phpstan.neon export-ignore +phpunit.xml export-ignore diff --git a/packages/GDPR/.gitignore b/packages/GDPR/.gitignore new file mode 100644 index 000000000..18c159d80 --- /dev/null +++ b/packages/GDPR/.gitignore @@ -0,0 +1,9 @@ +.idea/ +vendor/ +bin/ +tests/coverage +!tests/coverage/.gitkeep +file +.phpunit.result.cache +composer.lock +phpunit.xml diff --git a/packages/GDPR/LICENSE b/packages/GDPR/LICENSE new file mode 100644 index 000000000..6ad6ef15b --- /dev/null +++ b/packages/GDPR/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2024 Dariusz Gafka + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +**Scope of the License** + +Apache-2.0 Licence applies to non Enterprise Functionalities of the Ecotone Framework. +Functionalities of the Ecotone Framework referred to as Enterprise functionalities, are not covered under the Apache-2.0 license. These functionalities are provided under a separate Enterprise License. +For details on the Enterprise License, please write to support@simplycodedsoftware.com. diff --git a/packages/GDPR/README.md b/packages/GDPR/README.md new file mode 100644 index 000000000..2abe581b8 --- /dev/null +++ b/packages/GDPR/README.md @@ -0,0 +1,50 @@ +# This is Read Only Repository +To contribute make use of [Ecotone-Dev repository](https://github.com/ecotoneframework/ecotone-dev). + +

+ +

+ +![Github Actions](https://github.com/ecotoneFramework/ecotone-dev/actions/workflows/split-testing.yml/badge.svg) +[![Latest Stable Version](https://poser.pugx.org/ecotone/ecotone/v/stable)](https://packagist.org/packages/ecotone/ecotone) +[![License](https://poser.pugx.org/ecotone/ecotone/license)](https://packagist.org/packages/ecotone/ecotone) +[![Total Downloads](https://img.shields.io/packagist/dt/ecotone/ecotone)](https://packagist.org/packages/ecotone/ecotone) +[![PHP Version Require](https://img.shields.io/packagist/dependency-v/ecotone/ecotone/php.svg)](https://packagist.org/packages/ecotone/ecotone) + +`Ecotone Framework` is a `Service Bus` which enables `Message-Driven` architecture in `PHP`. +Based on resilient Message-Driven principles provides support for building applications that follows `Domain-Driven Design` (DDD), `Command Query Responsibility Segregation` (CQRS) and `Event Sourcing` (ES). + +> The term "Ecotone", in ecology means transition area between ecosystems, such as forest and grassland. +The Ecotone Framework functions as transition area between your components, modules and services. It glues things together, yet respects the boundaries of each area. + +> Ecotone can be used with [Symfony](https://docs.ecotone.tech/modules/symfony-ddd-cqrs-event-sourcing) and [Laravel](https://docs.ecotone.tech/modules/laravel-ddd-cqrs-event-sourcing) frameworks. + +## Getting started + +The quickstart [page](https://docs.ecotone.tech/quick-start) of the +[reference guide](https://docs.ecotone.tech) provides a starting point for using Ecotone. +Read more on the [Ecotone's Blog](https://blog.ecotone.tech). + +## Feature requests and issue reporting + +Use [issue tracking system](https://github.com/ecotoneframework/ecotone-dev/issues) for new feature request and bugs. +Please verify that it's not already reported by someone else. + +## Contact + +If you want to talk or ask question about Ecotone + +- [**Twitter**](https://twitter.com/EcotonePHP) +- **ecotoneframework@gmail.com** +- [**Community Channel**](https://discord.gg/CctGMcrYnV) + +## Support Ecotone + +If you want to help building and improving Ecotone consider becoming a sponsor: + +- [Sponsor Ecotone](https://github.com/sponsors/dgafka) +- [Contribute to Ecotone](https://github.com/ecotoneframework/ecotone-dev). + +## Tags + +PHP DDD CQRS Event Sourcing Symfony Laravel Service Bus diff --git a/packages/GDPR/composer.json b/packages/GDPR/composer.json new file mode 100644 index 000000000..c73948273 --- /dev/null +++ b/packages/GDPR/composer.json @@ -0,0 +1,90 @@ +{ + "name": "ecotone/gdpr", + "license": [ + "Apache-2.0", + "proprietary" + ], + "homepage": "https://docs.ecotone.tech/", + "forum": "https://discord.gg/CctGMcrYnV", + "type": "library", + "minimum-stability": "dev", + "prefer-stable": true, + "authors": [ + { + "name": "Dariusz Gafka", + "email": "support@simplycodedsoftware.com" + }, + { + "name": "Piotr ZajÄ…c", + "email": "piotr.zajac@unixslayer.pl" + } + ], + "keywords": [ + "ecotone", + "GDPR", + "personal data", + "encryption", + "event sourcing" + ], + "description": "Extends Ecotone with encryption of personal data in recorded events", + "autoload": { + "psr-4": { + "Ecotone\\GDPR\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Test\\Ecotone\\GDPR\\": [ + "tests" + ] + } + }, + "require": { + "ext-openssl": "*", + "defuse/php-encryption": "^2.4", + "ecotone/pdo-event-sourcing": "~1.235.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6", + "phpstan/phpstan": "^1.8", + "wikimedia/composer-merge-plugin": "^2.1" + }, + "scripts": { + "tests:phpstan": "vendor/bin/phpstan", + "tests:phpunit": "vendor/bin/phpunit", + "tests:ci": [ + "@tests:phpstan", + "@tests:phpunit" + ] + }, + "extra": { + "branch-alias": { + "dev-main": "1.235.1-dev" + }, + "ecotone": { + "repository": "gdpr" + }, + "merge-plugin": { + "include": [ + "../local_packages.json" + ] + }, + "license-info": { + "Apache-2.0": { + "name": "Apache License 2.0", + "url": "https://github.com/ecotoneframework/ecotone-dev/blob/main/LICENSE", + "description": "Allows to use non Enterprise features of Ecotone. For more information please write to support@simplycodedsoftware.com" + }, + "proprietary": { + "name": "Enterprise License", + "description": "Allows to use Enterprise features of Ecotone. For more information please write to support@simplycodedsoftware.com" + } + }, + "release-time": "2024-10-26 14:55:13" + }, + "config": { + "allow-plugins": { + "wikimedia/composer-merge-plugin": true + } + } +} diff --git a/packages/GDPR/phpstan.neon b/packages/GDPR/phpstan.neon new file mode 100644 index 000000000..eb8d8c2f1 --- /dev/null +++ b/packages/GDPR/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 1 + paths: + - src diff --git a/packages/GDPR/phpunit.xml.dist b/packages/GDPR/phpunit.xml.dist new file mode 100644 index 000000000..fc3ebe4f7 --- /dev/null +++ b/packages/GDPR/phpunit.xml.dist @@ -0,0 +1,20 @@ + + + + + ./src + + + + + + + + tests + + + diff --git a/packages/GDPR/src/Attribute/PersonalData.php b/packages/GDPR/src/Attribute/PersonalData.php new file mode 100644 index 000000000..989bec40f --- /dev/null +++ b/packages/GDPR/src/Attribute/PersonalData.php @@ -0,0 +1,10 @@ + $gateway->getInterfaceName() === EventStore::class + && in_array($gateway->getRelatedMethodName(), ['appendTo', 'load'], true) + && $personalDataEncryptionConfiguration->isEnabledFor($gateway->getReferenceName()) + ; + + $eventStoreGateways = array_filter($messagingConfiguration->getRegisteredGateways(), $filter); + + foreach ($eventStoreGateways as $gateway) { + if ($gateway->getRelatedMethodName() === 'appendTo') { + $gateway->addBeforeInterceptor(PersonalDataEncryptionInterceptorBuilder::appendToInterceptorBuilder()); + } + if ($gateway->getRelatedMethodName() === 'load') { + $gateway->addAfterInterceptor(PersonalDataEncryptionInterceptorBuilder::loadInterceptorBuilder()); + } + } + } + + public function canHandle($extensionObject): bool + { + return $extensionObject instanceof PersonalDataEncryptionConfiguration; + } + + public function getModulePackageName(): string + { + return ModulePackageList::GDPR_PACKAGE; + } +} diff --git a/packages/GDPR/src/ForgettablePayload/ForgettablePayloadConfiguration.php b/packages/GDPR/src/ForgettablePayload/ForgettablePayloadConfiguration.php new file mode 100644 index 000000000..3c6120174 --- /dev/null +++ b/packages/GDPR/src/ForgettablePayload/ForgettablePayloadConfiguration.php @@ -0,0 +1,29 @@ +useByDefault || in_array($eventStoreReferenceName, $this->isEnabledFor, true); + } + + public function useByDefault(bool $useByDefault = true): self + { + return new self($useByDefault); + } +} diff --git a/packages/GDPR/src/Key/EncryptionKeyProvider.php b/packages/GDPR/src/Key/EncryptionKeyProvider.php new file mode 100644 index 000000000..95af9ebbc --- /dev/null +++ b/packages/GDPR/src/Key/EncryptionKeyProvider.php @@ -0,0 +1,12 @@ +key; + } +} diff --git a/packages/GDPR/src/PersonalData/PersonalDataEncryptionConfiguration.php b/packages/GDPR/src/PersonalData/PersonalDataEncryptionConfiguration.php new file mode 100644 index 000000000..dadf4dff2 --- /dev/null +++ b/packages/GDPR/src/PersonalData/PersonalDataEncryptionConfiguration.php @@ -0,0 +1,29 @@ +useByDefault || in_array($eventStoreReferenceName, $this->isEnabledFor, true); + } + + public function useByDefault(bool $useByDefault = true): self + { + return new self($useByDefault); + } +} diff --git a/packages/GDPR/src/PersonalData/PersonalDataEncryptionInterceptor.php b/packages/GDPR/src/PersonalData/PersonalDataEncryptionInterceptor.php new file mode 100644 index 000000000..1c18184a9 --- /dev/null +++ b/packages/GDPR/src/PersonalData/PersonalDataEncryptionInterceptor.php @@ -0,0 +1,18 @@ +useByDefault(); + } +} diff --git a/packages/GDPR/tests/Fixture/User/Register.php b/packages/GDPR/tests/Fixture/User/Register.php new file mode 100644 index 000000000..4d900d027 --- /dev/null +++ b/packages/GDPR/tests/Fixture/User/Register.php @@ -0,0 +1,10 @@ +id, $command->email), + ]; + } + + #[QueryHandler('user.email')] + public function email(): string + { + return $this->email; + } + + #[EventSourcingHandler] + public function applyUserRegistered(UserRegistered $event): void + { + $this->id = $event->id; + $this->email = $event->email; + } +} diff --git a/packages/GDPR/tests/Fixture/User/UserRegistered.php b/packages/GDPR/tests/Fixture/User/UserRegistered.php new file mode 100644 index 000000000..a427219fd --- /dev/null +++ b/packages/GDPR/tests/Fixture/User/UserRegistered.php @@ -0,0 +1,21 @@ +getConnection()); + } + + public function getConnectionFactory(): ConnectionFactory + { + $dsn = getenv('DATABASE_DSN') ? getenv('DATABASE_DSN') : 'pgsql://ecotone:secret@127.0.0.1:5432/ecotone'; + if (! $dsn) { + throw new \InvalidArgumentException('Missing env `DATABASE_DSN` pointing to test database'); + } + + return new DbalConnectionFactory($dsn); + } + + public function getConnection(): Connection + { + return $this->getConnectionFactory()->createContext()->getDbalConnection(); + } + + protected static function clearDataTables(Connection $connection): void + { + foreach (self::getSchemaManager($connection)->listTableNames() as $tableNames) { + $sql = 'DROP TABLE ' . $tableNames; + $connection->executeQuery($sql); + } + } + + protected static function getSchemaManager(Connection $connection): AbstractSchemaManager + { + return method_exists($connection, 'getSchemaManager') ? $connection->getSchemaManager() : $connection->createSchemaManager(); + } +} diff --git a/packages/GDPR/tests/Integration/EncryptingAggregateEventsTest.php b/packages/GDPR/tests/Integration/EncryptingAggregateEventsTest.php new file mode 100644 index 000000000..1a4c9f4b9 --- /dev/null +++ b/packages/GDPR/tests/Integration/EncryptingAggregateEventsTest.php @@ -0,0 +1,72 @@ +bootstrap($key); + + $ecotone->withEventsFor( + 'user-1', + User::class, + [ + new UserRegistered('user-1', 'john@doe.com'), + ] + ); + + self::assertEquals( + Crypto::encrypt('john@doe.com', $key), + $this->getConnection()->fetchOne("select payload->>'email' from _12dea96fec20593566ab75692c9949596833adc9 where event_name = ? and metadata->>'_aggregate_id' = ?", ['user.registered', 'user-1']) + ); + + $ecotone->sendCommand(new Register('user-2', 'marry-ann@doe.com')); + + self::assertEquals( + Crypto::encrypt('marry-ann@doe.com', $key), + $this->getConnection()->fetchOne("select payload->>'email' from _12dea96fec20593566ab75692c9949596833adc9 where event_name = ? and metadata->>'_aggregate_id' = ?", ['user.registered', 'user-2']) + ); + } + + private function bootstrap(Key $key): FlowTestSupport + { + return EcotoneLite::bootstrapFlowTestingWithEventStore( + classesToResolve: [User::class], + containerOrAvailableServices: [DbalConnectionFactory::class => $this->getConnectionFactory()], + configuration: ServiceConfiguration::createWithDefaults() + ->withSkippedModulePackageNames( + ModulePackageList::allPackagesExcept( + [ + ModulePackageList::DBAL_PACKAGE, + ModulePackageList::EVENT_SOURCING_PACKAGE, + ModulePackageList::GDPR_PACKAGE, + ModulePackageList::JMS_CONVERTER_PACKAGE, + ] + ) + ) + ->withExtensionObjects([ + StaticEncryptionKeyProvider::create($key), + ]) + ->withNamespaces(['Test\Ecotone\GDPR\Fixture\User']), + pathToRootCatalog: __DIR__ . '/../../', + runForProductionEventStore: true + ); + } +} diff --git a/packages/PdoEventSourcing/tests/EventSourcingMessagingTestCase.php b/packages/PdoEventSourcing/tests/EventSourcingMessagingTestCase.php index d84dc70eb..a945cf5e5 100644 --- a/packages/PdoEventSourcing/tests/EventSourcingMessagingTestCase.php +++ b/packages/PdoEventSourcing/tests/EventSourcingMessagingTestCase.php @@ -3,6 +3,7 @@ namespace Test\Ecotone\EventSourcing; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\AbstractSchemaManager; use Ecotone\Dbal\DbalConnection; use Ecotone\Messaging\Handler\InMemoryReferenceSearchService; use Enqueue\Dbal\DbalConnectionFactory; @@ -18,10 +19,9 @@ abstract class EventSourcingMessagingTestCase extends TestCase private ConnectionFactory $tenantAConnection; private ConnectionFactory $tenantBConnection; - protected static function getSchemaManager(Connection $connection): \Doctrine\DBAL\Schema\AbstractSchemaManager + protected static function getSchemaManager(Connection $connection): AbstractSchemaManager { return method_exists($connection, 'getSchemaManager') ? $connection->getSchemaManager() : $connection->createSchemaManager(); - ; } protected function setUp(): void diff --git a/packages/local_packages.json b/packages/local_packages.json index ae0ba53dc..52b0c4267 100644 --- a/packages/local_packages.json +++ b/packages/local_packages.json @@ -6,11 +6,11 @@ }, { "type": "path", - "url": "Ecotone" + "url": "Dbal" }, { "type": "path", - "url": "Dbal" + "url": "Ecotone" }, { "type": "path", @@ -18,7 +18,7 @@ }, { "type": "path", - "url": "PdoEventSourcing" + "url": "GDPR" }, { "type": "path", @@ -30,11 +30,15 @@ }, { "type": "path", - "url": "Symfony" + "url": "LiteApplication" }, { "type": "path", - "url": "LiteApplication" + "url": "PdoEventSourcing" + }, + { + "type": "path", + "url": "Symfony" } ] -} \ No newline at end of file +}