feat: add advanced architecture, documentation, and coverage improvements (#65)

* fix(style): resolve PHPCS line-length warnings in source files

* fix(style): resolve PHPCS line-length warnings in test files

* feat(audit): add structured audit logging with ErrorContext and AuditContext

- ErrorContext: standardized error information with sensitive data sanitization
- AuditContext: structured context for audit entries with operation types
- StructuredAuditLogger: enhanced audit logger wrapper with timing support

* feat(recovery): add recovery mechanism for failed masking operations

- FailureMode enum: FAIL_OPEN, FAIL_CLOSED, FAIL_SAFE modes
- RecoveryStrategy interface and RecoveryResult value object
- RetryStrategy: exponential backoff with configurable attempts
- FallbackMaskStrategy: type-aware fallback values

* feat(strategies): add CallbackMaskingStrategy for custom masking logic

- Wraps custom callbacks as MaskingStrategy implementations
- Factory methods: constant(), hash(), partial() for common use cases
- Supports exact match and prefix match for field paths

* docs: add framework integration guides and examples

- symfony-integration.md: Symfony service configuration and Monolog setup
- psr3-decorator.md: PSR-3 logger decorator pattern implementation
- framework-examples.md: CakePHP, CodeIgniter 4, Laminas, Yii2, PSR-15
- docker-development.md: Docker development environment guide

* chore(docker): add Docker development environment

- Dockerfile: PHP 8.2-cli-alpine with Xdebug for coverage
- docker-compose.yml: development services with volume mounts

* feat(demo): add interactive GDPR pattern tester playground

- PatternTester.php: pattern testing utility with strategy support
- index.php: web API endpoint with JSON response handling
- playground.html: interactive web interface for testing patterns

* docs(todo): update with completed medium priority items

- Mark all PHPCS warnings as fixed (81 → 0)
- Document new Audit and Recovery features
- Update test count to 1,068 tests with 2,953 assertions
- Move remaining items to low priority

* feat: add advanced architecture, documentation, and coverage improvements

- Add architecture improvements:
  - ArrayAccessorInterface and DotArrayAccessor for decoupled array access
  - MaskingOrchestrator for single-responsibility masking coordination
  - GdprProcessorBuilder for fluent configuration
  - MaskingPluginInterface and AbstractMaskingPlugin for plugin architecture
  - PluginAwareProcessor for plugin hook execution
  - AuditLoggerFactory for instance-based audit logger creation

- Add advanced features:
  - SerializedDataProcessor for handling print_r/var_export/serialize output
  - KAnonymizer with GeneralizationStrategy for GDPR k-anonymity
  - RetentionPolicy for configurable data retention periods
  - StreamingProcessor for memory-efficient large log processing

- Add comprehensive documentation:
  - docs/performance-tuning.md - benchmarking, optimization, caching
  - docs/troubleshooting.md - common issues and solutions
  - docs/logging-integrations.md - ELK, Graylog, Datadog, etc.
  - docs/plugin-development.md - complete plugin development guide

- Improve test coverage (84.41% → 85.07%):
  - ConditionalRuleFactoryInstanceTest (100% coverage)
  - GdprProcessorBuilderEdgeCasesTest (100% coverage)
  - StrategyEdgeCasesTest for ReDoS detection and type parsing
  - 78 new tests, 119 new assertions

- Update TODO.md with current statistics:
  - 141 PHP files, 1,346 tests, 85.07% line coverage

* chore: tests, update actions, sonarcloud issues

* chore: rector

* fix: more sonarcloud fixes

* chore: more fixes

* refactor: copilot review fix

* chore: rector
This commit is contained in:
2025-12-22 13:38:18 +02:00
committed by GitHub
parent b1eb567b92
commit 8866daaf33
112 changed files with 15391 additions and 607 deletions

View File

@@ -22,7 +22,25 @@ class ConfigValidationTest extends TestCase
*
* @return ((bool|int|string)[]|bool|int)[]
*
* @psalm-return array{auto_register: bool, channels: list{'single', 'daily', 'stack'}, patterns: array<never, never>, field_paths: array<never, never>, custom_callbacks: array<never, never>, max_depth: int<1, 1000>, audit_logging: array{enabled: bool, channel: string}, performance: array{chunk_size: int<100, 10000>, garbage_collection_threshold: int<1000, 100000>}, validation: array{max_pattern_length: int<10, 1000>, max_field_path_length: int<5, 500>, allow_empty_patterns: bool, strict_regex_validation: bool}}
* @psalm-return array{
* auto_register: bool,
* channels: list{'single', 'daily', 'stack'},
* patterns: array<never, never>,
* field_paths: array<never, never>,
* custom_callbacks: array<never, never>,
* max_depth: int<1, 1000>,
* audit_logging: array{enabled: bool, channel: string},
* performance: array{
* chunk_size: int<100, 10000>,
* garbage_collection_threshold: int<1000, 100000>
* },
* validation: array{
* max_pattern_length: int<10, 1000>,
* max_field_path_length: int<5, 500>,
* allow_empty_patterns: bool,
* strict_regex_validation: bool
* }
* }
*/
private function getTestConfig(): array
{
@@ -42,10 +60,22 @@ class ConfigValidationTest extends TestCase
'garbage_collection_threshold' => max(1000, min(100000, (int) ($_ENV['GDPR_GC_THRESHOLD'] ?? 10000))),
],
'validation' => [
'max_pattern_length' => max(10, min(1000, (int) ($_ENV['GDPR_MAX_PATTERN_LENGTH'] ?? 500))),
'max_field_path_length' => max(5, min(500, (int) ($_ENV['GDPR_MAX_FIELD_PATH_LENGTH'] ?? 100))),
'allow_empty_patterns' => filter_var($_ENV['GDPR_ALLOW_EMPTY_PATTERNS'] ?? false, FILTER_VALIDATE_BOOLEAN),
'strict_regex_validation' => filter_var($_ENV['GDPR_STRICT_REGEX_VALIDATION'] ?? true, FILTER_VALIDATE_BOOLEAN),
'max_pattern_length' => max(
10,
min(1000, (int) ($_ENV['GDPR_MAX_PATTERN_LENGTH'] ?? 500))
),
'max_field_path_length' => max(
5,
min(500, (int) ($_ENV['GDPR_MAX_FIELD_PATH_LENGTH'] ?? 100))
),
'allow_empty_patterns' => filter_var(
$_ENV['GDPR_ALLOW_EMPTY_PATTERNS'] ?? false,
FILTER_VALIDATE_BOOLEAN
),
'strict_regex_validation' => filter_var(
$_ENV['GDPR_STRICT_REGEX_VALIDATION'] ?? true,
FILTER_VALIDATE_BOOLEAN
),
],
];
}
@@ -426,9 +456,18 @@ class ConfigValidationTest extends TestCase
// Security-focused defaults
$this->assertFalse($config['auto_register'], 'auto_register should default to false');
$this->assertFalse($config['audit_logging']['enabled'], 'audit logging should default to false');
$this->assertFalse($config['validation']['allow_empty_patterns'], 'empty patterns should not be allowed by default');
$this->assertTrue($config['validation']['strict_regex_validation'], 'strict regex validation should be enabled by default');
$this->assertFalse(
$config['audit_logging']['enabled'],
'audit logging should default to false'
);
$this->assertFalse(
$config['validation']['allow_empty_patterns'],
'empty patterns should not be allowed by default'
);
$this->assertTrue(
$config['validation']['strict_regex_validation'],
'strict regex validation should be enabled by default'
);
// Restore environment variables
foreach ($oldValues as $var => $value) {

View File

@@ -21,6 +21,7 @@ use Tests\TestConstants;
* Tests for the GdprProcessor class.
*
* @api
* @psalm-suppress DeprecatedMethod - Tests for deprecated validation methods
*/
#[CoversClass(GdprProcessor::class)]
class GdprProcessorValidationTest extends TestCase
@@ -272,7 +273,8 @@ class GdprProcessorValidationTest extends TestCase
public function constructorThrowsExceptionForInvalidDataTypeMaskKey(): void
{
$this->expectException(InvalidConfigurationException::class);
$this->expectExceptionMessage("Must be one of: integer, double, string, boolean, NULL, array, object, resource");
$expectedMsg = 'Must be one of: integer, double, string, boolean, NULL, array, object, resource';
$this->expectExceptionMessage($expectedMsg);
new GdprProcessor([], [], [], null, 100, ['invalid_type' => MaskConstants::MASK_MASKED]);
}
@@ -408,8 +410,12 @@ class GdprProcessorValidationTest extends TestCase
#[Test]
public function constructorHandlesComplexValidRegexPatterns(): void
{
// Complex IP address pattern (IPv4 octet validation)
$ipPattern = '/(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}'
. '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/';
$complexPatterns = [
'/(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/' => MaskConstants::MASK_IP,
$ipPattern => MaskConstants::MASK_IP,
TestConstants::PATTERN_EMAIL_FULL => MaskConstants::MASK_EMAIL,
'/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/' => MaskConstants::MASK_CARD
];