diff --git a/src/Provider/Doctrine/Configuration.php b/src/Provider/Doctrine/Configuration.php index e76a309a..db565498 100644 --- a/src/Provider/Doctrine/Configuration.php +++ b/src/Provider/Doctrine/Configuration.php @@ -5,6 +5,7 @@ namespace DH\Auditor\Provider\Doctrine; use DH\Auditor\Provider\ConfigurationInterface; +use DH\Auditor\Provider\Doctrine\Persistence\Helper\SchemaHelper; use DH\Auditor\Provider\Doctrine\Service\AuditingService; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -19,6 +20,16 @@ class Configuration implements ConfigurationInterface private string $tableSuffix; + /** + * @var array + */ + private array $extraFields = []; + + /** + * @var array + */ + private array $extraIndices = []; + private array $ignoredColumns; private ?array $entities = null; @@ -53,6 +64,20 @@ public function __construct(array $options) } } + if (isset($config['extra_fields']) && !empty($config['extra_fields'])) { + // use field names as array keys for easier lookup + foreach ($config['extra_fields'] as $fieldName => $fieldOptions) { + $this->extraFields[$fieldName] = $fieldOptions; + } + } + + if (isset($config['extra_indices']) && !empty($config['extra_indices'])) { + // use index names as array keys for easier lookup + foreach ($config['extra_indices'] as $indexName => $indexOptions) { + $this->extraIndices[$indexName] = $indexOptions; + } + } + $this->storageServices = $config['storage_services']; $this->auditingServices = $config['auditing_services']; $this->isViewerEnabled = $config['viewer']; @@ -68,6 +93,8 @@ public function configureOptions(OptionsResolver $resolver): void 'table_suffix' => '_audit', 'ignored_columns' => [], 'entities' => [], + 'extra_fields' => [], + 'extra_indices' => [], 'storage_services' => [], 'auditing_services' => [], 'viewer' => true, @@ -77,6 +104,8 @@ public function configureOptions(OptionsResolver $resolver): void ->setAllowedTypes('table_suffix', 'string') ->setAllowedTypes('ignored_columns', 'array') ->setAllowedTypes('entities', 'array') + ->setAllowedTypes('extra_fields', 'array') + ->setAllowedTypes('extra_indices', 'array') ->setAllowedTypes('storage_services', 'array') ->setAllowedTypes('auditing_services', 'array') ->setAllowedTypes('viewer', 'bool') @@ -157,6 +186,69 @@ public function getIgnoredColumns(): array return $this->ignoredColumns; } + public function getExtraFields(): array + { + return $this->extraFields; + } + + public function getAllFields(): array + { + return array_merge( + SchemaHelper::getAuditTableColumns(), + $this->extraFields + ); + } + + /** + * @param array $extraFields + * + * @return $this + */ + public function setExtraFields(array $extraFields): self + { + $this->extraFields = $extraFields; + + return $this; + } + + public function getExtraIndices(): array + { + return $this->extraIndices; + } + + public function prepareExtraIndices(string $tablename): array + { + $indices = []; + foreach ($this->extraIndices as $extraIndexField => $extraIndexOptions) { + $indices[$extraIndexField] = [ + 'type' => $extraIndexOptions['type'] ?? 'index', + 'name' => sprintf('%s_%s_idx', $extraIndexOptions['name_prefix'] ?? $extraIndexField, md5($tablename)), + ]; + } + + return $indices; + } + + public function getAllIndices(string $tablename): array + { + return array_merge( + SchemaHelper::getAuditTableIndices($tablename), + $this->prepareExtraIndices($tablename) + ); + } + + /** + * @param array $extraIndices + * + * @return $this + */ + public function setExtraIndices(array $extraIndices): self + { + $this->extraIndices = $extraIndices; + + return $this; + } + /** * Get the value of entities. */ @@ -216,10 +308,7 @@ public function setStorageMapper(callable $mapper): self return $this; } - /** - * @return null|callable|string - */ - public function getStorageMapper() + public function getStorageMapper(): ?callable { return $this->storageMapper; } diff --git a/src/Provider/Doctrine/DoctrineProvider.php b/src/Provider/Doctrine/DoctrineProvider.php index 7cdf5209..0b825b62 100644 --- a/src/Provider/Doctrine/DoctrineProvider.php +++ b/src/Provider/Doctrine/DoctrineProvider.php @@ -87,8 +87,6 @@ public function getStorageServiceForEntity(string $entity): StorageServiceInterf return array_values($this->getStorageServices())[0]; } - \assert(\is_callable($storageMapper)); // helps PHPStan - return $storageMapper($entity, $this->getStorageServices()); } @@ -99,19 +97,8 @@ public function persist(LifecycleEvent $event): void $entity = $payload['entity']; unset($payload['table'], $payload['entity']); - $fields = [ - 'type' => ':type', - 'object_id' => ':object_id', - 'discriminator' => ':discriminator', - 'transaction_hash' => ':transaction_hash', - 'diffs' => ':diffs', - 'blame_id' => ':blame_id', - 'blame_user' => ':blame_user', - 'blame_user_fqdn' => ':blame_user_fqdn', - 'blame_user_firewall' => ':blame_user_firewall', - 'ip' => ':ip', - 'created_at' => ':created_at', - ]; + $fields = array_combine(array_keys($payload), array_map(function ($x) {return ":{$x}"; }, array_keys($payload))); + \assert(\is_array($fields)); // helps PHPStan $query = sprintf( 'INSERT INTO %s (%s) VALUES (%s)', @@ -129,11 +116,6 @@ public function persist(LifecycleEvent $event): void } $statement->execute(); - - // let's get the last inserted ID from the database so other providers can use that info - $payload = $event->getPayload(); - $payload['id'] = (int) $storageService->getEntityManager()->getConnection()->lastInsertId(); - $event->setPayload($payload); } /** diff --git a/src/Provider/Doctrine/Persistence/Reader/Query.php b/src/Provider/Doctrine/Persistence/Reader/Query.php index 10f03f88..51c0be57 100644 --- a/src/Provider/Doctrine/Persistence/Reader/Query.php +++ b/src/Provider/Doctrine/Persistence/Reader/Query.php @@ -6,6 +6,8 @@ use DH\Auditor\Exception\InvalidArgumentException; use DH\Auditor\Model\Entry; +use DH\Auditor\Provider\ConfigurationInterface; +use DH\Auditor\Provider\Doctrine\Configuration; use DH\Auditor\Provider\Doctrine\Persistence\Helper\SchemaHelper; use DH\Auditor\Provider\Doctrine\Persistence\Reader\Filter\DateRangeFilter; use DH\Auditor\Provider\Doctrine\Persistence\Reader\Filter\FilterInterface; @@ -37,14 +39,21 @@ class Query private string $table; + /** + * @var Configuration + */ + private ConfigurationInterface $configuration; + private int $offset = 0; private int $limit = 0; - public function __construct(string $table, Connection $connection) + public function __construct(string $table, Connection $connection, ConfigurationInterface $configuration) { $this->connection = $connection; $this->table = $table; + \assert($configuration instanceof Configuration); + $this->configuration = $configuration; foreach ($this->getSupportedFilters() as $filterType) { $this->filters[$filterType] = []; @@ -149,7 +158,7 @@ public function limit(int $limit, int $offset = 0): self public function getSupportedFilters(): array { - return array_keys(SchemaHelper::getAuditTableIndices('fake')); + return array_keys($this->configuration->getAllIndices('fake')); } public function getFilters(): array diff --git a/src/Provider/Doctrine/Persistence/Reader/Reader.php b/src/Provider/Doctrine/Persistence/Reader/Reader.php index 72d34a80..d5f0f8a8 100644 --- a/src/Provider/Doctrine/Persistence/Reader/Reader.php +++ b/src/Provider/Doctrine/Persistence/Reader/Reader.php @@ -50,7 +50,11 @@ public function createQuery(string $entity, array $options = []): Query /** @var StorageService $storageService */ $storageService = $this->provider->getStorageServiceForEntity($entity); - $query = new Query($this->getEntityAuditTableName($entity), $storageService->getEntityManager()->getConnection()); + $query = new Query( + $this->getEntityAuditTableName($entity), + $storageService->getEntityManager()->getConnection(), + $this->provider->getConfiguration() + ); $query ->addOrderBy(Query::CREATED_AT, 'DESC') ->addOrderBy(Query::ID, 'DESC') @@ -83,6 +87,12 @@ public function createQuery(string $entity, array $options = []): Query $query->addFilter(new SimpleFilter(Query::DISCRIMINATOR, $entity)); } + foreach ($this->provider->getConfiguration()->getExtraIndices() as $indexedField => $extraIndexConfig) { + if (null !== $config[$indexedField]) { + $query->addFilter($indexedField, $config[$indexedField]); + } + } + return $query; } @@ -107,6 +117,11 @@ public function configureOptions(OptionsResolver $resolver): void ->setAllowedValues('page', static fn ($value) => null === $value || $value >= 1) ->setAllowedValues('page_size', static fn ($value) => null === $value || $value >= 1) ; + + foreach ($this->provider->getConfiguration()->getExtraIndices() as $indexedField => $extraIndexConfig) { + $resolver->setDefault($indexedField, null); + $resolver->setAllowedTypes($indexedField, ['null', 'int', 'string', 'array']); + } } /** diff --git a/src/Provider/Doctrine/Persistence/Schema/SchemaManager.php b/src/Provider/Doctrine/Persistence/Schema/SchemaManager.php index e67227eb..3c596d3a 100644 --- a/src/Provider/Doctrine/Persistence/Schema/SchemaManager.php +++ b/src/Provider/Doctrine/Persistence/Schema/SchemaManager.php @@ -8,7 +8,6 @@ use DH\Auditor\Provider\Doctrine\DoctrineProvider; use DH\Auditor\Provider\Doctrine\Persistence\Helper\DoctrineHelper; use DH\Auditor\Provider\Doctrine\Persistence\Helper\PlatformHelper; -use DH\Auditor\Provider\Doctrine\Persistence\Helper\SchemaHelper; use DH\Auditor\Provider\Doctrine\Service\AuditingService; use DH\Auditor\Provider\Doctrine\Service\StorageService; use Doctrine\DBAL\Connection; @@ -190,7 +189,7 @@ public function createAuditTable(string $entity, $table, ?Schema $schema = null) // Add columns to audit table $isJsonSupported = PlatformHelper::isJsonSupported($connection); - foreach (SchemaHelper::getAuditTableColumns() as $columnName => $struct) { + foreach ($configuration->getAllFields() as $columnName => $struct) { if (DoctrineHelper::getDoctrineType('JSON') === $struct['type'] && $isJsonSupported) { $type = DoctrineHelper::getDoctrineType('TEXT'); } else { @@ -201,7 +200,7 @@ public function createAuditTable(string $entity, $table, ?Schema $schema = null) } // Add indices to audit table - foreach (SchemaHelper::getAuditTableIndices($auditTablename) as $columnName => $struct) { + foreach ($configuration->getAllIndices($auditTablename) as $columnName => $struct) { if ('primary' === $struct['type']) { $auditTable->setPrimaryKey([$columnName]); } else { @@ -234,14 +233,16 @@ public function updateAuditTable(string $entity, Table $table, ?Schema $schema = $schema = $schemaManager->createSchema(); } + /** @var Configuration $configuration */ + $configuration = $this->provider->getConfiguration(); $table = $schema->getTable($table->getName()); $columns = $schemaManager->listTableColumns($table->getName()); // process columns - $this->processColumns($table, $columns, SchemaHelper::getAuditTableColumns(), $connection); + $this->processColumns($table, $columns, $configuration->getAllFields(), $connection); // process indices - $this->processIndices($table, SchemaHelper::getAuditTableIndices($table->getName()), $connection); + $this->processIndices($table, $configuration->getAllIndices($table->getName()), $connection); return $schema; } diff --git a/tests/Provider/Doctrine/ConfigurationTest.php b/tests/Provider/Doctrine/ConfigurationTest.php index d74e896b..7a2022af 100644 --- a/tests/Provider/Doctrine/ConfigurationTest.php +++ b/tests/Provider/Doctrine/ConfigurationTest.php @@ -123,4 +123,75 @@ public function testGetEntities(): void self::assertSame($entities, $configuration->getEntities(), 'AuditConfiguration::getEntities() returns configured entities list.'); } + + public function testGetExtraFields(): void + { + $extraFields = [ + 'example_int_field' => [ + 'type' => 'integer', + 'options' => [ + 'notnull' => true, + ], + ], + 'example_string_field' => [ + 'type' => 'string', + 'options' => [ + 'notnull' => false, + 'length' => 50, + ], + ], + ]; + + $configuration = $this->createProviderConfiguration([ + 'extra_fields' => $extraFields, + ]); + + self::assertSame($extraFields, $configuration->getExtraFields(), 'AuditConfiguration::getExtraFields() returns configured extra fields list.'); + } + + public function testGetExtraIndices(): void + { + $extraIndices = [ + 'example_default_index' => null, + 'example_configured_index' => [ + 'type' => 'primary', + 'name_prefix' => 'another_prefix', + ], + ]; + + $configuration = $this->createProviderConfiguration([ + 'extra_indices' => $extraIndices, + ]); + + self::assertSame($extraIndices, $configuration->getExtraIndices(), 'AuditConfiguration::getExtraIndices() returns configured extra indices list.'); + } + + public function testPrepareExtraIndices(): void + { + $extraIndicesConfig = [ + 'example_default_index' => null, + 'example_configured_index' => [ + 'type' => 'primary', + 'name_prefix' => 'another_prefix', + ], + ]; + $tableName = 'test_table'; + + $extraIndicesExpected = [ + 'example_default_index' => [ + 'type' => 'index', + 'name' => 'example_default_index_'.md5($tableName).'_idx', + ], + 'example_configured_index' => [ + 'type' => 'primary', + 'name' => 'another_prefix_'.md5($tableName).'_idx', + ], + ]; + + $configuration = $this->createProviderConfiguration([ + 'extra_indices' => $extraIndicesConfig, + ]); + + self::assertSame($extraIndicesExpected, $configuration->prepareExtraIndices($tableName), 'AuditConfiguration::prepareExtraIndices() returns transformed index list.'); + } } diff --git a/tests/Provider/Doctrine/Persistence/Reader/QueryTest.php b/tests/Provider/Doctrine/Persistence/Reader/QueryTest.php index 012bd49f..322dd925 100644 --- a/tests/Provider/Doctrine/Persistence/Reader/QueryTest.php +++ b/tests/Provider/Doctrine/Persistence/Reader/QueryTest.php @@ -11,6 +11,7 @@ use DH\Auditor\Provider\Doctrine\Persistence\Reader\Filter\SimpleFilter; use DH\Auditor\Provider\Doctrine\Persistence\Reader\Query; use DH\Auditor\Tests\Provider\Doctrine\Traits\ConnectionTrait; +use DH\Auditor\Tests\Provider\Doctrine\Traits\ProviderConfigurationTrait; use DH\Auditor\Tests\Traits\ReflectionTrait; use PHPUnit\Framework\TestCase; @@ -23,10 +24,11 @@ final class QueryTest extends TestCase { use ConnectionTrait; use ReflectionTrait; + use ProviderConfigurationTrait; public function testNoFiltersByDefault(): void { - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $filters = $query->getFilters(); foreach ($filters as $filter => $values) { @@ -39,7 +41,7 @@ public function testNoFiltersByDefault(): void */ public function testAddSimpleFilter(): void { - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $filter1 = new SimpleFilter(Query::TRANSACTION_HASH, '123abc'); $query->addFilter($filter1); @@ -62,12 +64,52 @@ public function testAddSimpleFilter(): void self::assertSame([$filter1, $filter2, $filter3], $filters[Query::TRANSACTION_HASH], 'Second filter is added.'); } + /** + * @depends testAddSimpleFilter + */ + public function testAddExtraIndexFilter(): void + { + $query = new Query( + 'author_audit', + $this->createConnection(), + $this->createProviderConfiguration([ + 'extra_fields' => [ + 'extra_column' => [ + 'type' => 'string', + 'options' => ['notnull' => true] + ] + ], + 'extra_indices' => ['extra_column' => null] + ]) + ); + $filter1 = new SimpleFilter('extra_column', '123abc'); + $query->addFilter($filter1); + + $filters = $query->getFilters(); + self::assertCount(1, $filters['extra_column'], 'Filter is added.'); + self::assertSame([$filter1], $filters['extra_column'], 'Filter is added.'); + + $filter2 = new SimpleFilter('extra_column', '456def'); + $query->addFilter($filter2); + + $filters = $query->getFilters(); + self::assertCount(2, $filters['extra_column'], 'Filter is added.'); + self::assertSame([$filter1, $filter2], $filters['extra_column'], 'Second filter is added.'); + + $filter3 = new SimpleFilter('extra_column', ['789ghi', '012jkl']); + $query->addFilter($filter3); + + $filters = $query->getFilters(); + self::assertCount(3, $filters['extra_column'], 'Filter is added.'); + self::assertSame([$filter1, $filter2, $filter3], $filters['extra_column'], 'Second filter is added.'); + } + /** * @depends testAddSimpleFilter */ public function testAddUnexpectedFilter(): void { - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $this->expectException(InvalidArgumentException::class); @@ -80,7 +122,7 @@ public function testAddUnexpectedFilter(): void public function testAddRangeFilter(): void { // only min bound - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $filter = new RangeFilter(Query::OBJECT_ID, 1); $query->addFilter($filter); @@ -88,7 +130,7 @@ public function testAddRangeFilter(): void self::assertSame([$filter], $filters[Query::OBJECT_ID], 'Range filter with min bound only is added.'); // only max bound - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $filter = new RangeFilter(Query::OBJECT_ID, null, 1); $query->addFilter($filter); @@ -96,7 +138,7 @@ public function testAddRangeFilter(): void self::assertSame([$filter], $filters[Query::OBJECT_ID], 'Range filter with max bound only is added.'); // min and max bound - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $filter = new RangeFilter(Query::OBJECT_ID, 5, 15); $query->addFilter($filter); @@ -113,7 +155,7 @@ public function testAddDateRangeFilter(): void $max = new DateTimeImmutable('+1 day'); // only min bound - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $filter = new DateRangeFilter(Query::CREATED_AT, $min); $query->addFilter($filter); @@ -121,7 +163,7 @@ public function testAddDateRangeFilter(): void self::assertSame([$filter], $filters[Query::CREATED_AT], 'Date range filter with min bound only is added.'); // only max bound - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $filter = new DateRangeFilter(Query::CREATED_AT, null, $max); $query->addFilter($filter); @@ -129,7 +171,7 @@ public function testAddDateRangeFilter(): void self::assertSame([$filter], $filters[Query::CREATED_AT], 'Date range filter with max bound only is added.'); // min and max bound - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $filter = new DateRangeFilter(Query::CREATED_AT, $min, $max); $query->addFilter($filter); @@ -139,7 +181,7 @@ public function testAddDateRangeFilter(): void public function testNoOrderByByDefault(): void { - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); self::assertSame([], $query->getOrderBy(), 'No ORDER BY by default.'); } @@ -149,7 +191,7 @@ public function testNoOrderByByDefault(): void */ public function testAddOrderBy(): void { - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $query->addOrderBy(Query::TRANSACTION_HASH, 'ASC'); $orderBy = $query->getOrderBy(); @@ -175,7 +217,7 @@ public function testAddOrderBy(): void */ public function testAddUnexpectedOrderBy(): void { - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $this->expectException(InvalidArgumentException::class); @@ -184,7 +226,7 @@ public function testAddUnexpectedOrderBy(): void public function testNoLimitNoOffsetByDefault(): void { - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); self::assertSame([0, 0], $query->getLimit(), 'No LIMIT by default.'); } @@ -194,7 +236,7 @@ public function testNoLimitNoOffsetByDefault(): void */ public function testLimitWithoutOffset(): void { - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $query->limit(10); self::assertSame([10, 0], $query->getLimit(), 'LIMIT without offset is OK.'); @@ -205,7 +247,7 @@ public function testLimitWithoutOffset(): void */ public function testWithLimitAndOffset(): void { - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $query->limit(10, 50); self::assertSame([10, 50], $query->getLimit(), 'LIMIT with offset is OK.'); @@ -216,7 +258,7 @@ public function testWithLimitAndOffset(): void */ public function testLimitNegativeLimit(): void { - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $this->expectException(InvalidArgumentException::class); $query->limit(-1, 50); @@ -227,7 +269,7 @@ public function testLimitNegativeLimit(): void */ public function testLimitNegativeOffset(): void { - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $this->expectException(InvalidArgumentException::class); $query->limit(0, -50); @@ -239,7 +281,7 @@ public function testLimitNegativeOffset(): void */ public function testBuildQueryBuilderDefault(): void { - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $reflectedMethod = $this->reflectMethod($query, 'buildQueryBuilder'); $queryBuilder = $reflectedMethod->invokeArgs($query, []); @@ -255,7 +297,7 @@ public function testBuildQueryBuilderDefault(): void */ public function testBuildQueryBuilderSimpleFilter(): void { - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $reflectedMethod = $this->reflectMethod($query, 'buildQueryBuilder'); // test SQL query with 1 filter @@ -289,12 +331,63 @@ public function testBuildQueryBuilderSimpleFilter(): void self::assertSame($expectedParameters, $queryBuilder->getParameters(), 'Parameters OK with 3 filters.'); } + /** + * @depends testBuildQueryBuilderSimpleFilter + */ + public function testBuildQueryBuilderExtraIndexFilter(): void + { + $query = new Query( + 'author_audit', + $this->createConnection(), + $this->createProviderConfiguration([ + 'extra_fields' => [ + 'extra_column' => [ + 'type' => 'string', + 'options' => ['notnull' => true] + ] + ], + 'extra_indices' => ['extra_column' => null] + ]) + ); + $reflectedMethod = $this->reflectMethod($query, 'buildQueryBuilder'); + + // test SQL query with 1 filter + $expectedQuery = 'SELECT * FROM author_audit at WHERE extra_column = :extra_column'; + $expectedParameters = [ + 'extra_column' => '123abc', + ]; + $query->addFilter(new SimpleFilter('extra_column', '123abc')); + $queryBuilder = $reflectedMethod->invokeArgs($query, []); + self::assertSame($expectedQuery, $queryBuilder->getSQL(), 'SQL query is OK with 1 filter.'); + self::assertSame($expectedParameters, $queryBuilder->getParameters(), 'Parameters OK with 1 filter.'); + + // test SQL query with 2 filters + $expectedQuery = 'SELECT * FROM author_audit at WHERE extra_column IN (:extra_column)'; + $expectedParameters = [ + 'extra_column' => ['123abc', '456def'], + ]; + $query->addFilter(new SimpleFilter('extra_column', '456def')); + $queryBuilder = $reflectedMethod->invokeArgs($query, []); + self::assertSame($expectedQuery, $queryBuilder->getSQL(), 'SQL query is OK with 2 filters.'); + self::assertSame($expectedParameters, $queryBuilder->getParameters(), 'Parameters OK with 2 filters.'); + + // test SQL query with 3 filters + $expectedQuery = 'SELECT * FROM author_audit at WHERE extra_column IN (:extra_column)'; + $expectedParameters = [ + 'extra_column' => ['123abc', '456def', '789ghj', '012jkl'], + ]; + $query->addFilter(new SimpleFilter('extra_column', ['789ghj', '012jkl'])); + $queryBuilder = $reflectedMethod->invokeArgs($query, []); + self::assertSame($expectedQuery, $queryBuilder->getSQL(), 'SQL query is OK with 3 filters.'); + self::assertSame($expectedParameters, $queryBuilder->getParameters(), 'Parameters OK with 3 filters.'); + } + /** * @depends testBuildQueryBuilderDefault */ public function testBuildQueryBuilderOrderBy(): void { - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $reflectedMethod = $this->reflectMethod($query, 'buildQueryBuilder'); // test SQL query with 1 ORDER BY @@ -315,7 +408,7 @@ public function testBuildQueryBuilderOrderBy(): void */ public function testBuildQueryBuilderLimit(): void { - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $reflectedMethod = $this->reflectMethod($query, 'buildQueryBuilder'); // test SQL query with LIMIT @@ -337,7 +430,7 @@ public function testBuildQueryBuilderLimit(): void public function testBuildQueryBuilderRangeFilter(): void { // test SQL query with a range filter, min bound only - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $reflectedMethod = $this->reflectMethod($query, 'buildQueryBuilder'); $expectedQuery = 'SELECT * FROM author_audit at WHERE object_id >= :min_object_id'; @@ -346,7 +439,7 @@ public function testBuildQueryBuilderRangeFilter(): void self::assertSame($expectedQuery, $queryBuilder->getSQL(), 'SQL query is OK with a range filter with min bound only.'); // test SQL query with a range filter, max bound only - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $reflectedMethod = $this->reflectMethod($query, 'buildQueryBuilder'); $expectedQuery = 'SELECT * FROM author_audit at WHERE object_id <= :max_object_id'; @@ -355,7 +448,7 @@ public function testBuildQueryBuilderRangeFilter(): void self::assertSame($expectedQuery, $queryBuilder->getSQL(), 'SQL query is OK with a range filter with max bound only.'); // test SQL query with a range filter with both bounds - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $reflectedMethod = $this->reflectMethod($query, 'buildQueryBuilder'); $expectedQuery = 'SELECT * FROM author_audit at WHERE object_id >= :min_object_id AND object_id <= :max_object_id'; @@ -373,7 +466,7 @@ public function testBuildQueryBuilderDateRangeFilter(): void $max = new DateTimeImmutable('+1 day'); // test SQL query with a date range filter, min bound only - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $reflectedMethod = $this->reflectMethod($query, 'buildQueryBuilder'); $expectedQuery = 'SELECT * FROM author_audit at WHERE object_id >= :min_object_id'; @@ -382,7 +475,7 @@ public function testBuildQueryBuilderDateRangeFilter(): void self::assertSame($expectedQuery, $queryBuilder->getSQL(), 'SQL query is OK with a range filter with min bound only.'); // test SQL query with a date range filter, max bound only - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $reflectedMethod = $this->reflectMethod($query, 'buildQueryBuilder'); $expectedQuery = 'SELECT * FROM author_audit at WHERE object_id <= :max_object_id'; @@ -391,7 +484,7 @@ public function testBuildQueryBuilderDateRangeFilter(): void self::assertSame($expectedQuery, $queryBuilder->getSQL(), 'SQL query is OK with a range filter with max bound only.'); // test SQL query with a date range filter with both bounds - $query = new Query('author_audit', $this->createConnection()); + $query = new Query('author_audit', $this->createConnection(), $this->createProviderConfiguration([])); $reflectedMethod = $this->reflectMethod($query, 'buildQueryBuilder'); $expectedQuery = 'SELECT * FROM author_audit at WHERE object_id >= :min_object_id AND object_id <= :max_object_id'; diff --git a/tests/Provider/Doctrine/Persistence/Schema/SchemaManagerTest.php b/tests/Provider/Doctrine/Persistence/Schema/SchemaManagerTest.php index 61be8749..174f7859 100644 --- a/tests/Provider/Doctrine/Persistence/Schema/SchemaManagerTest.php +++ b/tests/Provider/Doctrine/Persistence/Schema/SchemaManagerTest.php @@ -62,6 +62,24 @@ public function testStorageServicesSetup(): void public function testCreateAuditTable(): void { + $extraFields = [ + 'extra_field' => [ + 'type' => 'integer', + 'options' => [ + 'notnull' => true, + ], + ], + ]; + $extraIndices = [ + 'extra_field' => [ + 'type' => 'index', + 'name_prefix' => 'other_prefix', + ], + ]; + /** @var Configuration $configuration */ + $configuration = $this->provider->getConfiguration(); + $configuration->setExtraFields($extraFields); + $configuration->setExtraIndices($extraIndices); $updater = new SchemaManager($this->provider); /** @var StorageService $storageService */ @@ -83,13 +101,21 @@ public function testCreateAuditTable(): void self::assertNotNull($authorAuditTable, 'author_audit table has been created.'); // check expected columns - $expected = SchemaHelper::getAuditTableColumns(); + $expected = array_merge(SchemaHelper::getAuditTableColumns(), $extraFields); foreach ($expected as $name => $options) { self::assertTrue($authorAuditTable->hasColumn($name), 'audit table has a column named "'.$name.'".'); } // check expected indices - $expected = SchemaHelper::getAuditTableIndices($authorAuditTable->getName()); + $expected = array_merge( + SchemaHelper::getAuditTableIndices($authorAuditTable->getName()), + [ + 'extra_field' => [ + 'type' => 'index', + 'name' => 'other_prefix_'.md5($authorAuditTable->getName()).'_idx', + ], + ] + ); foreach ($expected as $name => $options) { if ('primary' === $options['type']) { self::assertTrue($authorAuditTable->hasPrimaryKey(), 'audit table has a primary key named "'.$name.'".'); diff --git a/tests/Provider/Doctrine/Traits/ConnectionTrait.php b/tests/Provider/Doctrine/Traits/ConnectionTrait.php index 468d0542..5dc1bb74 100644 --- a/tests/Provider/Doctrine/Traits/ConnectionTrait.php +++ b/tests/Provider/Doctrine/Traits/ConnectionTrait.php @@ -4,6 +4,7 @@ namespace DH\Auditor\Tests\Provider\Doctrine\Traits; +use DH\Auditor\Provider\Doctrine\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager;