Duplicate DTO Detection
The Duplicate DTO Checker automatically scans test files for duplicate DTO class names before running tests, preventing cryptic PHP errors with clear, actionable error messages.
Why This Matters
Section titled “Why This Matters”When multiple test files define DTOs with the same class name, PHP throws a fatal error:
Fatal error: Cannot declare class UserDto, because the name is already in useThis error:
- Stops all tests from running
- Doesn’t show which files have the conflict
- Wastes time debugging
The Duplicate DTO Checker solves this by detecting conflicts before tests run and showing exactly which files need to be fixed.
How It Works
Section titled “How It Works”The checker runs automatically when you execute tests:
- Scans all PHP files in the
tests/directory - Finds all classes extending
SimpleDtoorLiteDto - Checks for duplicate class names (considering namespaces)
- Reports any duplicates with file locations
- Exits with an error if duplicates are found
Example Output
Section titled “Example Output”When duplicates are detected, you’ll see:
⚠️ DUPLICATE DTO CLASSES FOUND!
The following DTO classes are defined in multiple test files:
📦 UserDto: - tests/Unit/SimpleDto/SimpleDtoTest.php - tests/Integration/Symfony/DtoValueResolverTest.php - tests/Integration/Laravel/DtoValueResolverTest.php
📦 AddressDto: - tests/Unit/SimpleDto/DotNotationAccessTest.php - tests/Unit/LiteDto/DotNotationAccessTest.php
⚠️ This can cause test failures with unclear error messages.Please rename the DTOs to make them unique (e.g., SimpleDtoUserDto, SymfonyUserDto, etc.)
To disable this check, set the environment variable: SKIP_DUPLICATE_DTO_CHECK=1Configuration
Section titled “Configuration”Disabling the Check
Section titled “Disabling the Check”Temporarily disable the check during development:
SKIP_DUPLICATE_DTO_CHECK=1 vendor/bin/pestOr in your shell:
export SKIP_DUPLICATE_DTO_CHECK=1vendor/bin/pestExcluded Directories
Section titled “Excluded Directories”The checker automatically excludes:
- Fixtures directories: DTOs in
tests/*/Fixtures/can have duplicate names - Helper files:
DtoTestHelper.phpandDuplicateDtoChecker.phpare excluded - Test files:
DuplicateDtoCheckerTest.phpis excluded
Namespace Handling
Section titled “Namespace Handling”The checker considers namespaces when detecting duplicates:
✅ No Conflict (Different Namespaces)
Section titled “✅ No Conflict (Different Namespaces)”namespace Tests\Unit\SimpleDto;class UserDto extends SimpleDto { ... }
// tests/Integration/Symfony/DtoValueResolverTest.phpnamespace Tests\Integration\Symfony;class UserDto extends SimpleDto { ... }These are different classes because they have different namespaces.
❌ Conflict (Same Namespace)
Section titled “❌ Conflict (Same Namespace)”namespace Tests\Unit;class UserDto extends SimpleDto { ... }
// tests/Unit/LiteDto/LiteDtoTest.phpnamespace Tests\Unit;class UserDto extends SimpleDto { ... }These are duplicate classes because they have the same namespace.
❌ Conflict (No Namespace)
Section titled “❌ Conflict (No Namespace)”class UserDto extends SimpleDto { ... }
// tests/Integration/Symfony/DtoValueResolverTest.phpclass UserDto extends SimpleDto { ... }These are duplicate classes because neither has a namespace.
Fixing Duplicates
Section titled “Fixing Duplicates”Option 1: Use Descriptive Prefixes
Section titled “Option 1: Use Descriptive Prefixes”Add context-specific prefixes to make names unique:
class SimpleDtoUserDto extends SimpleDto { ... }
// tests/Integration/Symfony/DtoValueResolverTest.phpclass SymfonyUserDto extends SimpleDto { ... }
// tests/Integration/Laravel/DtoValueResolverTest.phpclass LaravelUserDto extends SimpleDto { ... }Option 2: Use Namespaces
Section titled “Option 2: Use Namespaces”Put DTOs in different namespaces:
namespace Tests\Unit\SimpleDto;class UserDto extends SimpleDto { ... }
// tests/Integration/Symfony/DtoValueResolverTest.phpnamespace Tests\Integration\Symfony;class UserDto extends SimpleDto { ... }Option 3: Use Fixtures
Section titled “Option 3: Use Fixtures”If a DTO is used across multiple tests, move it to a Fixtures directory:
namespace Tests\Unit\Fixtures;class UserDto extends SimpleDto { ... }
// tests/Unit/SimpleDto/SimpleDtoTest.phpuse Tests\Unit\Fixtures\UserDto;test('creates user dto', function() { $dto = new UserDto(...);});
// tests/Unit/LiteDto/LiteDtoTest.phpuse Tests\Unit\Fixtures\UserDto;test('converts to lite dto', function() { $dto = new UserDto(...);});Best Practices
Section titled “Best Practices”1. Use Descriptive Names
Section titled “1. Use Descriptive Names”Make DTO names specific to their test context:
// ✅ Good - Clear contextclass SimpleDtoValidationUserDto extends SimpleDto { ... }class SymfonyControllerUserDto extends SimpleDto { ... }class LaravelRequestUserDto extends SimpleDto { ... }
// ❌ Bad - Generic namesclass UserDto extends SimpleDto { ... }class TestDto extends SimpleDto { ... }2. Use Namespaces for Organization
Section titled “2. Use Namespaces for Organization”Group related DTOs in namespaces:
namespace Tests\Unit\SimpleDto\Validation;class UserDto extends SimpleDto { ... }
// tests/Unit/SimpleDto/Casting/CastingTest.phpnamespace Tests\Unit\SimpleDto\Casting;class UserDto extends SimpleDto { ... }3. Share Common DTOs via Fixtures
Section titled “3. Share Common DTOs via Fixtures”Avoid duplication by using Fixtures:
namespace Tests\Fixtures\Dtos;class UserDto extends SimpleDto { ... }
// Use in multiple testsuse Tests\Fixtures\Dtos\UserDto;Manual Usage
Section titled “Manual Usage”You can also run the checker manually in your code:
use Tests\Unit\Helpers\DuplicateDtoChecker;
// Check and throw exception on duplicatesDuplicateDtoChecker::check(__DIR__ . '/tests');
// Check and only print warning (don't throw)$duplicates = DuplicateDtoChecker::check(__DIR__ . '/tests', false);
if (!empty($duplicates)) { foreach ($duplicates as $className => $files) { echo "Duplicate: $className\n"; foreach ($files as $file) { echo " - $file\n"; } }}Implementation Details
Section titled “Implementation Details”The checker uses:
- RecursiveDirectoryIterator: Scans all PHP files recursively
- Regular expressions: Finds class definitions and namespaces
- Static analysis: No classes are loaded or instantiated
This makes it fast and safe to run before tests.
Performance
Section titled “Performance”The checker is optimized for speed:
- Scans ~100 test files in less than 50ms
- No impact on test execution time
- Runs once at startup, not per test
See Also
Section titled “See Also”- Testing DTOs - Complete guide to testing DTOs
- Creating DTOs - DTO creation guide
- Best Practices - DTO best practices