From 3e6048c5ccb8c864417ad8eb14b663ee7d138fb8 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 11 Jul 2024 22:52:37 -0500 Subject: [PATCH] Implement some short term caching --- app/Actions/Tabulation/AllJudgesCount.php | 19 ++++++++++++------- .../Tabulation/RankAuditionEntries.php | 12 +++++++++--- app/Providers/AppServiceProvider.php | 2 +- app/Services/AuditionService.php | 10 ++++------ app/Services/EntryService.php | 10 +++++----- app/Services/UserService.php | 9 +++++---- app/helpers.php | 3 ++- config/cache.php | 1 + .../AllJudgesCountTest.php | 19 +++++++++++++------ .../Actions/CalculateScoreSheetTotalTest.php | 13 +++++++++---- tests/Feature/Actions/EnterScoreTest.php | 4 +++- .../Actions/RankAuditionEntriesTest.php | 13 +++++++++---- tests/Feature/Models/EntryTest.php | 1 + .../Feature/Pages/Admin/SchoolsIndexTest.php | 6 +++++- tests/Feature/Pages/Setup/DrawStoreTest.php | 7 +++++-- .../Feature/Services/AuditionServiceTest.php | 14 ++++++++++---- tests/Feature/Services/ScoreServiceTest.php | 4 +++- 17 files changed, 97 insertions(+), 50 deletions(-) diff --git a/app/Actions/Tabulation/AllJudgesCount.php b/app/Actions/Tabulation/AllJudgesCount.php index 619d817..9e40fee 100644 --- a/app/Actions/Tabulation/AllJudgesCount.php +++ b/app/Actions/Tabulation/AllJudgesCount.php @@ -7,25 +7,30 @@ namespace App\Actions\Tabulation; use App\Exceptions\TabulationException; use App\Models\Entry; use App\Services\AuditionService; +use Illuminate\Support\Facades\Cache; class AllJudgesCount implements CalculateEntryScore { protected CalculateScoreSheetTotal $calculator; protected AuditionService $auditionService; - public function __construct() + public function __construct(CalculateScoreSheetTotal $calculator, AuditionService $auditionService) { - $this->calculator = app(CalculateScoreSheetTotal::class); - $this->auditionService = app(AuditionService::class); + $this->calculator = $calculator; + $this->auditionService = $auditionService; } public function calculate(string $mode, Entry $entry): array { - $this->basicValidation($mode, $entry); - $this->areAllJudgesIn($entry); - $this->areAllJudgesValid($entry); + $cacheKey = 'entryScore-'.$entry->id.'-'.$mode; + return Cache::remember($cacheKey, 10, function () use ($mode, $entry) { + $this->basicValidation($mode, $entry); + $this->areAllJudgesIn($entry); + $this->areAllJudgesValid($entry); + + return $this->getJudgeTotals($mode, $entry); + }); - return $this->getJudgeTotals($mode, $entry); } protected function getJudgeTotals($mode, Entry $entry) diff --git a/app/Actions/Tabulation/RankAuditionEntries.php b/app/Actions/Tabulation/RankAuditionEntries.php index 0c13cf3..32a81e2 100644 --- a/app/Actions/Tabulation/RankAuditionEntries.php +++ b/app/Actions/Tabulation/RankAuditionEntries.php @@ -7,6 +7,7 @@ namespace App\Actions\Tabulation; use App\Exceptions\TabulationException; use App\Models\Audition; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Facades\Cache; class RankAuditionEntries { @@ -17,12 +18,17 @@ class RankAuditionEntries $this->calculator = $calculator; } - public function booo() + public function rank(string $mode, Audition $audition): Collection { - return 'blah'; + $cacheKey = 'audition'.$audition->id.$mode; + + return Cache::remember($cacheKey, 30, function () use ($mode, $audition) { + return $this->calculateRank($mode, $audition); + }); + } - public function rank(string $mode, Audition $audition): Collection + public function calculateRank(string $mode, Audition $audition): Collection { $this->basicValidation($mode, $audition); $entries = match ($mode) { diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index b9769f5..195fbc4 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -69,6 +69,6 @@ class AppServiceProvider extends ServiceProvider User::observe(UserObserver::class); SeatingLimit::observe(SeatingLimitObserver::class); - Model::preventLazyLoading(! app()->isProduction()); + #Model::preventLazyLoading(! app()->isProduction()); } } diff --git a/app/Services/AuditionService.php b/app/Services/AuditionService.php index 988a3b9..1b18787 100644 --- a/app/Services/AuditionService.php +++ b/app/Services/AuditionService.php @@ -16,7 +16,10 @@ class AuditionService public function __construct() { - self::$allAuditionIds = Audition::pluck('id'); + $cacheKey = 'allAuditionIds'; + self::$allAuditionIds = Cache::remember($cacheKey, 60, function () { + return Audition::pluck('id'); + }); } /** @@ -75,9 +78,4 @@ class AuditionService throw new AuditionServiceException('Invalid sort requested. Sort must be tiebreak or weight'); } } - - public function auditionExists($audition) - { - return self::$allAuditionIds->contains($audition->id); - } } diff --git a/app/Services/EntryService.php b/app/Services/EntryService.php index 6e4890c..8d1ba71 100644 --- a/app/Services/EntryService.php +++ b/app/Services/EntryService.php @@ -3,6 +3,7 @@ namespace App\Services; use App\Models\Entry; +use Illuminate\Support\Facades\Cache; class EntryService { @@ -25,11 +26,10 @@ class EntryService public function entryExists(Entry $entry): bool { - static $allEntryIds = null; - - if ($allEntryIds === null) { - $allEntryIds = Entry::pluck('id'); - } + $cacheKey = 'allEntryIds'; + $allEntryIds = Cache::remember($cacheKey, 60, function () { + return Entry::pluck('id'); + }); return $allEntryIds->contains($entry->id); } diff --git a/app/Services/UserService.php b/app/Services/UserService.php index 463ec4d..545e069 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -3,6 +3,7 @@ namespace App\Services; use App\Models\User; +use Illuminate\Support\Facades\Cache; class UserService { @@ -13,10 +14,10 @@ class UserService public function userExists(User $user): bool { - static $allUserIds = null; - if ($allUserIds === null) { - $allUserIds = User::pluck('id'); - } + $cacheKey = 'allUserIds'; + $allUserIds = Cache::remember($cacheKey, 60, function () { + return User::pluck('id'); + }); return $allUserIds->contains($user->id); } diff --git a/app/helpers.php b/app/helpers.php index abe2a72..09e1327 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -5,6 +5,7 @@ use App\Exceptions\ScoreEntryException; use App\Models\Entry; use App\Models\User; use App\Settings; +use Illuminate\Support\Facades\App; function tw_max_width_class_array(): array { @@ -39,6 +40,6 @@ function auditionSetting($key) */ function enterScore(User $user, Entry $entry, array $scores): \App\Models\ScoreSheet { - $scoreEntry = new EnterScore(); + $scoreEntry = App::make(EnterScore::class); return $scoreEntry($user, $entry, $scores); } diff --git a/config/cache.php b/config/cache.php index 6b57b18..12aa893 100644 --- a/config/cache.php +++ b/config/cache.php @@ -74,6 +74,7 @@ return [ 'driver' => 'redis', 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), + 'prefix' => env('REDIS_CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), ], 'dynamodb' => [ diff --git a/tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php b/tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php index f916a38..efb7e99 100644 --- a/tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php +++ b/tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php @@ -8,17 +8,20 @@ use App\Models\Entry; use App\Models\Room; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\App; uses(RefreshDatabase::class); it('throws an exception if mode is not seating or advancement', function () { - $calculator = new AllJudgesCount(); + #$calculator = new AllJudgesCount(); + $calculator = App::make(AllJudgesCount::class); $calculator->calculate('WRONG', Entry::factory()->create()); })->throws(TabulationException::class, 'Mode must be seating or advancement'); it('throws an exception if entry is not valid', function () { // Arrange - $calculator = new AllJudgesCount(); + #$calculator = new AllJudgesCount(); + $calculator = App::make(AllJudgesCount::class); // Act $calculator->calculate('seating', Entry::factory()->make()); // Assert @@ -38,7 +41,8 @@ it('throws an exception if entry is missing judge scores', function () { 1004 => 80, 1005 => 90, ]; - $calculator = new AllJudgesCount(); + #$calculator = new AllJudgesCount(); + $calculator = App::make(AllJudgesCount::class); enterScore($judge1, $entry, $scores); // Act $calculator->calculate('seating', $entry); @@ -61,7 +65,8 @@ it('throws an exception if a score exists from an invalid judge', function () { 1004 => 80, 1005 => 90, ]; - $calculator = new AllJudgesCount(); + #$calculator = new AllJudgesCount(); + $calculator = App::make(AllJudgesCount::class); enterScore($judge1, $entry, $scores); $scoreSheetToSpoof = enterScore($judge2, $entry, $scores); $scoreSheetToSpoof->update(['user_id' => $judge3->id]); @@ -92,7 +97,8 @@ it('correctly calculates scores for seating', function () { 1004 => 85, 1005 => 95, ]; - $calculator = new AllJudgesCount(); + #$calculator = new AllJudgesCount(); + $calculator = App::make(AllJudgesCount::class); enterScore($judge1, $entry, $scores); enterScore($judge2, $entry, $scores2); // Act @@ -124,7 +130,8 @@ it('correctly calculates scores for advancement', function () { 1004 => 85, 1005 => 95, ]; - $calculator = new AllJudgesCount(); + #$calculator = new AllJudgesCount(); + $calculator = App::make(AllJudgesCount::class); enterScore($judge1, $entry, $scores); enterScore($judge2, $entry, $scores2); // Act diff --git a/tests/Feature/Actions/CalculateScoreSheetTotalTest.php b/tests/Feature/Actions/CalculateScoreSheetTotalTest.php index 870d729..a3ce1c1 100644 --- a/tests/Feature/Actions/CalculateScoreSheetTotalTest.php +++ b/tests/Feature/Actions/CalculateScoreSheetTotalTest.php @@ -8,11 +8,11 @@ use App\Models\Entry; use App\Models\Room; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\Artisan; uses(RefreshDatabase::class); -beforeEach(function () { - $this->calculator = app(CalculateScoreSheetTotal::class); -}); + + it('throws an exception if an invalid mode is called for', function () { $calculator = app(CalculateScoreSheetTotal::class); $calculator('anything', Entry::factory()->create(), User::factory()->create()); @@ -27,9 +27,14 @@ it('throws an exception if an invalid entry is provided', function () { })->throws(TabulationException::class, 'Invalid entry provided'); it('throws an exception if the specified judge has not scored the entry', function () { // Arrange + loadSampleAudition(); + $judge = User::factory()->create(); + Room::find(1000)->addJudge($judge); + $entry = Entry::factory()->create(['audition_id' => 1000]); + Artisan::call('cache:clear'); $calculator = app(CalculateScoreSheetTotal::class); // Act - $calculator('seating', Entry::factory()->create(), User::factory()->create()); + $calculator('seating', $entry, $judge); //Assert })->throws(TabulationException::class, 'No score sheet by that judge for that entry'); it('correctly calculates final score for seating', function () { diff --git a/tests/Feature/Actions/EnterScoreTest.php b/tests/Feature/Actions/EnterScoreTest.php index 18f1dde..b9dd140 100644 --- a/tests/Feature/Actions/EnterScoreTest.php +++ b/tests/Feature/Actions/EnterScoreTest.php @@ -9,11 +9,13 @@ use App\Models\Room; use App\Models\ScoreSheet; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\App; uses(RefreshDatabase::class); beforeEach(function () { - $this->scoreEntry = new EnterScore(); + #$this->scoreEntry = new EnterScore(); + $this->scoreEntry = App::make(EnterScore::class); }); test('throws an exception if the user does not exist', function () { diff --git a/tests/Feature/Actions/RankAuditionEntriesTest.php b/tests/Feature/Actions/RankAuditionEntriesTest.php index b8fb87f..6ced42e 100644 --- a/tests/Feature/Actions/RankAuditionEntriesTest.php +++ b/tests/Feature/Actions/RankAuditionEntriesTest.php @@ -8,17 +8,20 @@ use App\Models\Entry; use App\Models\Room; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Artisan; uses(RefreshDatabase::class); it('throws an exception if an invalid mode is specified', function () { - $ranker = new RankAuditionEntries(new AllJudgesCount()); + #$ranker = new RankAuditionEntries(new AllJudgesCount()); + $ranker = App::make(RankAuditionEntries::class); $ranker->rank('wrong', Audition::factory()->create()); })->throws(TabulationException::class, 'Mode must be seating or advancement'); it('throws an exception if an invalid audition is provided', function () { // Arrange - $ranker = new RankAuditionEntries(new AllJudgesCount()); + #$ranker = new RankAuditionEntries(new AllJudgesCount()); + $ranker = App::make(RankAuditionEntries::class); // Act & Assert $ranker->rank('seating', Audition::factory()->make()); })->throws(TabulationException::class, 'Invalid audition provided'); @@ -26,7 +29,8 @@ it('includes all entries of the given mode in the return', function () { $audition = Audition::factory()->create(); $entries = Entry::factory()->seatingOnly()->count(10)->create(['audition_id' => $audition->id]); $otherEntries = Entry::factory()->advanceOnly()->count(10)->create(['audition_id' => $audition->id]); - $ranker = new RankAuditionEntries(new AllJudgesCount()); + #$ranker = new RankAuditionEntries(new AllJudgesCount()); + $ranker = App::make(RankAuditionEntries::class); // Act $return = $ranker->rank('seating', $audition); // Assert @@ -39,6 +43,7 @@ it('includes all entries of the given mode in the return', function () { }); it('places entries in the proper order', function () { // Arrange + Artisan::call('cache:clear'); loadSampleAudition(); $judge = User::factory()->create(); Room::find(1000)->addJudge($judge); @@ -54,7 +59,7 @@ it('places entries in the proper order', function () { enterScore($judge, $entries[3], $scoreArray4); enterScore($judge, $entries[4], $scoreArray5); Artisan::call('cache:clear'); - $ranker = new RankAuditionEntries(new AllJudgesCount()); + $ranker = App::make(RankAuditionEntries::class); $expectedOrder = [4, 1, 3, 5, 2]; // Act $return = $ranker->rank('seating', Audition::find(1000)); diff --git a/tests/Feature/Models/EntryTest.php b/tests/Feature/Models/EntryTest.php index c0e21a9..4d30f30 100644 --- a/tests/Feature/Models/EntryTest.php +++ b/tests/Feature/Models/EntryTest.php @@ -169,6 +169,7 @@ it('has a forAdvancement scope that only returns those entries entered for advan Entry::factory()->count(10)->create(['for_seating' => true, 'for_advancement' => true]); Entry::factory()->count(5)->create(['for_seating' => false, 'for_advancement' => true]); // Act & Assert + expect(Entry::forAdvancement()->count())->toBe(16) ->and(Entry::forAdvancement()->get()->first())->toBeInstanceOf(Entry::class) ->and(Entry::forAdvancement()->get()->first()->student->first_name)->toBe('Advance Only'); diff --git a/tests/Feature/Pages/Admin/SchoolsIndexTest.php b/tests/Feature/Pages/Admin/SchoolsIndexTest.php index bc05450..d7d8e9a 100644 --- a/tests/Feature/Pages/Admin/SchoolsIndexTest.php +++ b/tests/Feature/Pages/Admin/SchoolsIndexTest.php @@ -4,9 +4,12 @@ use App\Models\Entry; use App\Models\School; use App\Models\Student; use App\Models\User; +use App\Services\EntryService; +use App\Services\Invoice\InvoiceOneFeePerEntry; use App\Settings; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\App; use function Pest\Laravel\actingAs; use function Pest\Laravel\get; @@ -62,7 +65,8 @@ it('has a new school link', function () { }); it('shows school data', function () { // Arrange - $invoiceDataService = new App\Services\Invoice\InvoiceOneFeePerEntry(new App\Services\EntryService(new App\Services\AuditionService())); + #$invoiceDataService = new App\Services\Invoice\InvoiceOneFeePerEntry(new App\Services\EntryService(new App\Services\AuditionService())); + $invoiceDataService = App::make(InvoiceOneFeePerEntry::class); Settings::set('school_fees', 1000); Settings::set('late_fee', 2500); actingAs($this->adminUser); diff --git a/tests/Feature/Pages/Setup/DrawStoreTest.php b/tests/Feature/Pages/Setup/DrawStoreTest.php index ad3e63f..44cfe60 100644 --- a/tests/Feature/Pages/Setup/DrawStoreTest.php +++ b/tests/Feature/Pages/Setup/DrawStoreTest.php @@ -4,6 +4,7 @@ use App\Models\Audition; use App\Models\Entry; use App\Services\DrawService; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\App; uses(RefreshDatabase::class); @@ -77,7 +78,8 @@ it('sets the draw_number column on each entry in the audition based on the rando // Arrange $audition = Audition::factory()->hasEntries(10)->create(); Entry::all()->each(fn ($entry) => expect($entry->draw_number)->toBeNull()); - $drawService = new DrawService(); + #$drawService = new DrawService(); + $drawService = App::make(DrawService::class); $drawService->runOneDraw($audition); // Act & Assert Entry::all()->each(fn ($entry) => expect($entry->draw_number)->not()->toBeNull()); @@ -86,7 +88,8 @@ it('only sets draw numbers in the specified audition', function () { // Arrange $audition = Audition::factory()->hasEntries(10)->create(); $bonusEntry = Entry::factory()->create(); - $drawService = new DrawService(); + #$drawService = new DrawService(); + $drawService = App::make(DrawService::class); $drawService->runOneDraw($audition); // Act & Assert expect($bonusEntry->draw_number)->toBeNull(); diff --git a/tests/Feature/Services/AuditionServiceTest.php b/tests/Feature/Services/AuditionServiceTest.php index 8804a14..5bbf8c0 100644 --- a/tests/Feature/Services/AuditionServiceTest.php +++ b/tests/Feature/Services/AuditionServiceTest.php @@ -3,25 +3,30 @@ /** @noinspection PhpUnhandledExceptionInspection */ use App\Models\Audition; +use App\Services\AuditionService; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\App; uses(RefreshDatabase::class); it('throws an exception when an invalid mode is requested', function () { - $auditionService = new \App\Services\AuditionService(); + #$auditionService = new \App\Services\AuditionService(); + $auditionService = App::make(AuditionService::class); $this->expectException(\App\Exceptions\AuditionServiceException::class); $auditionService->getSubscores(new Audition(), 'invalid_mode'); }); it('throws an exception when an invalid sort is requested', function () { // Arrange - $auditionService = new \App\Services\AuditionService(); + #$auditionService = new \App\Services\AuditionService(); + $auditionService = App::make(AuditionService::class); $this->expectException(\App\Exceptions\AuditionServiceException::class); // Act $auditionService->getSubscores(new Audition(), 'seating', 'invalid_sort'); }); it('throws an exception when an invalid audition is provided', function () { // Arrange - $auditionService = new \App\Services\AuditionService(); + #$auditionService = new \App\Services\AuditionService(); + $auditionService = App::make(AuditionService::class); $this->expectException(\App\Exceptions\AuditionServiceException::class); $auditionService->getSubscores(new Audition(), 'seating', 'tiebreak'); // Act & Assert @@ -30,7 +35,8 @@ it('throws an exception when an invalid audition is provided', function () { it('gets subscores for an audition', function () { // Arrange loadSampleAudition(); - $auditionService = new \App\Services\AuditionService(); + #$auditionService = new \App\Services\AuditionService(); + $auditionService = App::make(AuditionService::class); // Act $subscores = $auditionService->getSubscores(Audition::find(1000), 'seating', 'tiebreak'); // Assert diff --git a/tests/Feature/Services/ScoreServiceTest.php b/tests/Feature/Services/ScoreServiceTest.php index 64f544f..df9d3e3 100644 --- a/tests/Feature/Services/ScoreServiceTest.php +++ b/tests/Feature/Services/ScoreServiceTest.php @@ -7,11 +7,13 @@ use App\Models\ScoreSheet; use App\Models\User; use App\Services\ScoreService; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\App; uses(RefreshDatabase::class); beforeEach(function () { - $this->scoreService = new ScoreService(); + #$this->scoreService = new ScoreService(); + $this->scoreService = App::make(ScoreService::class); }); it('can check if an entry is fully scored', function () {