This document captures critical lessons learned from resolving 31 failing tests after a major controller refactoring, ensuring future development maintains code quality standards.
After implementing Task 10 (lesson management CRUD), the test suite had 31 failures due to:
- Laravel 12 service registration issues
- Test infrastructure gaps
- DTO validation expectation mismatches
- Controller signature changes from refactoring
Key Insight: Major refactoring work should include immediate test validation to catch breaking changes early.
Critical Discovery (June 19, 2025): Task 10.2 was marked "done" but lesson creation UI was never actually implemented, requiring complete implementation from scratch in Task 21.3. This represents a fundamental failure in task completion verification.
Mistake: Attempted to use withAliases()
method which doesn't exist in Laravel 12.
Solution:
- Register services as singletons in
bootstrap/app.php
- Add facade aliases in
AppServiceProvider
usingAliasLoader
- Prefer dependency injection over facades in Action classes
// bootstrap/app.php
->withSingletons([
'log.service' => \App\Services\LogService::class,
])
// AppServiceProvider.php
public function boot(): void
{
$loader = \Illuminate\Foundation\AliasLoader::getInstance();
$loader->alias('LogService', \App\Facades\LogService::class);
}
Lesson: Always verify Laravel version-specific patterns before implementation.
Mistake: Assumed unit tests would automatically bootstrap Laravel application.
Solution: Created proper test infrastructure:
CreatesApplication
trait for consistent app bootstrapping- Updated
TestCase
to use the trait - Ensured all tests have access to Laravel services
Lesson: Unit tests require explicit Laravel application bootstrapping.
Mistake: Expected Laravel's ValidationException
instead of Spatie Laravel Data exceptions.
Solution: Updated test expectations to match actual library behavior:
CannotCreateData
for missing required parametersCannotCastEnum
for invalid enum values- Focused tests on successful DTO creation rather than internal validation
Lesson: Test against actual library behavior, not assumptions about what exceptions should be thrown.
Mistake: Tests still used old UpdateLessonData
constructor with id:
parameter after refactoring to remove it.
Solution:
- Updated all test constructor calls to match new DTO signature
- Changed
execute($data)
calls toexecute($lesson, $data)
pattern - Ensured tests match actual implementation
Lesson: When refactoring DTOs or Action signatures, immediately update corresponding tests.
Mistake: ReorderLessonsData
lacked proper validation rules for array elements.
Solution: Added comprehensive validation:
public static function rules(): array
{
return [
'module_id' => ['required', 'integer', 'exists:modules,id'],
'lesson_ids' => ['present', 'array'],
'lesson_ids.*' => ['integer'],
];
}
Lesson: DTOs should have complete validation rules, including array element validation.
Mistake: Relying on controller request validation instead of leveraging Spatie Laravel Data's built-in validation capabilities.
Problem Identified: The architecture uses DTOs for data transfer but validation was happening at the controller/request level, missing the opportunity for centralized, reusable validation logic.
Solution:
- Move validation logic into DTO
rules()
methods - Use DTO validation attributes (
#[Required]
,#[Exists]
, etc.) - Let DTOs handle their own validation concerns
- Controllers become thinner and focused on orchestration
// Good: DTO handles its own validation
class CreateLessonData extends Data
{
public function __construct(
#[Required, Exists('modules', 'id')]
public int $module_id,
#[Required, Rule(['string', 'max:255'])]
public string $title,
#[Rule(['nullable', 'string', 'max:1000'])]
public ?string $description = null,
) {}
public static function rules(): array
{
return [
'module_id' => ['required', 'integer', 'exists:modules,id'],
'title' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string', 'max:1000'],
];
}
}
Lesson: DTOs should be self-validating. Move validation logic from controllers/requests into DTOs for better encapsulation and reusability.
Critical Oversight: PHPStan was not run during the development process, missing type safety issues that could have been caught early.
Problems This Caused:
- Type mismatches in method signatures
- Incorrect property types in DTOs
- Missing return type declarations
- Unused imports and variables
- Potential runtime errors from type issues
Solution: Integrate PHPStan into regular development workflow:
# Should be run after every significant change
composer phpstan
# Level 8 analysis for strict type checking
./vendor/bin/phpstan analyse --level=8
What We Missed:
- DTO constructor parameter types could have been validated
- Action method signatures could have been verified
- Service injection types could have been checked
- Return type consistency could have been enforced
Lesson: Static analysis is not optional. PHPStan should be run as frequently as tests to catch type-related issues before they become runtime problems.
Always run both tests and static analysis after changes:
# Essential quality checks
composer test # Run full test suite
composer phpstan # Run static analysis
Never proceed with new features if either tests fail or PHPStan reports errors.
Prefer dependency injection over facades:
// Good: Constructor injection
public function __construct(private LogService $logger) {}
// Avoid: Facade usage in Actions
App\Facades\LogService::info();
- Constructor parameters match use cases
- Validation rules cover all scenarios (in DTO, not controller)
- Array validation includes element rules
- Validation attributes used appropriately (
#[Required]
,#[Exists]
, etc.) - DTOs are self-validating with
rules()
method - Tests cover both success and failure cases
- Exception types match library behavior
- PHPStan passes with no type errors
- Use
withSingletons()
in bootstrap/app.php for service registration - Register facade aliases in AppServiceProvider with AliasLoader
- Test infrastructure requires explicit app bootstrapping
- Always use dependency injection for services
- Follow execute() → handle() pattern with logging
- Update tests when changing method signatures
- Log all business operations with context
- Document current test status
- Identify tests that will be affected
- Plan test updates alongside code changes
- Run full test suite immediately (
composer test
) - Run static analysis (
composer phpstan
) - Fix failing tests and type errors before new development
- Verify no architectural regressions
- Update validation rules completely (in DTO, not controller)
- Use appropriate validation attributes in constructor
- Update test expectations immediately
- Verify exception types match library behavior
- Run PHPStan to verify type safety
- Verify Laravel version compatibility
- Update registration patterns appropriately
- Test service resolution in various contexts
Always use --all
flag when committing:
# Preferred: Stage all changes including untracked files
git add --all && git commit -m "commit message"
# Alternative: Commit all tracked changes directly
git commit --all -m "commit message"
# Never use selective staging unless specifically required
# git add specific-file.txt # Avoid this pattern
Why --all
is important:
- Ensures all related changes are included in the commit
- Prevents leaving task management updates (.taskmaster/tasks/tasks.json) uncommitted
- Includes build artifacts and generated files (public/build/manifest.json)
- Maintains complete history of all project state changes
- Avoids partial commits that break the development workflow
Commit Message Format: Follow the established pattern with detailed technical information and Claude Code attribution.
# Run full test suite
composer test
# Run specific test file
./vendor/bin/pest tests/Feature/SpecificTest.php
# Run tests with coverage
composer test -- --coverage
# Static analysis
composer phpstan
# Run single test with verbose output
./vendor/bin/pest tests/Feature/TestFile.php::test_name --verbose
# Debug with dd() or dump() in tests
# Use Mockery::mock() for service mocking
- Never commit failing tests or PHPStan errors
- Run both tests and static analysis after every significant change
- Fix broken tests and type errors immediately, don't accumulate technical debt
- Treat PHPStan level 8 as mandatory, not optional
- Update CLAUDE.md when establishing new patterns
- Document service registration changes
- Keep architectural decisions visible
- Test small changes frequently
- Don't batch multiple refactoring changes
- Validate assumptions about library behavior
- Laravel 12 has different service registration patterns - verify before implementing
- Test infrastructure is not automatic - explicit bootstrapping required
- Library exceptions differ from expectations - test against actual behavior
- Refactoring breaks tests predictably - plan test updates alongside code changes
- Quality gates prevent technical debt - never skip test validation or static analysis
- DTOs should handle their own validation - move validation from controllers to DTOs
- PHPStan is not optional - type safety is as important as functional correctness
- Validation belongs in DTOs, not requests - leverage Spatie Laravel Data capabilities
- Task documentation is mandatory - always update tasks with implementation summaries before marking as done
- CRITICAL: Task completion verification is mandatory - never mark tasks "done" without end-to-end functional verification
- Incomplete implementations create cascading failures - missing UI components cause confusing "SQL errors" downstream
Date Discovered: June 19, 2025
Context: While working on Task 21.3 (Fix Lesson Creation SQL Errors)
What Happened:
- Task 10.2 ("Implement admin UI components for lesson management") was marked as "done"
- The task description explicitly stated: "Create forms for adding/editing lessons with dynamic fields based on content type selection"
- However, NO lesson creation form, controller, or route was actually implemented
- This was only discovered when fixing SQL errors caused by missing routes
- Required complete implementation from scratch: CreateLessonPageController, routes, LessonForm component, and Create page
Impact:
- Task 21.3 became significantly more complex than expected
- "SQL errors" were actually missing fundamental UI infrastructure
- Wasted development time debugging what appeared to be SQL issues
- False confidence in system completeness
Root Cause Analysis:
- No End-to-End Verification: Task was marked done without verifying the UI actually worked
- Missing Integration Testing: Tests may have passed but actual user flow was broken
- Incomplete Task Validation: No verification that all stated deliverables were actually delivered
- Premature Task Closure: Moved to next tasks without confirming current task functionality
Before Marking ANY Task as "Done":
- Functional Verification: Manually test the complete user workflow end-to-end
- Route Verification: Ensure all expected routes are registered and accessible
- UI Completeness Check: Verify all mentioned UI components actually exist and function
- Integration Testing: Test the full stack from frontend to backend
- Documentation Verification: Confirm all stated deliverables in task description are actually delivered
Task Completion Checklist:
- All backend endpoints work and return expected responses
- All frontend components exist and render correctly
- All routes are properly registered and accessible
- User workflows function end-to-end without errors
- All tests pass (unit, feature, integration)
- Static analysis passes (PHPStan)
- Frontend builds successfully (TypeScript compilation)
- Manual testing of primary user paths completed
- Implementation summary documents what was actually built (not what was planned)
Never Mark Complete Unless:
- You can personally use the feature from the UI
- All error scenarios are handled gracefully
- The feature integrates properly with existing functionality
- No placeholder or TODO code remains
New Guideline: Before marking any task or subtask as "done", you MUST update it with a comprehensive implementation summary.
Why This Matters:
- Provides complete traceability of what was actually implemented
- Documents decisions made during implementation that may differ from original plan
- Creates valuable reference for future debugging or enhancement work
- Ensures knowledge transfer for team members
- Maintains project history and lessons learned
Required Implementation Summary Elements:
- Problem Statement: Clear description of what issue was being solved
- Solution Approach: High-level overview of the implementation strategy
- Files Modified: List of all files changed with brief description of changes
- Files Created: List of new files created with their purpose
- Technical Details: Key implementation decisions, patterns used, dependencies
- Quality Verification: Test results, static analysis status, any issues resolved
- Commit Reference: Git commit hash for traceability
Example Implementation Summary Format:
## Implementation Summary
### Problem
Brief description of the issue being solved.
### Solution Implemented
1. **Component/File 1**: Description of changes made
2. **Component/File 2**: Description of changes made
### Files Modified
- path/to/file1.php (description of changes)
- path/to/file2.tsx (description of changes)
### Files Created
- path/to/newfile.tsx (purpose and functionality)
### Quality Assurance
- Tests: X passing, Y failing (if any failures, explain)
- Static Analysis: Clean/Issues resolved
- Following project patterns: Yes/No with details
### Commit
Reference to git commit with detailed message.
Process:
- Complete the implementation work
- Run quality checks (
composer test
andcomposer phpstan
) - Update the task/subtask with implementation summary using
update_task
orupdate_subtask
- Only then mark the task as "done"
- Create commit with detailed message using
git add --all
orgit commit --all
This guideline applies to:
- All main tasks when fully completed
- All subtasks when individually completed
- Any significant implementation work tracked in Task Master
When working on this codebase:
- Check this document before major changes
- Follow established patterns for service registration
- Maintain test quality as a non-negotiable standard
- Use dependency injection over facades in Action classes
- Remember Laravel 12-specific requirements
- ALWAYS run
composer phpstan
alongsidecomposer test
- Validate in DTOs, not controllers/requests
- Type safety is mandatory, not optional
- Document implementation summaries before marking tasks done
- Always use
git add --all
orgit commit --all
when committing to include all changes - CRITICAL: Verify end-to-end functionality before marking tasks complete
- Test user workflows manually, not just unit/feature tests
Date: June 19, 2025
Context: Task 17 implementation proceeded without TDD despite explicit requirements
What Happened: Even with comprehensive documentation (LESSONS_LEARNED.md, CLAUDE.md) explicitly stating "TDD Required" and multiple quality gate requirements, development proceeded with implementation-first approach, completely ignoring established processes. Entire backend infrastructure (DTOs, Actions, Controllers, Routes, Policies) was implemented without writing a single test.
Impact:
- Zero confidence in actual functionality (tests weren't written to verify it works)
- Violation of fundamental project development principles
- Incompatible with autonomous development goals
- Sets dangerous precedent for future development
- Documentation Awareness vs. Adherence: Reading documentation but treating requirements as suggestions rather than immutable laws
- Implementation Momentum: Getting caught up in "technical flow" while ignoring process requirements
- Quality Gate Bypassing: Assuming existing passing tests validate new functionality without writing specific tests
- Process Prioritization Failure: Prioritizing feature completion speed over established workflow adherence
For independent Claude Code operation, these are NON-NEGOTIABLE:
- LESSONS_LEARNED.md and CLAUDE.md requirements are immutable laws, not suggestions
- Every requirement must be followed exactly, no exceptions
- "TDD Required" means tests MUST be written before any implementation
- Quality gates are blocking - never proceed if tests fail or PHPStan reports errors
- Write tests BEFORE any implementation, no exceptions
- No "I'll add tests later" mentality
- Test each component (DTO, Action, Controller) as it's being developed
- Verify functionality through tests, not assumptions
- Following established workflow is more important than feature completion speed
- Never skip established patterns for convenience
- Each step must be completed properly before moving to the next
- Quality and adherence take precedence over development velocity
- Actively reference project documentation before starting any work
- Question any impulse to skip established processes
- Treat process violations as critical failures requiring immediate correction
- Document and learn from any process deviations
Before Starting Any Task:
- Re-read relevant sections of LESSONS_LEARNED.md and CLAUDE.md
- Confirm understanding of TDD requirements
- Plan test-first approach before any implementation
- Identify quality gates that must pass before proceeding
During Development:
- Write tests before implementation for every component
- Run quality checks (
composer test
andcomposer phpstan
) after every change - Verify end-to-end functionality before marking anything complete
- Follow established patterns exactly without deviation
Task Completion:
- All tests passing (including new tests for new functionality)
- PHPStan clean with no errors
- End-to-end functional verification completed
- Implementation summary documented
- Only then mark task as complete
Autonomous development requires religious adherence to documented processes. Any deviation from established workflows indicates the system is not ready for independent operation.
Bottom Line: Both test suite health AND type safety are critical quality indicators. Task documentation ensures knowledge transfer and project continuity. Most critically: tasks must be functionally verified end-to-end before marking complete. Never compromise on quality, documentation, or functional verification. For autonomous operation: process adherence is not optional - it's the foundation of reliable, independent development.