chore: consolidate documentation
This commit is contained in:
@@ -1,498 +0,0 @@
|
|||||||
# MyEP Next Booking - Project Overview
|
|
||||||
|
|
||||||
**Symfony 6.4 travel booking application** integrating with Bus Pro Net (BPN) XML API.
|
|
||||||
|
|
||||||
## Core Architecture
|
|
||||||
|
|
||||||
### Multi-Step Booking Flow
|
|
||||||
1. **Step 1**: Room selection and dates (`Create\Step1Controller`)
|
|
||||||
2. **Step 2**: Participant details with card-based UI (`Create\Step2Controller`)
|
|
||||||
3. **Step 3**: Payment method selection (`Create\Step3Controller`)
|
|
||||||
4. **Step 4**: Final confirmation with comprehensive pricing breakdown and submission to BPN API (`Create\Step4Controller`)
|
|
||||||
- Displays complete pricing breakdown by category (rooms, services grouped by type)
|
|
||||||
- Shows per-participant total pricing in card headers
|
|
||||||
- Displays individual service prices inline with each booked service
|
|
||||||
- Three-level pricing transparency: aggregate, participant, and service-level
|
|
||||||
5. **Edit Flow**: Similar card-based UI for existing bookings (`Edit\IndexController`)
|
|
||||||
|
|
||||||
### Card-Based UI Pattern (Production)
|
|
||||||
- **Overview**: Grid of participant cards with lazy-loaded individual forms
|
|
||||||
- **Performance**: Handles 50+ participants efficiently via HTMX
|
|
||||||
- **Controllers**:
|
|
||||||
- `Create\Step2Controller` - create flow with validation before step 3
|
|
||||||
- `Edit\IndexController` - edit flow with validation before API submission
|
|
||||||
- **Shared Logic**:
|
|
||||||
- `ParticipantCardFlowTrait` - Card rendering and form handling
|
|
||||||
- `ParticipantCardDataService` - Card data generation with optional validation state
|
|
||||||
- **Validation Pattern**:
|
|
||||||
- Both controllers use validation-only forms (`BookingCreateStep2Type`, `BookingEditType`)
|
|
||||||
- Standard Symfony form flow: `handleRequest()` → `isSubmitted()` → `isValid()`
|
|
||||||
- On invalid: Cards show red border with "unvollständige oder fehlerhafte Daten" badge
|
|
||||||
- Validation state determined via `ParticipantCardDataService::getAllCardsDataWithValidation()`
|
|
||||||
- On valid: Proceed to next step (create) or submit to API (edit)
|
|
||||||
|
|
||||||
## Key Architectural Layers
|
|
||||||
|
|
||||||
### BusProNet Integration (`src/BusProNet/`)
|
|
||||||
- `ApiClient` - XML API communication
|
|
||||||
- `XmlParser/` - Response parsers (travels, hotels, bookings)
|
|
||||||
- `XmlLoader/` - Data loaders with caching
|
|
||||||
- `DataProcessor/` - Transform API data to DTOs
|
|
||||||
|
|
||||||
### Form System (`src/Form/`)
|
|
||||||
- **DTOs**: `BookingCreateDto`, `ParticipantDto`, `ParticipantEditDto` (session-stored)
|
|
||||||
- `ParticipantEditDto` wrapper enables cross-participant validation (e.g., email uniqueness)
|
|
||||||
- Wrapper contains participant being edited + full booking context for validation
|
|
||||||
- **Field Handlers**: 15+ specialized handlers in `src/Form/Service/`
|
|
||||||
- Registered via service tags with dependency resolution
|
|
||||||
- Process in dependency order via `ParticipantFieldHandlerRegistry`
|
|
||||||
- `ParticipantDateOfBirthFieldHandler` handles array input from BirthdayType widget
|
|
||||||
- **Conditional Fields**: Universal condition system (`FieldConditionInterface`)
|
|
||||||
- Age-based, field-dependent, service-specific conditions
|
|
||||||
- Applied via `CreateFieldStateProvider` / `EditFieldStateProvider`
|
|
||||||
- **HTMX Integration**: Real-time updates for dynamic fields
|
|
||||||
- **Email Uniqueness Validation**: Adult participants (16+) must have unique email addresses
|
|
||||||
- Children (under 16) exempt from validation, can share emails with adults
|
|
||||||
- Implemented via `ParticipantEditDto::validateEmailUniqueness()` callback validator
|
|
||||||
- Case-insensitive comparison with whitespace normalization
|
|
||||||
- Validation groups: `booking_create`, `booking_edit`
|
|
||||||
- **Date of Birth Field**: Uses `BirthdayType` with three text inputs (day, month, year)
|
|
||||||
- Widget: `widget: 'text'` renders separate text fields instead of dropdowns
|
|
||||||
- Input format: `input: 'datetime_immutable'` for immutable date handling
|
|
||||||
- Custom theme: `birthday_widget` block in `templates/forms.html.twig` handles rendering
|
|
||||||
- German format: Day.Month.Year with hardcoded placeholders ("Tag", "Monat", "Jahr")
|
|
||||||
- Field order explicitly set to `['day', 'month', 'year']` for German date convention
|
|
||||||
- HTMX attributes applied to parent container for form refresh on change
|
|
||||||
|
|
||||||
### Service Layer (`src/Service/`)
|
|
||||||
- `BookingService` - Core booking workflow
|
|
||||||
- `BookingPriceCalculatorService` - Comprehensive pricing calculations
|
|
||||||
- `getPricingBreakdown()` - Complete pricing breakdown with rooms and services grouped by type
|
|
||||||
- `calculateAllParticipantIndividualPrices()` - Per-participant total pricing
|
|
||||||
- `calculateIndividualParticipantPrice()` - Single participant total (room + all services)
|
|
||||||
- `calculateServicePricing()` - Service aggregation with grouping by subtype
|
|
||||||
- Powers sidebar summary and Step 4 confirmation pricing display
|
|
||||||
- `BookingFingerprintService` - Dirty state detection for edit mode
|
|
||||||
- `TravelDataService` - API integration and caching
|
|
||||||
- `ParticipantCardDataService` - Card display data with optional validation state
|
|
||||||
- `getAllCardsData()` - Basic card data without validation
|
|
||||||
- `getAllCardsDataWithValidation()` - Card data enriched with validation state for error display
|
|
||||||
- `InsuranceService` - Consolidated insurance operations (eligibility, type filtering, reassignment) with request-scoped caching
|
|
||||||
- `RoomAssignmentService` - Automatic room assignment with intelligent conflict resolution
|
|
||||||
|
|
||||||
### Room Reassignment with Conflict Resolution
|
|
||||||
The room assignment system allows participants to freely select any room from Step 1 selections, with automatic conflict resolution when capacity is exceeded.
|
|
||||||
|
|
||||||
**Key Features:**
|
|
||||||
- **Flexible Selection**: All rooms from Step 1 always appear in participant dropdown, regardless of current capacity
|
|
||||||
- **Auto-Assignment**: When exactly ONE room type selected, all participants auto-assigned via `RoomAssignmentService`
|
|
||||||
- **Field Display**: Single room type hides dropdown field entirely, displays room label as read-only text
|
|
||||||
- **Conflict Resolution**: When participant selects room at capacity, system automatically unassigns minimum participants needed
|
|
||||||
- **Unassignment Priority**: Highest index participants unassigned first (keeps applicant and early participants stable)
|
|
||||||
- **User Notifications**: Unassigned participants receive warning notifications via toast system
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
- `ParticipantRoomChoiceLoader`: Always includes all selected rooms (no capacity filtering)
|
|
||||||
- `ParticipantAssignedRoomFieldHandler`: Detects conflicts and resolves via auto-unassignment
|
|
||||||
- `detectRoomCapacityConflict()`: Calculates if assignment would exceed capacity
|
|
||||||
- `resolveRoomCapacityConflict()`: Unassigns minimum participants (highest index first)
|
|
||||||
- Generates notifications for unassigned participants only (not for user-initiated assignment)
|
|
||||||
- Handles missing field gracefully when single room type (field excluded from form)
|
|
||||||
- `RoomAssignmentService`: Auto-assigns when single room type selected at DTO level
|
|
||||||
- `CreateFieldStateProvider`: Hides `assignedRoomId` field when `SingleRoomTypeCondition` is true
|
|
||||||
- `BookingDto::getSingleRoomLabel()`: Returns room label for template display when single room type
|
|
||||||
- Template: Conditionally renders dropdown (multiple rooms) or read-only label (single room)
|
|
||||||
|
|
||||||
**Example Scenario:**
|
|
||||||
- User selects 1x "Doppelzimmer" (capacity 2), 3 participants
|
|
||||||
- Participants 0 and 1 auto-assigned to "Doppelzimmer"
|
|
||||||
- Participant 2 manually selects "Doppelzimmer" (at capacity)
|
|
||||||
- System automatically unassigns Participant 1 (highest index)
|
|
||||||
- Participant 2 gets assigned, Participant 1 receives warning notification
|
|
||||||
|
|
||||||
## Critical Patterns
|
|
||||||
|
|
||||||
### Field Handler Pattern
|
|
||||||
```php
|
|
||||||
// Handlers process in dependency order (topological sort)
|
|
||||||
// Example: insurance handler depends on ALL price-affecting fields
|
|
||||||
$this->fieldHandlerRegistry->processFieldsForParticipant(
|
|
||||||
$participantData,
|
|
||||||
$bookingDto,
|
|
||||||
$index
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Important**: Field handlers use "sync pattern" - only sync fields present in original submission to avoid validation errors.
|
|
||||||
|
|
||||||
### HTMX Block-Based Rendering
|
|
||||||
```php
|
|
||||||
// All HTMX swaps target #main-content with innerHTML
|
|
||||||
// OOB swaps for sidebar: #booking-summary
|
|
||||||
return $this->htmxOobResponse(
|
|
||||||
'booking/_participant_form.html.twig',
|
|
||||||
['participant_form', 'booking_summary'],
|
|
||||||
$templateData
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Service Enrichment Pattern
|
|
||||||
Services from BPN API may lack complete data (especially prices). Always enrich from travel data:
|
|
||||||
```php
|
|
||||||
// BookingDataProcessor::enrichParticipantServicesFromTravel()
|
|
||||||
// Looks up each service in travel data and replaces with full version
|
|
||||||
```
|
|
||||||
|
|
||||||
### Notification System
|
|
||||||
Field handlers generate notifications (auto-changes) → collected by controller → sent via HX-Trigger → displayed as toasts.
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
- `ParticipantDto::$notifications` - Array of notification messages (keyed by MD5 hash for deduplication)
|
|
||||||
- `ParticipantDto::addNotification($type, $message, $id = null)` - Add notification with auto-generated or custom ID
|
|
||||||
- Field handlers call `addNotification()` when making automatic changes (e.g., insurance reassignment, rental clearing)
|
|
||||||
- Controllers collect notifications via `collectParticipantNotifications()` after field processing
|
|
||||||
- Notifications sent to frontend via `HX-Trigger` response header with `showNotifications` event
|
|
||||||
- Frontend `toast_controller.js` displays notifications using toastify-js library
|
|
||||||
- Notification types: `info`, `warning`, `success` (mapped to Tailwind color classes)
|
|
||||||
|
|
||||||
### Floating-Point Precision in Price Calculations
|
|
||||||
**Critical**: All monetary comparisons must account for floating-point arithmetic accumulation errors.
|
|
||||||
|
|
||||||
**The Problem:**
|
|
||||||
- Prices are parsed from XML as German-formatted strings (e.g., `"1.812,70"`)
|
|
||||||
- Multiple price additions (rooms + services + insurances) accumulate tiny precision errors (`~1e-15` per operation)
|
|
||||||
- With bulk insurance and deep calculation chains, errors compound to `~1e-13` or larger
|
|
||||||
- Example: API returns `1812.7`, calculation produces `1812.6999999999998`
|
|
||||||
|
|
||||||
**The Solution:**
|
|
||||||
- **Always round monetary values to 2 decimal places before comparison**
|
|
||||||
- Use `round($price, 2)` for cent precision (standard for EUR currency)
|
|
||||||
- Never use strict equality (`===` or `!==`) on unrounded float prices
|
|
||||||
|
|
||||||
**Implementation Example (Step3Controller:93-94):**
|
|
||||||
```php
|
|
||||||
// CORRECT: Round to cent precision before comparison
|
|
||||||
$apiTotal = round($inquiryResponse->totalPrice ?? 0.0, 2);
|
|
||||||
$calculatedTotal = round($this->priceCalculator->calculateGrandTotal($bookingCreateDto), 2);
|
|
||||||
|
|
||||||
if ($apiTotal !== $calculatedTotal) {
|
|
||||||
// Handle mismatch
|
|
||||||
}
|
|
||||||
|
|
||||||
// WRONG: Direct float comparison (will fail due to precision errors)
|
|
||||||
if ($inquiryResponse->totalPrice !== $this->priceCalculator->calculateGrandTotal($bookingCreateDto)) {
|
|
||||||
// This comparison is unreliable!
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Important Notes:**
|
|
||||||
- Rounding eliminates precision errors smaller than 1 cent (€0.01)
|
|
||||||
- The BPN API returns prices already rounded to cent precision
|
|
||||||
- This is the industry-standard approach for financial calculations
|
|
||||||
- Only affects comparison logic - does not alter actual price calculation flow
|
|
||||||
|
|
||||||
### Dirty State Detection (Edit Mode)
|
|
||||||
Fingerprint-based change detection to warn users about unsaved modifications:
|
|
||||||
- **BookingFingerprintService** generates SHA-256 hash of all mutable booking data
|
|
||||||
- Original fingerprint stored in `BookingDto::$originalFingerprint` on API load
|
|
||||||
- `isDirty()` compares current state with original to detect changes
|
|
||||||
- Yellow warning banner displays when changes detected
|
|
||||||
- Update button conditionally shown only when dirty
|
|
||||||
- Fingerprint persists in session, survives page refreshes
|
|
||||||
- Resets on submission, "Änderungen verwerfen", or "Zurück" actions
|
|
||||||
|
|
||||||
**Unavailable Services Handling:**
|
|
||||||
- Services with `available <= 0` included in edit mode (not filtered out)
|
|
||||||
- `ParticipantFieldOptionsProvider::shouldMakeServiceReadonly()` implements intelligent readonly logic:
|
|
||||||
- **Create mode**: Uses standard availability calculator (filters out unavailable services)
|
|
||||||
- **Edit mode**: Services participants already have remain editable even if now fully booked
|
|
||||||
- **Edit mode**: Unavailable services participant doesn't have are marked readonly
|
|
||||||
- Prevents fingerprint false positives when services become fully booked during editing session
|
|
||||||
- Ensures service IDs remain consistent in form submissions for accurate dirty detection
|
|
||||||
|
|
||||||
## Key Service Dependencies
|
|
||||||
|
|
||||||
### Field Handler Execution Order
|
|
||||||
Critical for correct pricing and auto-reassignment:
|
|
||||||
1. Age-dependent fields (dateOfBirth)
|
|
||||||
2. Price-affecting services (skiPass, rentals, courses, board, transportation, pickup, parking)
|
|
||||||
3. **Insurance handler LAST** (depends on all price-affecting fields)
|
|
||||||
4. Bulk insurance handler (applicant only)
|
|
||||||
|
|
||||||
### Insurance System
|
|
||||||
- **3-pass parsing**: Referenced IDs → Individual insurances → Packages with family detection
|
|
||||||
- **Auto-reassignment**: Maintains insurance type when price tier changes
|
|
||||||
- **Age constraints**: Absolute age (at travel date) vs birth year
|
|
||||||
- **Hydration**: `TravelDataService::hydrateInsurancePackageRelationships()` rebuilds package relationships after cache deserialization
|
|
||||||
- **ID Type**: Insurance IDs are strings (not integers) - ensure all test fixtures use string IDs
|
|
||||||
- **Mutability**: Insurances are always readonly in edit mode (API limitation) - see `InsuranceMutabilityCondition`
|
|
||||||
|
|
||||||
### Transportation Services
|
|
||||||
- **Unified pickup field**: Single field for both directions (BPN API limitation)
|
|
||||||
- **Conditional visibility**: Pickup vs parking fields mutually exclusive
|
|
||||||
- **Direction mapping**: `DirectionMapper` translates API ↔ internal codes
|
|
||||||
- **Discount replacement logic**: Automatic correction of incompatible transportation combinations
|
|
||||||
- When inbound bus selected with discounted PKW outbound: system automatically replaces with regular PKW
|
|
||||||
- When inbound changes from bus to PKW: system automatically restores discounted PKW if available
|
|
||||||
- Form options update dynamically to show only valid PKW variant
|
|
||||||
- User notified via toast messages about automatic changes
|
|
||||||
- Implemented via `ParticipantTransportationDiscountReplacementFieldHandler`
|
|
||||||
- Prevents invalid pricing where user gets both PKW discount and bus pricing
|
|
||||||
|
|
||||||
## Important Field Dependencies
|
|
||||||
|
|
||||||
### Ski Pass → Rentals → Insurance → Body Dimensions
|
|
||||||
- Rentals filtered by ski pass duration (exact date matching)
|
|
||||||
- Rental insurance only shown when rentals selected
|
|
||||||
- Body dimensions only shown when rentals selected
|
|
||||||
- All cleared automatically when dependencies removed
|
|
||||||
|
|
||||||
### Date of Birth → Age-Dependent Services
|
|
||||||
- Courses, additional services, board, insurance hidden until DOB provided
|
|
||||||
- Age evaluated at travel start date, not current date
|
|
||||||
- Dual constraint types: absolute_age, birth_year, mixed
|
|
||||||
- **Array input handling**: `ParticipantDateOfBirthFieldHandler` processes array `['day' => X, 'month' => Y, 'year' => Z]` from three-field widget
|
|
||||||
- **Date normalization**: Handler converts array to `DateTimeImmutable` using `sprintf()` with proper formatting
|
|
||||||
|
|
||||||
### Bulk Insurance Booking
|
|
||||||
- Applicant enables bulk → applies to all participants
|
|
||||||
- `BulkInsuranceBookingCondition` hides dependent participant insurance fields
|
|
||||||
- Uses `InsuranceService::batchAssignInsuranceToParticipants()` for price tier matching
|
|
||||||
|
|
||||||
### Room Assignment Field
|
|
||||||
- **Single room type**: Field completely hidden, room label displayed as read-only text
|
|
||||||
- **Multiple room types**: Dropdown shown with all selected rooms from Step 1
|
|
||||||
- `SingleRoomTypeCondition` controls field visibility in `CreateFieldStateProvider`
|
|
||||||
- Auto-assignment by `RoomAssignmentService` happens at DTO level, independent of form field
|
|
||||||
- Field handler gracefully handles missing field when excluded from form
|
|
||||||
|
|
||||||
## Data Flow
|
|
||||||
|
|
||||||
### Create Flow
|
|
||||||
1. Load/create DTO from session
|
|
||||||
2. Enrich with fresh API availability data
|
|
||||||
3. Auto-assign rooms, preselect mandatory services
|
|
||||||
4. Render cards → user edits participant → field handlers process → save to session
|
|
||||||
5. Validation check before step 3
|
|
||||||
6. Final submission to BPN API
|
|
||||||
|
|
||||||
### Edit Flow
|
|
||||||
1. Load booking from BPN API on first visit
|
|
||||||
2. Generate fingerprint of initial state for dirty detection
|
|
||||||
3. Store in session with `MODE_EDIT`
|
|
||||||
4. Apply mutability constraints via `EditFieldStateProvider`
|
|
||||||
5. Same card-based UI as create flow
|
|
||||||
6. Handle canceled participants (status 'S')
|
|
||||||
7. Display warning banner when unsaved changes detected
|
|
||||||
8. **Validation on submission**: Form wraps cards, validates all participants before API call
|
|
||||||
9. Submit changes back to BPN API (only if validation passes)
|
|
||||||
10. Staleness warnings after 5 minutes
|
|
||||||
11. **Session cleanup**: "Zurück" button clears session via `app_booking_edit_cancel` action
|
|
||||||
|
|
||||||
## Common Development Tasks
|
|
||||||
|
|
||||||
### Adding a New Field Handler
|
|
||||||
1. Create handler class extending `AbstractParticipantFieldHandler`
|
|
||||||
2. Implement `shouldProcess()`, `process()`, `getDependencies()`
|
|
||||||
3. Register in `services.yaml` with `participant.field_handler` tag
|
|
||||||
4. Add field options to `ParticipantFieldOptionsProvider`
|
|
||||||
5. Add conditional logic to `CreateFieldStateProvider` if needed
|
|
||||||
6. Update template with HTMX refresh triggers
|
|
||||||
|
|
||||||
### Adding a New Conditional Field
|
|
||||||
1. Create condition class implementing `FieldConditionInterface`
|
|
||||||
2. Register in `CreateFieldStateProvider::registerFieldStateConditions()`
|
|
||||||
3. Use composite conditions for complex logic (AND/OR/NOT)
|
|
||||||
|
|
||||||
### Debugging Field Handler Issues
|
|
||||||
- Check execution order in `ParticipantFieldHandlerRegistry` (topological sort)
|
|
||||||
- Verify `shouldProcess()` logic for mode awareness
|
|
||||||
- Ensure dependencies declared correctly
|
|
||||||
- Check sync pattern: only sync fields in original submission
|
|
||||||
|
|
||||||
### Adding Cross-Participant Validation
|
|
||||||
1. Add validation method to `ParticipantEditDto` (wrapper DTO)
|
|
||||||
2. Use `#[Assert\Callback]` attribute with appropriate validation groups
|
|
||||||
3. Access booking context via `$this->bookingContext` for cross-participant checks
|
|
||||||
4. Access current participant via `$this->participant`
|
|
||||||
5. Validation errors automatically displayed on participant cards via red border and badge
|
|
||||||
|
|
||||||
### Writing Tests
|
|
||||||
- **Insurance IDs**: Always use strings, not integers (e.g., `'100'` not `100`)
|
|
||||||
- **Room properties**: Use `$label` property, not `$name`
|
|
||||||
- **Participant names**: Index 0 expects "Anmelder:in", others expect "Teilnehmer:in N" (1-based)
|
|
||||||
- **Mock dependencies**: Ensure all constructor dependencies have mocks. Note: `InsuranceService` is stateless with no dependencies (no mocking required)
|
|
||||||
- **Insurance mutability**: In edit mode, insurances are always readonly (API limitation)
|
|
||||||
- **Notification arrays**: Use associative keys (MD5 hashes), not numeric indices - use `reset($notifications)` to get first notification
|
|
||||||
|
|
||||||
## File Locations
|
|
||||||
|
|
||||||
### Key Design Patterns
|
|
||||||
|
|
||||||
**Service Availability in Edit Mode:**
|
|
||||||
Edit mode requires special handling of unavailable services to prevent fingerprint false positives:
|
|
||||||
```php
|
|
||||||
// ParticipantFieldOptionsProvider - intelligent availability filtering
|
|
||||||
$bookingDto->travel->getAdditionalServicesBySubTypes(
|
|
||||||
Constants::TOKEN_COURSES,
|
|
||||||
BookingDto::MODE_CREATE === $bookingDto->getMode() // Only filter in create mode
|
|
||||||
)
|
|
||||||
|
|
||||||
// shouldMakeServiceReadonly() - context-aware readonly logic
|
|
||||||
// In edit mode: service readonly ONLY if unavailable AND participant doesn't have it
|
|
||||||
private function shouldMakeServiceReadonly(Service $service, BookingDto $bookingDto, int $participantIndex, string $fieldName): bool
|
|
||||||
{
|
|
||||||
if (BookingDto::MODE_CREATE === $bookingDto->getMode()) {
|
|
||||||
return $this->isServiceUnavailableForParticipant($service, $bookingDto, $participantIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edit mode: allow keeping services participant already has
|
|
||||||
if (null !== $service->available && $service->available > 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$participantHasService = match ($fieldName) {
|
|
||||||
'courses' => $this->hasServiceById($participant->courses, $service->id),
|
|
||||||
'additionalServices' => $this->hasServiceById($participant->additionalServices, $service->id),
|
|
||||||
// ... other field types
|
|
||||||
};
|
|
||||||
|
|
||||||
return !$participantHasService; // Readonly only if participant doesn't have it
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Session Lifecycle Management:**
|
|
||||||
Edit mode session requires proper cleanup to prevent dirty state persistence:
|
|
||||||
- **Entry**: `IndexController::loadFormData()` initializes from API with original fingerprint
|
|
||||||
- **Exit (save)**: `IndexController::index()` clears session on successful API update
|
|
||||||
- **Exit (discard)**: `IndexController::reloadFromApi()` clears session and reloads from API
|
|
||||||
- **Exit (cancel)**: `IndexController::cancelEdit()` clears session when user clicks "Zurück"
|
|
||||||
- **Validation**: Dirty state persists across page refreshes until explicit action taken
|
|
||||||
|
|
||||||
### Controllers
|
|
||||||
**Create Namespace** (`src/Controller/Booking/Create/`):
|
|
||||||
- `IndexController.php` - Booking session initialization and error handling
|
|
||||||
- `Step1Controller.php` - Room selection and dates
|
|
||||||
- `Step2Controller.php` - Participant details with card-based UI
|
|
||||||
- `Step3Controller.php` - Payment method selection
|
|
||||||
- `Step4Controller.php` - Final confirmation with comprehensive pricing display and API submission
|
|
||||||
- Integrates `BookingPriceCalculatorService` for complete pricing breakdown
|
|
||||||
- Calculates per-participant individual prices via `calculateAllParticipantIndividualPrices()`
|
|
||||||
- Template displays three-level pricing: aggregate breakdown, per-participant totals, and inline service prices
|
|
||||||
- All service prices shown in blue (text-blue-700) with German formatting
|
|
||||||
- Zero prices hidden per project standards
|
|
||||||
- `SuccessController.php` - Success page after booking completion
|
|
||||||
|
|
||||||
**Edit Namespace** (`src/Controller/Booking/Edit/`):
|
|
||||||
- `IndexController.php` - Edit flow with card-based UI, validation, and session management
|
|
||||||
- `index()` - Main edit view with dirty state detection
|
|
||||||
- `editParticipant()` - Individual participant form editing
|
|
||||||
- `refreshParticipantForm()` - HTMX refresh without validation
|
|
||||||
- `reloadFromApi()` - Discard changes and reload from API
|
|
||||||
- `cancelEdit()` - Clean session exit to bookings list
|
|
||||||
|
|
||||||
**Root Booking Namespace** (`src/Controller/Booking/`):
|
|
||||||
- `IndexController.php` - Bookings list
|
|
||||||
- `DownloadController.php` - Booking document downloads
|
|
||||||
|
|
||||||
**Shared Traits** (`src/Controller/Booking/Traits/`):
|
|
||||||
- `ParticipantCardFlowTrait` - Card rendering, form creation, summary calculation
|
|
||||||
- Creates `ParticipantEditDto` wrapper for email uniqueness validation
|
|
||||||
- Wraps participant form type with booking context for cross-participant validation
|
|
||||||
- `BookingCreateTrait` - Create flow helpers
|
|
||||||
- `BookingDataTrait` - API data fetching
|
|
||||||
- `BookingExceptionHandlerTrait` - Error handling
|
|
||||||
|
|
||||||
### Templates
|
|
||||||
**Create Flow**:
|
|
||||||
- `templates/booking/create/step_1.html.twig` - Room selection
|
|
||||||
- `templates/booking/create/step_2.html.twig` - Participant cards
|
|
||||||
- `templates/booking/create/step_3.html.twig` - Payment method
|
|
||||||
- `templates/booking/create/step_4.html.twig` - Final confirmation with comprehensive pricing display
|
|
||||||
- **Aggregate pricing breakdown**: Rooms and services grouped by type with totals
|
|
||||||
- **Per-participant cards**: Individual total price in header, all personal details and service selections
|
|
||||||
- **Inline service pricing**: Each service shows price in blue (€X,XX format) next to label
|
|
||||||
- **Payment summary**: Selected payment method and bank details (if debit)
|
|
||||||
- **Confirmation checkbox**: Final acceptance before API submission
|
|
||||||
- `templates/booking/create/success.html.twig` - Success page
|
|
||||||
- `templates/booking/create/error.html.twig` - Error page
|
|
||||||
|
|
||||||
**Edit Flow**:
|
|
||||||
- `templates/booking/edit/index.html.twig` - Edit with participant cards
|
|
||||||
|
|
||||||
**Shared Components**:
|
|
||||||
- `templates/booking/_participant_card.html.twig` - Individual participant card
|
|
||||||
- Displays validation state with red border and "unvollständige oder fehlerhafte Daten" badge
|
|
||||||
- Requires `isValid` and `errorMessages` in cardData for validation display
|
|
||||||
- `templates/booking/_participant_form.html.twig` - Participant edit form
|
|
||||||
- `templates/booking/_summary.html.twig` - Pricing summary sidebar
|
|
||||||
|
|
||||||
### Services
|
|
||||||
- `src/Service/BookingService.php` - Core workflow and session management
|
|
||||||
- `src/Service/BookingFingerprintService.php` - Dirty state detection via SHA-256 fingerprinting
|
|
||||||
- `src/Service/ParticipantCardDataService.php` - Card data generation
|
|
||||||
- `src/Form/Service/ParticipantFieldHandlerRegistry.php` - Handler orchestration
|
|
||||||
- `src/Form/Service/ParticipantFieldOptionsProvider.php` - Field configuration with mode-aware availability
|
|
||||||
- `src/Form/Service/CreateFieldStateProvider.php` - Conditional field states
|
|
||||||
- `src/Form/Service/EditFieldStateProvider.php` - Edit mode mutability constraints
|
|
||||||
|
|
||||||
### Field Handlers
|
|
||||||
- `src/Form/Service/Participant*FieldHandler.php` (16+ handlers)
|
|
||||||
- Transportation (outbound, inbound, discount replacement), pickup, parking, ski pass, rentals, insurance, bulk insurance, etc.
|
|
||||||
- `ParticipantTransportationDiscountReplacementFieldHandler` - Automatic discount replacement for invalid transportation combinations
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./vendor/bin/phpunit # All tests (234 tests, 588 assertions)
|
|
||||||
./vendor/bin/phpunit tests/Service/ # Service layer
|
|
||||||
./vendor/bin/phpunit tests/BusProNet/ # API integration
|
|
||||||
./vendor/bin/phpunit tests/Form/ # Form processing and field handlers
|
|
||||||
/opt/homebrew/bin/php-cs-fixer fix --rules=@Symfony # Code style (Symfony ruleset)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Test Coverage Areas:**
|
|
||||||
- BusProNet data loaders and processors
|
|
||||||
- XML parsers (travels, hotels, bookings, insurances)
|
|
||||||
- Form DTOs and field handlers (including `ParticipantEditDto` wrapper)
|
|
||||||
- Service layer (pricing, insurance matching, room assignment, card data generation)
|
|
||||||
- Conditional field system
|
|
||||||
- Cross-participant validation (email uniqueness)
|
|
||||||
- Utility classes
|
|
||||||
|
|
||||||
## Development Environment
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ddev start # Start DDEV
|
|
||||||
ddev composer install # Install dependencies
|
|
||||||
ddev exec bin/console cache:clear # Clear cache
|
|
||||||
ddev logs # Read PHP error logs
|
|
||||||
ddev exec "php -r 'opcache_reset()';" # Clear opcache after code changes
|
|
||||||
```
|
|
||||||
|
|
||||||
## Important Notes
|
|
||||||
|
|
||||||
- **Room prices are per person**
|
|
||||||
- **Room model uses `$label` property** (not `$name`) - ensure test fixtures use correct property
|
|
||||||
- **Zero prices display without suffix** (e.g., "Vollpension" not "Vollpension (€0,00)")
|
|
||||||
- **All services sorted by price** (cheapest first) via `SortByPriceTrait`
|
|
||||||
- **Field sync pattern critical**: Only sync fields in original submission to avoid "extra fields" errors
|
|
||||||
- **Insurance handler requires mode awareness**: Skips processing in edit mode (API doesn't return insurance data)
|
|
||||||
- **Insurance IDs are strings**: All insurance IDs must be strings, not integers (type safety)
|
|
||||||
- **Clear opcache after code changes** affecting hydration or serialization
|
|
||||||
- **HTMX targeting consistency**: All swaps target `#main-content` with `innerHTML`, sidebar via OOB swap
|
|
||||||
- **Validation pattern**: Both create and edit flows use validation-only forms that wrap card UI for standard Symfony form handling
|
|
||||||
- **Card validation display**: Uses `ParticipantCardDataService::getAllCardsDataWithValidation()` to enrich cards with validation state
|
|
||||||
- **Cross-participant validation**: Implemented via `ParticipantEditDto` wrapper with callback validators
|
|
||||||
- **Email uniqueness**: Adults (16+) require unique emails; children exempt from validation
|
|
||||||
- **Edit mode service availability**: Services with `available <= 0` remain visible and editable for participants who already have them (prevents fingerprint false positives)
|
|
||||||
- **Session cleanup on exit**: All exit paths from edit mode (save, discard, cancel) properly clear session to reset dirty state
|
|
||||||
- **Participant naming convention**: Index 0 is "Anmelder:in", others are "Teilnehmer:in N" (1-based, not 0-based)
|
|
||||||
- **Notification array structure**: Uses associative keys (MD5 hashes) for deduplication, not numeric indices
|
|
||||||
- **Floating-point price comparisons**: ALWAYS use `round($price, 2)` before comparing monetary values to avoid precision errors (see "Floating-Point Precision in Price Calculations" section)
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- **Project conventions**: `../CLAUDE.md` (root level)
|
|
||||||
- **User preferences**: `~/.claude/CLAUDE.md`
|
|
||||||
- **Documentation index**: `README.md` (this directory)
|
|
||||||
-337
@@ -1,337 +0,0 @@
|
|||||||
# MyEP Next Booking System
|
|
||||||
|
|
||||||
A sophisticated Symfony 6.4 travel booking application that integrates with Bus Pro Net (BPN) XML API for comprehensive travel management. The system handles complex multi-step booking workflows with dynamic participant forms, conditional field logic, and real-time pricing display.
|
|
||||||
|
|
||||||
## 🚀 Quick Start
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
- PHP 8.1+
|
|
||||||
- Composer
|
|
||||||
- Node.js & npm
|
|
||||||
- DDEV (recommended for local development)
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone the repository
|
|
||||||
git clone [repository-url]
|
|
||||||
cd myep-next-booking
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
composer install
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# Start local development environment
|
|
||||||
ddev start
|
|
||||||
|
|
||||||
# Run database migrations
|
|
||||||
bin/console doctrine:migrations:migrate
|
|
||||||
|
|
||||||
# Generate OAuth2 keys
|
|
||||||
bin/console app:generate-keys
|
|
||||||
|
|
||||||
# Compile assets
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🏗️ Architecture Overview
|
|
||||||
|
|
||||||
### Multi-Step Booking Flow
|
|
||||||
1. **Step 1**: Room selection with quantities and dates
|
|
||||||
2. **Step 2**: Participant details with conditional fields and service selection
|
|
||||||
3. **Step 3**: Final confirmation and submission to BPN API
|
|
||||||
|
|
||||||
### Core Components
|
|
||||||
|
|
||||||
#### BusProNet Integration (`src/BusProNet/`)
|
|
||||||
- **ApiClient**: XML API communication layer
|
|
||||||
- **XmlParser/**: Response parsing for travels, hotels, bookings
|
|
||||||
- **XmlLoader/**: Data loading with caching
|
|
||||||
- **DataProcessor/**: API data transformation
|
|
||||||
|
|
||||||
#### Advanced Form System (`src/Form/Service/`)
|
|
||||||
- **Conditional Field States**: Dynamic field behavior based on participant data
|
|
||||||
- **Service Field Handlers**: Modular field processing with dependency resolution
|
|
||||||
- **Transportation Services**: Comprehensive pickup, parking, and transportation options
|
|
||||||
- **Real-time Updates**: HTMX integration for seamless UX
|
|
||||||
|
|
||||||
#### Service Architecture (`src/Service/`)
|
|
||||||
- **BookingService**: Core booking workflow management
|
|
||||||
- **TravelDataService**: Travel data access and caching
|
|
||||||
- **BookingPriceCalculatorService**: Real-time pricing calculations
|
|
||||||
|
|
||||||
## 🎯 Key Features
|
|
||||||
|
|
||||||
### ✅ Implemented Features
|
|
||||||
|
|
||||||
#### Multi-Step Booking Workflow
|
|
||||||
- Room selection with dynamic pricing display
|
|
||||||
- Participant registration with conditional fields
|
|
||||||
- Service selection (board, ski passes, courses, rentals)
|
|
||||||
- Real-time booking summary with pricing
|
|
||||||
|
|
||||||
#### Transportation Services
|
|
||||||
- **Transportation Selection**: Bus vs. car transport options
|
|
||||||
- **Pickup Services**: Location-based pickup with conditional visibility
|
|
||||||
- **Parking Services**: Self-organized transport parking options
|
|
||||||
- **Direction Mapping**: Outbound/inbound transportation handling
|
|
||||||
|
|
||||||
#### Advanced Form System
|
|
||||||
- **Conditional Field States**: Age-based, value-dependent field visibility
|
|
||||||
- **Dynamic Field Options**: Context-aware choice generation
|
|
||||||
- **HTMX Integration**: Real-time form updates without page refresh
|
|
||||||
- **XSS Protection**: Built-in security measures
|
|
||||||
|
|
||||||
#### Pricing & Display
|
|
||||||
- **Inline Pricing**: Service costs displayed in form options
|
|
||||||
- **Real-time Calculations**: Live pricing updates via HTMX
|
|
||||||
- **Smart Formatting**: Zero-price services handled gracefully
|
|
||||||
- **Unified Summary**: Integrated booking and pricing display
|
|
||||||
|
|
||||||
#### Service Integration
|
|
||||||
- **Field Handler Registry**: Modular field processing system
|
|
||||||
- **Service Registration**: Automatic field handler discovery
|
|
||||||
- **Dependency Resolution**: Smart field interdependency handling
|
|
||||||
|
|
||||||
### 🔄 Ongoing Development
|
|
||||||
- Age-based field constraints
|
|
||||||
- Enhanced pricing features (discounts, taxes)
|
|
||||||
- Advanced booking management
|
|
||||||
- Extended BPN API integration
|
|
||||||
|
|
||||||
## 🛠️ Development
|
|
||||||
|
|
||||||
### Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Development workflow
|
|
||||||
npm run dev # Development build
|
|
||||||
npm run watch # Watch mode
|
|
||||||
npm run build # Production build
|
|
||||||
|
|
||||||
# Testing
|
|
||||||
bin/phpunit # All tests
|
|
||||||
./vendor/bin/phpunit tests/Service/ # Specific directory
|
|
||||||
./vendor/bin/phpunit tests/BusProNet/ # API integration tests
|
|
||||||
|
|
||||||
# Code quality
|
|
||||||
./vendor/bin/php-cs-fixer fix # Fix code style
|
|
||||||
|
|
||||||
# Cache & debugging
|
|
||||||
bin/console cache:clear # Clear cache
|
|
||||||
bin/console debug:router # Debug routes
|
|
||||||
bin/console debug:container # Debug services
|
|
||||||
|
|
||||||
# Custom commands
|
|
||||||
bin/console app:cleanup-xml-dumps # Clean XML dump files
|
|
||||||
bin/console app:generate-keys # Generate OAuth2 keys
|
|
||||||
```
|
|
||||||
|
|
||||||
### Development Standards
|
|
||||||
|
|
||||||
#### Code Style
|
|
||||||
- PSR-12 compliance with `declare(strict_types=1)`
|
|
||||||
- PHP 8+ features (typed properties, constructor promotion, match expressions)
|
|
||||||
- Explicit comparisons and Yoda conditions
|
|
||||||
- Immutable DateTime objects (DateTimeImmutable, CarbonImmutable)
|
|
||||||
|
|
||||||
#### Architecture Patterns
|
|
||||||
- Service layer for business logic
|
|
||||||
- DTO pattern for type-safe form data
|
|
||||||
- Registry pattern for configurable components
|
|
||||||
- Field handler pattern for complex form processing
|
|
||||||
- Trait-based code reuse
|
|
||||||
|
|
||||||
## 🏛️ Technical Stack
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
- **Framework**: Symfony 6.4 LTS
|
|
||||||
- **PHP**: 8.1+
|
|
||||||
- **Database**: MariaDB with Doctrine ORM
|
|
||||||
- **API Integration**: Custom XML client for BPN API
|
|
||||||
- **Authentication**: OAuth2 Server Bundle
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
- **JavaScript**: Stimulus (Hotwired) controllers
|
|
||||||
- **Dynamic Updates**: HTMX for seamless interactions
|
|
||||||
- **Styling**: TailwindCSS
|
|
||||||
- **Build Tool**: Webpack Encore
|
|
||||||
- **Templating**: Twig
|
|
||||||
|
|
||||||
### Development & Operations
|
|
||||||
- **Local Environment**: DDEV (PHP 8.2, MariaDB 10.11)
|
|
||||||
- **File Operations**: Flysystem with SFTP support
|
|
||||||
- **Date Handling**: Carbon for advanced date/time manipulation
|
|
||||||
- **Logging**: Monolog with multiple channels
|
|
||||||
- **Testing**: PHPUnit with Symfony bridge
|
|
||||||
|
|
||||||
## 📋 Form System Architecture
|
|
||||||
|
|
||||||
### Field Handler System
|
|
||||||
The application uses a sophisticated field handler system for processing complex participant forms:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// Example field handler
|
|
||||||
class ParticipantTransportationOutboundFieldHandler extends AbstractParticipantFieldHandler
|
|
||||||
{
|
|
||||||
public function processField(/* ... */): void
|
|
||||||
{
|
|
||||||
// Complex field processing with conditional logic
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDependencies(): array
|
|
||||||
{
|
|
||||||
return ['assignedRoomId', 'dateOfBirth']; // Field dependencies
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Conditional Field States
|
|
||||||
Fields can have dynamic states based on conditions:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// Age-based field state
|
|
||||||
$this->fieldStateConditions['advancedServices'] = [
|
|
||||||
'hidden' => new AgeRangeCondition(null, 15), // Hide for under 15
|
|
||||||
'required' => FieldValueCondition::equals('roomType', 'suite'),
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
### Available Field Handlers
|
|
||||||
- **Transportation**: Outbound/inbound transport selection
|
|
||||||
- **Pickup Services**: Location-based pickup options
|
|
||||||
- **Parking**: Self-organized transport parking
|
|
||||||
- **Accommodation**: Board, room assignment
|
|
||||||
- **Activities**: Ski passes, courses, rentals
|
|
||||||
- **Personal Data**: Age-aware field processing
|
|
||||||
|
|
||||||
## 💰 Pricing System
|
|
||||||
|
|
||||||
### Real-time Pricing Display
|
|
||||||
- **Inline Pricing**: Costs shown in form options
|
|
||||||
- **Live Updates**: HTMX-powered real-time calculations
|
|
||||||
- **Smart Formatting**: Zero-price services handled elegantly
|
|
||||||
- **Unified Summary**: Integrated pricing in booking summary
|
|
||||||
|
|
||||||
### Service Integration
|
|
||||||
```php
|
|
||||||
// Pricing calculation example
|
|
||||||
$serviceTotal = $this->bookingService->calculateServiceTotal($bookingDto);
|
|
||||||
$roomTotal = $this->bookingService->calculateRoomTotal($bookingDto);
|
|
||||||
$grandTotal = $serviceTotal + $roomTotal;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔄 BusProNet API Integration
|
|
||||||
|
|
||||||
### XML Communication
|
|
||||||
- **Request Building**: Dynamic XML generation for BPN API
|
|
||||||
- **Response Parsing**: Structured XML parsing with validation
|
|
||||||
- **Data Caching**: Intelligent caching for performance
|
|
||||||
- **Error Handling**: Comprehensive error management
|
|
||||||
|
|
||||||
### Data Flow
|
|
||||||
1. Form submission triggers API request building
|
|
||||||
2. XML sent to BPN API endpoints
|
|
||||||
3. Response parsed and validated
|
|
||||||
4. Data transformed for application use
|
|
||||||
5. Results cached for performance
|
|
||||||
|
|
||||||
## 🧪 Testing Strategy
|
|
||||||
|
|
||||||
### Test Coverage
|
|
||||||
- **Unit Tests**: Service layer and business logic
|
|
||||||
- **Integration Tests**: API communication and data processing
|
|
||||||
- **Form Tests**: Field handler and validation logic
|
|
||||||
- **XML Tests**: API response parsing with fixtures
|
|
||||||
|
|
||||||
### Running Tests
|
|
||||||
```bash
|
|
||||||
# All tests
|
|
||||||
./vendor/bin/phpunit
|
|
||||||
|
|
||||||
# Specific test suites
|
|
||||||
./vendor/bin/phpunit tests/BusProNet/ # API integration
|
|
||||||
./vendor/bin/phpunit tests/Service/ # Service layer
|
|
||||||
./vendor/bin/phpunit tests/Form/ # Form processing
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📚 Documentation
|
|
||||||
|
|
||||||
### Available Documentation
|
|
||||||
- **[PROJECT_OVERVIEW.md](PROJECT_OVERVIEW.md)**: Comprehensive architecture and implementation guide
|
|
||||||
- **[CLAUDE.md](../CLAUDE.md)**: Development guidelines for AI assistance (root level)
|
|
||||||
|
|
||||||
### Architecture Documentation
|
|
||||||
Each major system component has detailed documentation covering:
|
|
||||||
- Implementation patterns
|
|
||||||
- Usage examples
|
|
||||||
- Extension guidelines
|
|
||||||
- Testing strategies
|
|
||||||
|
|
||||||
## 🔒 Security & Configuration
|
|
||||||
|
|
||||||
### Environment Setup
|
|
||||||
- BPN API credentials in `.env.local`
|
|
||||||
- OAuth2 encryption keys via custom command
|
|
||||||
- SFTP configuration for deployment
|
|
||||||
- Logging channels for monitoring
|
|
||||||
|
|
||||||
### Security Features
|
|
||||||
- XSS protection in form processing
|
|
||||||
- OAuth2 authentication
|
|
||||||
- Secure API communication
|
|
||||||
- Input validation and sanitization
|
|
||||||
|
|
||||||
## 🚀 Deployment
|
|
||||||
|
|
||||||
### Production Requirements
|
|
||||||
- PHP 8.1+ with required extensions
|
|
||||||
- MariaDB 10.11+
|
|
||||||
- Web server (Apache/Nginx)
|
|
||||||
- SFTP access for file operations
|
|
||||||
- BPN API credentials
|
|
||||||
|
|
||||||
### Deployment Steps
|
|
||||||
1. Install dependencies (`composer install --no-dev`)
|
|
||||||
2. Generate OAuth2 keys (`bin/console app:generate-keys`)
|
|
||||||
3. Run database migrations (`bin/console doctrine:migrations:migrate`)
|
|
||||||
4. Build production assets (`npm run build`)
|
|
||||||
5. Configure environment variables
|
|
||||||
6. Set up SFTP access for XML exports
|
|
||||||
|
|
||||||
## 🤝 Contributing
|
|
||||||
|
|
||||||
### Development Workflow
|
|
||||||
1. Follow PSR-12 coding standards
|
|
||||||
2. Use type declarations consistently
|
|
||||||
3. Write comprehensive tests for new features
|
|
||||||
4. Update documentation for architectural changes
|
|
||||||
5. Use the field handler pattern for form extensions
|
|
||||||
|
|
||||||
### Key Patterns
|
|
||||||
- **Service Layer**: Business logic separation
|
|
||||||
- **DTO Pattern**: Type-safe data transfer
|
|
||||||
- **Registry Pattern**: Component discovery
|
|
||||||
- **Field Handlers**: Modular form processing
|
|
||||||
|
|
||||||
## 📞 Support
|
|
||||||
|
|
||||||
### Logging Channels
|
|
||||||
- **app**: General application logs
|
|
||||||
- **bpn**: BusProNet API interactions
|
|
||||||
- **security**: Authentication/authorization
|
|
||||||
- **db**: Database-related logs
|
|
||||||
|
|
||||||
### Debugging
|
|
||||||
- Use `bin/console debug:router` for route inspection
|
|
||||||
- Use `bin/console debug:container` for service inspection
|
|
||||||
- Check logs in `var/log/` for troubleshooting
|
|
||||||
- Use DDEV for consistent development environment
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Version**: 2.0
|
|
||||||
**Symfony**: 6.4 LTS
|
|
||||||
**PHP**: 8.1+
|
|
||||||
**Status**: Production Ready
|
|
||||||
**Last Updated**: 2025-01-XX
|
|
||||||
Reference in New Issue
Block a user