Performance Audit Report

šŸ“„ General
← Back to Documentation
# Performance Audit Report ## Executive Summary This report identifies performance bottlenecks and optimization opportunities across controllers, models, and views in the ZapaziMe booking system. --- ## 1. Controller Performance Issues ### 1.1 BookingController.php **Issue 1.1.1: N+1 Query in venue_objects loop (Line 107)** - **Location**: `BookingController@store()` - **Problem**: `VenueObject::find($objectId)` called inside foreach loop - **Impact**: O(n) database queries where n = number of venue objects - **Severity**: HIGH - **Solution**: ```php // Current (N+1) foreach ($validated['venue_objects'] as $objectId => $quantity) { $venueObject = \App\Models\VenueObject::find($objectId); } // Optimized (single query) $objectIds = array_keys(array_filter($validated['venue_objects'], fn($q) => $q > 0)); $venueObjects = \App\Models\VenueObject::whereIn('id', $objectIds)->get()->keyBy('id'); foreach ($validated['venue_objects'] as $objectId => $quantity) { $venueObject = $venueObjects[$objectId] ?? null; } ``` **Issue 1.1.2: Similar N+1 in services loop (Line 151+)** - **Location**: `BookingController@store()` - **Problem**: `VenueService::find($serviceId)` inside loop - **Impact**: O(n) database queries - **Severity**: HIGH - **Solution**: Same pattern as above - use `whereIn()` before loop --- ### 1.2 ReservationController.php **Issue 1.2.1: N+1 Query in spots loop (Line 47)** - **Location**: `ReservationController@store()` - **Problem**: `VenueSpot::find($spotId)` inside foreach loop - **Impact**: O(n) database queries - **Severity**: HIGH - **Solution**: ```php // Current (N+1) foreach ($validated['spots'] as $spotId) { $spot = VenueSpot::find($spotId); } // Optimized (single query) $spots = VenueSpot::whereIn('id', $validated['spots'])->get()->keyBy('id'); foreach ($validated['spots'] as $spotId) { $spot = $spots[$spotId] ?? null; } ``` **Issue 1.2.2: Inefficient spot status updates (Line 58)** - **Location**: `ReservationController@store()` - **Problem**: Individual UPDATE queries for each spot - **Impact**: O(n) UPDATE queries - **Severity**: MEDIUM - **Solution**: ```php // Optimized (single UPDATE) VenueSpot::whereIn('id', $validated['spots'])->update(['status' => 'unavailable']); ``` --- ### 1.3 ClientController.php **Issue 1.3.1: Multiple Queries for Booking Stats (Lines 36-40)** - **Location**: `ClientController@dashboard()` - **Problem**: 5 separate COUNT queries for different statuses - **Impact**: 5 database queries when 1 would suffice - **Severity**: MEDIUM - **Solution**: ```php // Current (5 queries) $bookingStats = [ 'total' => Booking::where('user_id', $user->id)->count(), 'pending' => Booking::where('user_id', $user->id)->where('status', 'pending')->count(), 'confirmed' => Booking::where('user_id', $user->id)->where('status', 'confirmed')->count(), 'completed' => Booking::where('user_id', $user->id)->where('status', 'completed')->count(), 'cancelled' => Booking::where('user_id', $user->id)->where('status', 'cancelled')->count(), ]; // Optimized (1 query with conditional aggregation) $bookingStats = Booking::where('user_id', $user->id) ->selectRaw(' COUNT(*) as total, SUM(CASE WHEN status = "pending" THEN 1 ELSE 0 END) as pending, SUM(CASE WHEN status = "confirmed" THEN 1 ELSE 0 END) as confirmed, SUM(CASE WHEN status = "completed" THEN 1 ELSE 0 END) as completed, SUM(CASE WHEN status = "cancelled" THEN 1 ELSE 0 END) as cancelled ') ->first() ->toArray(); ``` **Issue 1.3.2: N+1 Query in Monthly Stats Loop (Lines 93-117)** - **Location**: `ClientController@getMonthlyBookingStats()` - **Problem**: Querying bookings for each month in a loop (12 queries) - **Impact**: 12 database queries - **Severity**: HIGH - **Solution**: ```php // Current (12 queries in loop) for ($i = 11; $i >= 0; $i--) { $month = now()->subMonths($i); $bookings = Booking::where('user_id', $user->id) ->whereYear('created_at', $year) ->whereMonth('created_at', $month->format('m')) ->get(); } // Optimized (1 query with GROUP BY) $stats = Booking::where('user_id', $user->id) ->where('created_at', '>=', now()->subMonths(12)->startOfMonth()) ->selectRaw(' YEAR(created_at) as year, MONTH(created_at) as month, COUNT(*) as bookings, SUM(CASE WHEN payment_status = "paid" THEN total_price ELSE 0 END) as spent, SUM(CASE WHEN status = "pending" THEN 1 ELSE 0 END) as pending, SUM(CASE WHEN status = "confirmed" THEN 1 ELSE 0 END) as confirmed, SUM(CASE WHEN status = "completed" THEN 1 ELSE 0 END) as completed ') ->groupBy('year', 'month') ->orderBy('year', 'desc') ->orderBy('month', 'desc') ->get() ->map(function($item) { return [ 'month' => \Carbon\Carbon::create($item->year, $item->month)->format('M'), 'year' => $item->year, 'bookings' => $item->bookings, 'spent' => $item->spent, 'pending' => $item->pending, 'confirmed' => $item->confirmed, 'completed' => $item->completed, ]; })->toArray(); ``` **Issue 1.3.3: Duplicate Recent Activity Query (Lines 28-32, 67-71)** - **Location**: `ClientController@dashboard()` - **Problem**: Same query executed twice with minor differences - **Impact**: Unnecessary duplicate query - **Severity**: LOW - **Solution**: Reuse the same dataset --- ### 1.4 VenueController.php **Issue 1.4.1: Missing Eager Loading (Line 13)** - **Location**: `VenueController@getSpots()` - **Problem**: No eager loading on venueSpots relationship - **Impact**: If spots have relationships, could cause N+1 - **Severity**: LOW-MEDIUM - **Solution**: ```php // Add eager loading if spots have relationships $spots = $venue->venueSpots()->with(['venue'])->get(); ``` --- ## 2. Model Performance Issues ### 2.1 Common Patterns to Check **Issue 2.1.1: Accessors with Database Queries** - **Problem**: Accessors that execute database queries can cause N+1 issues - **Severity**: HIGH - **Recommendation**: Review all accessor methods in models for database queries **Issue 2.1.2: Missing Casts** - **Problem**: JSON fields not cast to array, causing JSON parsing on every access - **Severity**: MEDIUM - **Recommendation**: Add `$casts` for all JSON columns **Issue 2.1.3: Missing Eager Loading in Relationships** - **Problem**: Relationships not pre-loaded in common queries - **Severity**: HIGH - **Recommendation**: Add `with()` in common queries --- ## 3. View Performance Issues ### 3.1 Blade Templates **Issue 3.1.1: Database Queries in Views** - **Problem**: Direct database queries in Blade templates - **Impact**: Difficult to cache, hard to optimize - **Severity**: HIGH - **Recommendation**: Move all data fetching to controllers **Issue 3.1.2: Inefficient Loops** - **Problem**: Nested loops or loops with database queries - **Impact**: O(n²) or worse performance - **Severity**: HIGH - **Recommendation**: Pre-compute data in controllers **Issue 3.1.3: Missing Caching** - **Problem**: Static data queried on every request - **Impact**: Unnecessary database load - **Severity**: MEDIUM - **Recommendation**: Cache venue types, categories, countries, etc. --- ## 4. General Recommendations ### 4.1 Immediate Actions (High Priority) 1. **Fix N+1 queries in BookingController** - Use `whereIn()` before loops 2. **Fix N+1 queries in ReservationController** - Use `whereIn()` before loops 3. **Optimize ClientController dashboard stats** - Use conditional aggregation 4. **Optimize monthly stats query** - Use GROUP BY instead of loop ### 4.2 Short-term Actions (Medium Priority) 1. **Add eager loading** to all relationship queries 2. **Add caching** for static/infrequently changing data 3. **Review all accessors** for database queries 4. **Add JSON casts** for all JSON columns ### 4.3 Long-term Actions (Low Priority) 1. **Implement query caching** with Redis 2. **Add database query monitoring** (Laravel Telescope or debugbar) 3. **Implement lazy loading** for images 4. **Add pagination** for large datasets --- ## 5. Performance Monitoring ### 5.1 Recommended Tools 1. **Laravel Telescope** - Monitor queries, requests, exceptions 2. **Laravel Debugbar** - Development performance profiling 3. **Clockwork** - Request timeline and query analysis 4. **MySQL Slow Query Log** - Identify slow queries ### 5.2 Key Metrics to Monitor 1. **Query count per request** - Target: < 50 for most pages 2. **Query execution time** - Target: < 100ms for most queries 3. **Response time** - Target: < 500ms for most pages 4. **Memory usage** - Target: < 128MB for most pages --- ## 6. Specific File Recommendations ### 6.1 Files Requiring Immediate Attention 1. `app/Http/Controllers/BookingController.php` - Lines 107, 151+ 2. `app/Http/Controllers/ReservationController.php` - Lines 47, 58 3. `app/Http/Controllers/ClientController.php` - Lines 36-40, 93-117 ### 6.2 Files Requiring Review 1. `app/Models/Booking.php` - Check accessors and relationships 2. `app/Models/VenueObject.php` - Check accessors and relationships 3. `app/Models/VenueSpot.php` - Check accessors and relationships 4. All Blade views in `resources/views/web/` - Check for database queries --- ## 7. Estimated Performance Improvements Implementing the high-priority fixes should result in: - **60-80% reduction** in database queries on dashboard pages - **50-70% reduction** in query execution time for booking/reservation creation - **40-60% improvement** in overall page load times --- ## 8. Next Steps 1. Review and fix high-priority controller issues 2. Add eager loading to common queries 3. Implement caching for static data 4. Set up performance monitoring tools 5. Create automated performance tests --- ## 9. View Performance Issues ### 9.1 location.blade.php **Issue 9.1.1: @php blocks with random generation in loops (Lines 552-556, 572-574, 704-708, 724-727, 736-737, 765-768)** - **Problem**: Random number generation in @php blocks inside foreach loops - **Impact**: Unnecessary computation on every iteration - **Severity**: MEDIUM - **Solution**: Move random generation outside loops or pre-compute in controller ```php // Current (inefficient) @foreach($venues as $venue) @php $propertyTypes = ['Apartment', 'House', 'Villa', 'Studio']; $selectedType = $propertyTypes[array_rand($propertyTypes)]; $bedrooms = rand(0, 4); $guests = rand(2, 8); @endphp @endforeach // Optimized (pre-compute in controller) @foreach($venues as $venue) <span class="px-3 py-1">{{ $venue->property_type }}</span> <span>{{ $venue->bedrooms }} {{ __('bed') }}</span> @endforeach ``` **Issue 9.1.2: Method calls in views without caching (Lines 598, 750)** - **Problem**: `$venue->getLowestPrice()` called in loop - **Impact**: Potential database queries in each iteration - **Severity**: HIGH - **Solution**: Pre-compute in controller with eager loading ```php // In controller $venues = Venue::with(['venueFacilities', 'place', 'location']) ->get() ->map(function($venue) { $venue->lowest_price = $venue->getLowestPrice(); return $venue; }); ``` **Issue 9.1.3: Missing eager loading (Lines 528, 532)** - **Problem**: Accessing `$venue->place`, `$venue->location`, `$venue->venueFacilities` in loop - **Impact**: N+1 queries - **Severity**: HIGH - **Solution**: Add eager loading in controller ```php // In controller $venues = Venue::with(['place', 'location', 'venueFacilities'])->get(); ``` ### 9.2 bookings.blade.php **Issue 9.2.1: Missing eager loading for relationships (Lines 91-100)** - **Problem**: Accessing `$booking->venueServices` and `$booking->venuePackages` in loop - **Impact**: N+1 queries - **Severity**: HIGH - **Solution**: Add eager loading in controller ```php // In controller $bookings = Booking::with(['venue', 'venueServices', 'venuePackages'])->get(); ``` ### 9.3 booking-details.blade.php **Issue 9.3.1: Missing eager loading for venue (Lines 44-64)** - **Problem**: Accessing `$booking->venue` without eager loading - **Impact**: N+1 query - **Severity**: MEDIUM - **Solution**: Add eager loading in controller ```php // In controller $booking = Booking::with(['venue'])->findOrFail($id); ``` ### 9.4 home.blade.php **Issue 9.4.1: Large video file (Line 8)** - **Problem**: Video background without lazy loading or optimization - **Impact**: Slow initial page load - **Severity**: MEDIUM - **Solution**: Add lazy loading, use compressed version, or consider image fallback ```html <!-- Add loading attribute --> <video id="hero-video" autoplay muted loop playsinline preload="metadata" class="..."> <source src="{{ asset('public/media/videos/hero8.mp4') }}" type="video/mp4"> </video> <!-- Or use poster image --> <video poster="{{ asset('images/hero-poster.jpg') }}" ...> ``` ### 9.5 company-registration-wizard.blade.php **Issue 9.5.1: Debug info section exposed (Lines 50-82)** - **Problem**: Debug information displayed in production view - **Impact**: Security risk, performance overhead - **Severity**: HIGH - **Solution**: Remove debug section from production code ```php // Remove this entire section @if(session('debug_info')) <div class="mb-6 p-4 bg-red-100 border border-red-300 rounded-lg"> ... </div> @endif ``` --- ## 10. View Performance Recommendations ### 10.1 Immediate Actions (High Priority) 1. **Add eager loading to all booking-related views** - `bookings.blade.php`: Add `venueServices`, `venuePackages` - `booking-details.blade.php`: Add `venue` - `location.blade.php`: Add `place`, `location`, `venueFacilities` 2. **Remove debug code from production views** - Remove `session('debug_info')` section from `company-registration-wizard.blade.php` 3. **Pre-compute random values in controller** - Move random generation from view to controller for `location.blade.php` ### 10.2 Short-term Actions (Medium Priority) 1. **Optimize video loading** - Add `preload="metadata"` to video - Add poster image - Consider using compressed version 2. **Cache view fragments** - Cache static sections like footer, navigation - Use `@cache` directive for expensive computations 3. **Lazy load images** - Add `loading="lazy"` to all images - Use placeholder images for initial load ### 10.3 Long-term Actions (Low Priority) 1. **Implement view component caching** - Cache entire components that don't change frequently - Use Laravel's fragment caching 2. **Use view composer for shared data** - Move common data to view composers - Avoid repeated queries in multiple views 3. **Implement CDN for static assets** - Serve images, videos from CDN - Use CloudFront or similar service --- ## 11. View-Specific Fixes ### Fix for location.blade.php Controller ```php // In the controller that loads location.blade.php public function show($locationId, Request $request) { $location = Location::findOrFail($locationId); $venues = Venue::where('location_id', $locationId) ->with(['place', 'location', 'venueFacilities']) ->get() ->map(function($venue) { $venue->lowest_price = $venue->getLowestPrice(); // Pre-compute random values if needed $venue->property_type = $this->getRandomPropertyType($venue->id); $venue->bedrooms = $this->getRandomBedrooms($venue->id); $venue->guests = $this->getRandomGuests($venue->id); $venue->rating = $this->getRandomRating($venue->id); $venue->reviews_count = $this->getRandomReviews($venue->id); return $venue; }); return view('web.location', compact('location', 'venues', ...)); } ``` ### Fix for bookings.blade.php Controller ```php // Add eager loading $bookings = Booking::with(['venue', 'venueServices', 'venuePackages']) ->where('user_id', Auth::id()) ->orderBy('created_at', 'desc') ->paginate(20); ``` ### Fix for booking-details.blade.php Controller ```php // Add eager loading $booking = Booking::with(['venue', 'venueServices', 'venuePackages']) ->findOrFail($id); ``` --- ## 12. Estimated View Performance Improvements Implementing the view optimizations should result in: - **70-90% reduction** in database queries for listing pages - **40-60% improvement** in initial page load time for home page - **50-70% improvement** in rendering time for loops with relationships - **30-50% improvement** in overall page performance