From 634e251fc346ba2b699bfa673314e0fb98b7ecff Mon Sep 17 00:00:00 2001 From: Levin Date: Wed, 11 Dec 2024 20:34:20 +0100 Subject: [PATCH 01/11] de-tangle the hafascontroller a bit --- app/Console/Commands/RefreshCurrentTrips.php | 34 +++--- app/DataProviders/DbRestInterface.php | 16 +++ app/DataProviders/FptfHelper.php | 12 ++ .../HafasController.php | 107 ++++-------------- .../Repositories/StationRepository.php | 61 ++++++++++ .../Controllers/API/v1/EventController.php | 2 +- .../API/v1/TransportController.php | 2 +- .../Backend/Transport/StationController.php | 2 +- .../Frontend/Admin/CheckinController.php | 4 - .../Frontend/Admin/EventController.php | 2 +- app/Http/Controllers/TransportController.php | 1 + app/Jobs/RefreshStopover.php | 2 +- .../CheckinHydratorRepository.php | 2 +- ...26_203951_create_train_stopovers_table.php | 4 +- tests/Feature/StationSearchTest.php | 4 +- .../Feature/Transport/BackendCheckinTest.php | 2 +- .../Feature/Transport/TransportStatsTest.php | 11 -- tests/Feature/Webhooks/WebhookStatusTest.php | 14 +-- 18 files changed, 150 insertions(+), 132 deletions(-) create mode 100644 app/DataProviders/DbRestInterface.php create mode 100644 app/DataProviders/FptfHelper.php rename app/{Http/Controllers => DataProviders}/HafasController.php (81%) create mode 100644 app/DataProviders/Repositories/StationRepository.php diff --git a/app/Console/Commands/RefreshCurrentTrips.php b/app/Console/Commands/RefreshCurrentTrips.php index 7b7e447b7..ca63aaa8c 100644 --- a/app/Console/Commands/RefreshCurrentTrips.php +++ b/app/Console/Commands/RefreshCurrentTrips.php @@ -2,11 +2,11 @@ namespace App\Console\Commands; +use App\DataProviders\HafasController; use App\Enum\TripSource; use App\Exceptions\HafasException; -use App\Http\Controllers\HafasController; -use App\Models\Trip; use App\Models\Checkin; +use App\Models\Trip; use Illuminate\Console\Command; use PDOException; @@ -23,22 +23,22 @@ public function handle(): int { ->join('train_stopovers as origin_stopovers', 'origin_stopovers.id', '=', 'train_checkins.origin_stopover_id') ->join('train_stopovers as destination_stopovers', 'destination_stopovers.id', '=', 'train_checkins.destination_stopover_id') ->where(function($query) { - $query->where('destination_stopovers.arrival_planned', '>=', now()->subMinutes(20)) - ->orWhere('destination_stopovers.arrival_real', '>=', now()->subMinutes(20)); - }) + $query->where('destination_stopovers.arrival_planned', '>=', now()->subMinutes(20)) + ->orWhere('destination_stopovers.arrival_real', '>=', now()->subMinutes(20)); + }) ->where(function($query) { - $query->where('origin_stopovers.departure_planned', '<=', now()->addMinutes(20)) - ->orWhere('origin_stopovers.departure_real', '<=', now()->addMinutes(20)); - }) + $query->where('origin_stopovers.departure_planned', '<=', now()->addMinutes(20)) + ->orWhere('origin_stopovers.departure_real', '<=', now()->addMinutes(20)); + }) ->where(function($query) { - $query->where('hafas_trips.last_refreshed', '<', now()->subMinutes(5)) - ->orWhereNull('hafas_trips.last_refreshed'); - }) - ->where('hafas_trips.source', TripSource::HAFAS->value) - ->select('hafas_trips.*') - ->distinct() - ->orderBy('hafas_trips.last_refreshed') - ->get(); + $query->where('hafas_trips.last_refreshed', '<', now()->subMinutes(5)) + ->orWhereNull('hafas_trips.last_refreshed'); + }) + ->where('hafas_trips.source', TripSource::HAFAS->value) + ->select('hafas_trips.*') + ->distinct() + ->orderBy('hafas_trips.last_refreshed') + ->get(); if ($trips->isEmpty()) { $this->warn('No trips to be refreshed'); @@ -53,7 +53,7 @@ public function handle(): int { $this->info('Refreshing trip ' . $trip->trip_id . ' (' . $trip->linename . ')...'); $trip->update(['last_refreshed' => now()]); - $rawHafas = HafasController::fetchRawHafasTrip($trip->trip_id, $trip->linename); + $rawHafas = HafasController::fetchRawHafasTrip($trip->trip_id, $trip->linename); $updatedCounts = HafasController::refreshStopovers($rawHafas); $this->info('Updated ' . $updatedCounts->stopovers . ' stopovers.'); diff --git a/app/DataProviders/DbRestInterface.php b/app/DataProviders/DbRestInterface.php new file mode 100644 index 000000000..faf7b5239 --- /dev/null +++ b/app/DataProviders/DbRestInterface.php @@ -0,0 +1,16 @@ +get("/stations/$rilIdentifier"); - if (!$response->ok()) { + if (!$response->ok() || empty($response->body()) || $response->body() === '[]') { return null; } $data = json_decode($response->body(), false, 512, JSON_THROW_ON_ERROR); - return Station::updateOrCreate([ - 'ibnr' => $data->id - ], [ - 'rilIdentifier' => $data->ril100, - 'name' => $data->name, - 'latitude' => $data->location->latitude, - 'longitude' => $data->location->longitude - ]); + return StationRepository::parseHafasStopObject($data); } catch (Exception $exception) { report($exception); } @@ -83,54 +79,12 @@ public static function getStations(string $query, int $results = 10): Collection return Collection::empty(); } - return self::parseHafasStops($data); + return Repositories\StationRepository::parseHafasStops($data); } catch (JsonException $exception) { throw new HafasException($exception->getMessage()); } } - /** - * @param stdClass $hafasStop - * - * @return Station - * @throws PDOException - */ - public static function parseHafasStopObject(stdClass $hafasStop): Station { - return Station::updateOrCreate([ - 'ibnr' => $hafasStop->id - ], [ - 'name' => $hafasStop->name, - 'latitude' => $hafasStop->location?->latitude, - 'longitude' => $hafasStop->location?->longitude, - ]); - } - - private static function parseHafasStops(array $hafasResponse): Collection { - $payload = []; - foreach ($hafasResponse as $hafasStation) { - $payload[] = [ - 'ibnr' => $hafasStation->id, - 'name' => $hafasStation->name, - 'latitude' => $hafasStation?->location?->latitude, - 'longitude' => $hafasStation?->location?->longitude, - ]; - } - return self::upsertStations($payload); - } - - private static function upsertStations(array $payload) { - $ibnrs = array_column($payload, 'ibnr'); - if (empty($ibnrs)) { - return new Collection(); - } - Station::upsert($payload, ['ibnr'], ['name', 'latitude', 'longitude']); - return Station::whereIn('ibnr', $ibnrs)->get() - ->sortBy(function(Station $station) use ($ibnrs) { - return array_search($station->ibnr, $ibnrs); - }) - ->values(); - } - /** * @throws HafasException */ @@ -147,7 +101,7 @@ public static function getNearbyStations(float $latitude, float $longitude, int } $data = json_decode($response->getBody()->getContents(), false, 512, JSON_THROW_ON_ERROR); - $stations = self::parseHafasStops($data); + $stations = Repositories\StationRepository::parseHafasStops($data); foreach ($data as $hafasStation) { $station = $stations->where('ibnr', $hafasStation->id)->first(); @@ -176,16 +130,16 @@ public static function fetchDepartures( $query = [ 'when' => $time->toIso8601String(), 'duration' => $duration, - HTT::NATIONAL_EXPRESS->value => self::checkTravelType($type, TravelType::EXPRESS), - HTT::NATIONAL->value => self::checkTravelType($type, TravelType::EXPRESS), - HTT::REGIONAL_EXP->value => self::checkTravelType($type, TravelType::EXPRESS), - HTT::REGIONAL->value => self::checkTravelType($type, TravelType::REGIONAL), - HTT::SUBURBAN->value => self::checkTravelType($type, TravelType::SUBURBAN), - HTT::BUS->value => self::checkTravelType($type, TravelType::BUS), - HTT::FERRY->value => self::checkTravelType($type, TravelType::FERRY), - HTT::SUBWAY->value => self::checkTravelType($type, TravelType::SUBWAY), - HTT::TRAM->value => self::checkTravelType($type, TravelType::TRAM), - HTT::TAXI->value => self::checkTravelType($type, TravelType::TAXI), + HTT::NATIONAL_EXPRESS->value => FptfHelper::checkTravelType($type, TravelType::EXPRESS), + HTT::NATIONAL->value => FptfHelper::checkTravelType($type, TravelType::EXPRESS), + HTT::REGIONAL_EXP->value => FptfHelper::checkTravelType($type, TravelType::EXPRESS), + HTT::REGIONAL->value => FptfHelper::checkTravelType($type, TravelType::REGIONAL), + HTT::SUBURBAN->value => FptfHelper::checkTravelType($type, TravelType::SUBURBAN), + HTT::BUS->value => FptfHelper::checkTravelType($type, TravelType::BUS), + HTT::FERRY->value => FptfHelper::checkTravelType($type, TravelType::FERRY), + HTT::SUBWAY->value => FptfHelper::checkTravelType($type, TravelType::SUBWAY), + HTT::TRAM->value => FptfHelper::checkTravelType($type, TravelType::TRAM), + HTT::TAXI->value => FptfHelper::checkTravelType($type, TravelType::TAXI), ]; $response = $client->get('/stops/' . $station->ibnr . '/departures', $query); @@ -196,10 +150,6 @@ public static function fetchDepartures( return json_decode($response->body(), false, 512, JSON_THROW_ON_ERROR); } - public static function checkTravelType(?TravelType $type, TravelType $travelType): string { - return (is_null($type) || $type === $travelType) ? 'true' : 'false'; - } - /** * @param Station $station * @param Carbon $when @@ -269,7 +219,7 @@ public static function getDepartures( 'longitude' => $departure->stop?->location?->longitude, ]; } - $stations = self::upsertStations($stationPayload); + $stations = Repositories\StationRepository::upsertStations($stationPayload); //Then match the stations to the departures $departures = collect(); @@ -331,14 +281,7 @@ private static function fetchStation(int $ibnr): Station { } $data = json_decode($response->body()); - return Station::updateOrCreate([ - 'ibnr' => $data->id - ], [ - 'name' => $data->name, - 'latitude' => $data->location->latitude, - 'longitude' => $data->location->longitude - ]); - + return StationRepository::parseHafasStopObject($data); } /** @@ -375,8 +318,8 @@ public static function fetchRawHafasTrip(string $tripId, string $lineName) { */ public static function fetchHafasTrip(string $tripID, string $lineName): Trip { $tripJson = self::fetchRawHafasTrip($tripID, $lineName); - $origin = self::parseHafasStopObject($tripJson->origin); - $destination = self::parseHafasStopObject($tripJson->destination); + $origin = Repositories\StationRepository::parseHafasStopObject($tripJson->origin); + $destination = Repositories\StationRepository::parseHafasStopObject($tripJson->destination); $operator = null; if (isset($tripJson->line->operator->id)) { @@ -424,7 +367,7 @@ public static function fetchHafasTrip(string $tripID, string $lineName): Trip { 'longitude' => $stopover->stop->location?->longitude, ]; } - $stations = self::upsertStations($payload); + $stations = Repositories\StationRepository::upsertStations($payload); foreach ($tripJson->stopovers as $stopover) { //TODO: make this better 🤯 @@ -487,7 +430,7 @@ public static function refreshStopovers(stdClass $rawHafas): stdClass { continue; // No realtime data present for this stopover, keep existing data } - $stop = self::parseHafasStopObject($stopover->stop); + $stop = Repositories\StationRepository::parseHafasStopObject($stopover->stop); $arrivalPlanned = Carbon::parse($stopover->plannedArrival)->tz(config('app.timezone')); $departurePlanned = Carbon::parse($stopover->plannedDeparture)->tz(config('app.timezone')); diff --git a/app/DataProviders/Repositories/StationRepository.php b/app/DataProviders/Repositories/StationRepository.php new file mode 100644 index 000000000..1e3836d6a --- /dev/null +++ b/app/DataProviders/Repositories/StationRepository.php @@ -0,0 +1,61 @@ + $hafasStop->name, + 'latitude' => $hafasStop->location?->latitude, + 'longitude' => $hafasStop->location?->longitude, + ]; + + if (isset($hafasStop->ril100)) { + $data['rilIdentifier'] = $hafasStop->ril100; + } + + return Station::updateOrCreate([ + 'ibnr' => $hafasStop->id + ], $data); + } + + public static function parseHafasStops(array $hafasResponse): Collection { + $payload = []; + foreach ($hafasResponse as $hafasStation) { + $payload[] = [ + 'ibnr' => $hafasStation->id, + 'name' => $hafasStation->name, + 'latitude' => $hafasStation?->location?->latitude, + 'longitude' => $hafasStation?->location?->longitude, + ]; + } + return self::upsertStations($payload); + } + + public static function upsertStations(array $payload) { + $ibnrs = array_column($payload, 'ibnr'); + if (empty($ibnrs)) { + return new Collection(); + } + Station::upsert($payload, ['ibnr'], ['name', 'latitude', 'longitude']); + return Station::whereIn('ibnr', $ibnrs)->get() + ->sortBy(function(Station $station) use ($ibnrs) { + return array_search($station->ibnr, $ibnrs); + }) + ->values(); + } +} diff --git a/app/Http/Controllers/API/v1/EventController.php b/app/Http/Controllers/API/v1/EventController.php index 83e083851..608e4aac4 100644 --- a/app/Http/Controllers/API/v1/EventController.php +++ b/app/Http/Controllers/API/v1/EventController.php @@ -2,8 +2,8 @@ namespace App\Http\Controllers\API\v1; +use App\DataProviders\HafasController; use App\Http\Controllers\Backend\EventController as EventBackend; -use App\Http\Controllers\HafasController; use App\Http\Controllers\StatusController; use App\Http\Resources\EventDetailsResource; use App\Http\Resources\EventResource; diff --git a/app/Http/Controllers/API/v1/TransportController.php b/app/Http/Controllers/API/v1/TransportController.php index fff3bcb5d..9719a7ce0 100644 --- a/app/Http/Controllers/API/v1/TransportController.php +++ b/app/Http/Controllers/API/v1/TransportController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\API\v1; +use App\DataProviders\HafasController; use App\Dto\Transport\Station as StationDto; use App\Enum\Business; use App\Enum\StatusVisibility; @@ -12,7 +13,6 @@ use App\Exceptions\StationNotOnTripException; use App\Http\Controllers\Backend\Transport\StationController; use App\Http\Controllers\Backend\Transport\TrainCheckinController; -use App\Http\Controllers\HafasController; use App\Http\Controllers\TransportController as TransportBackend; use App\Http\Resources\CheckinSuccessResource; use App\Http\Resources\StationResource; diff --git a/app/Http/Controllers/Backend/Transport/StationController.php b/app/Http/Controllers/Backend/Transport/StationController.php index ca61846c4..4318505ec 100644 --- a/app/Http/Controllers/Backend/Transport/StationController.php +++ b/app/Http/Controllers/Backend/Transport/StationController.php @@ -2,9 +2,9 @@ namespace App\Http\Controllers\Backend\Transport; +use App\DataProviders\HafasController; use App\Exceptions\HafasException; use App\Http\Controllers\Controller; -use App\Http\Controllers\HafasController; use App\Http\Resources\StationResource; use App\Models\Checkin; use App\Models\Station; diff --git a/app/Http/Controllers/Frontend/Admin/CheckinController.php b/app/Http/Controllers/Frontend/Admin/CheckinController.php index 1df1e3eea..b21b102b5 100644 --- a/app/Http/Controllers/Frontend/Admin/CheckinController.php +++ b/app/Http/Controllers/Frontend/Admin/CheckinController.php @@ -10,14 +10,10 @@ use App\Exceptions\StationNotOnTripException; use App\Exceptions\TrainCheckinAlreadyExistException; use App\Http\Controllers\Backend\Transport\TrainCheckinController; -use App\Http\Controllers\HafasController; use App\Http\Controllers\TransportController as TransportBackend; use App\Hydrators\CheckinRequestHydrator; -use App\Jobs\PostStatusOnMastodon; use App\Models\Event; -use App\Models\Station; use App\Models\Status; -use App\Models\Stopover; use App\Models\User; use Carbon\Carbon; use Illuminate\Database\Eloquent\ModelNotFoundException; diff --git a/app/Http/Controllers/Frontend/Admin/EventController.php b/app/Http/Controllers/Frontend/Admin/EventController.php index 77f99619d..16348d7f5 100644 --- a/app/Http/Controllers/Frontend/Admin/EventController.php +++ b/app/Http/Controllers/Frontend/Admin/EventController.php @@ -2,11 +2,11 @@ namespace App\Http\Controllers\Frontend\Admin; +use App\DataProviders\HafasController; use App\Enum\EventRejectionReason; use App\Exceptions\HafasException; use App\Http\Controllers\Backend\Admin\EventController as AdminEventBackend; use App\Http\Controllers\Controller; -use App\Http\Controllers\HafasController; use App\Models\Event; use App\Models\EventSuggestion; use App\Notifications\EventSuggestionProcessed; diff --git a/app/Http/Controllers/TransportController.php b/app/Http/Controllers/TransportController.php index e8c7c8f48..93e06db0a 100644 --- a/app/Http/Controllers/TransportController.php +++ b/app/Http/Controllers/TransportController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\DataProviders\HafasController; use App\Enum\TravelType; use App\Exceptions\HafasException; use App\Http\Controllers\Backend\Transport\StationController; diff --git a/app/Jobs/RefreshStopover.php b/app/Jobs/RefreshStopover.php index 802b4ef82..3b68ee874 100644 --- a/app/Jobs/RefreshStopover.php +++ b/app/Jobs/RefreshStopover.php @@ -2,7 +2,7 @@ namespace App\Jobs; -use App\Http\Controllers\HafasController; +use App\DataProviders\HafasController; use App\Models\Stopover; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; diff --git a/app/Repositories/CheckinHydratorRepository.php b/app/Repositories/CheckinHydratorRepository.php index ed1e8cde7..8dc188a07 100644 --- a/app/Repositories/CheckinHydratorRepository.php +++ b/app/Repositories/CheckinHydratorRepository.php @@ -2,8 +2,8 @@ namespace App\Repositories; +use App\DataProviders\HafasController; use App\Exceptions\HafasException; -use App\Http\Controllers\HafasController; use App\Models\Event; use App\Models\Station; use App\Models\Stopover; diff --git a/database/migrations/2021_01_26_203951_create_train_stopovers_table.php b/database/migrations/2021_01_26_203951_create_train_stopovers_table.php index 4370c7027..16fb0b2dd 100644 --- a/database/migrations/2021_01_26_203951_create_train_stopovers_table.php +++ b/database/migrations/2021_01_26_203951_create_train_stopovers_table.php @@ -1,6 +1,6 @@ stop); + $hafasStop = StationRepository::parseHafasStopObject($stopover->stop); Stopover::updateOrCreate( [ diff --git a/tests/Feature/StationSearchTest.php b/tests/Feature/StationSearchTest.php index 795c2da3b..b321596b7 100644 --- a/tests/Feature/StationSearchTest.php +++ b/tests/Feature/StationSearchTest.php @@ -2,9 +2,9 @@ namespace Tests\Feature; +use App\DataProviders\HafasController; use App\Exceptions\HafasException; use App\Http\Controllers\Backend\Transport\StationController; -use App\Http\Controllers\HafasController; use App\Models\Station; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -66,7 +66,7 @@ public function testGetNearbyStations(): void { self::HANNOVER_HBF['location']['longitude']); $this->assertEquals(self::HANNOVER_HBF['name'], $result[0]->name); - $this->assertEquals(421, $result[0]->distance,); + $this->assertEquals(421, $result[0]->distance); } public function testGetNearbyStationFails(): void { diff --git a/tests/Feature/Transport/BackendCheckinTest.php b/tests/Feature/Transport/BackendCheckinTest.php index 9099086e0..6a766100d 100644 --- a/tests/Feature/Transport/BackendCheckinTest.php +++ b/tests/Feature/Transport/BackendCheckinTest.php @@ -2,12 +2,12 @@ namespace Tests\Feature\Transport; +use App\DataProviders\HafasController; use App\Enum\TravelType; use App\Exceptions\CheckInCollisionException; use App\Exceptions\HafasException; use App\Exceptions\StationNotOnTripException; use App\Http\Controllers\Backend\Transport\TrainCheckinController; -use App\Http\Controllers\HafasController; use App\Http\Controllers\TransportController; use App\Models\Stopover; use App\Models\User; diff --git a/tests/Feature/Transport/TransportStatsTest.php b/tests/Feature/Transport/TransportStatsTest.php index 39c112968..1d3429e48 100644 --- a/tests/Feature/Transport/TransportStatsTest.php +++ b/tests/Feature/Transport/TransportStatsTest.php @@ -2,23 +2,12 @@ namespace Feature\Transport; -use App\Enum\TravelType; -use App\Exceptions\CheckInCollisionException; -use App\Exceptions\HafasException; -use App\Exceptions\StationNotOnTripException; -use App\Http\Controllers\API\v1\LikesController; use App\Http\Controllers\Backend\Stats\TransportStatsController; -use App\Http\Controllers\Backend\Transport\TrainCheckinController; -use App\Http\Controllers\HafasController; use App\Http\Controllers\StatusController as StatusBackend; -use App\Http\Controllers\TransportController; use App\Models\Checkin; -use App\Models\Stopover; use App\Models\User; use Carbon\Carbon; use Illuminate\Foundation\Testing\RefreshDatabase; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Http; use Tests\FeatureTestCase; class TransportStatsTest extends FeatureTestCase diff --git a/tests/Feature/Webhooks/WebhookStatusTest.php b/tests/Feature/Webhooks/WebhookStatusTest.php index e48effe6e..5cc21c792 100644 --- a/tests/Feature/Webhooks/WebhookStatusTest.php +++ b/tests/Feature/Webhooks/WebhookStatusTest.php @@ -2,12 +2,12 @@ namespace Tests\Feature\Webhooks; +use App\DataProviders\HafasController; use App\Dto\Internal\CheckInRequestDto; use App\Enum\Business; use App\Enum\StatusVisibility; use App\Enum\WebhookEvent; use App\Http\Controllers\Backend\Transport\TrainCheckinController; -use App\Http\Controllers\HafasController; use App\Http\Controllers\StatusController; use App\Http\Resources\StatusResource; use App\Jobs\MonitoredCallWebhookJob; @@ -34,7 +34,7 @@ public function testWebhookSendingOnStatusCreation(): void { Bus::assertDispatched(function(MonitoredCallWebhookJob $job) use ($status) { assertEquals([ 'event' => WebhookEvent::CHECKIN_CREATE->value, - 'status' => new StatusResource($status), + 'status' => new StatusResource($status), ], $job->payload); return true; }); @@ -61,7 +61,7 @@ public function testWebhookSendingOnStatusBodyChange() { $job->payload['event'] ); assertEquals($status->id, $job->payload['status']['id']); - assertEquals('New Example Body', $job->payload['status']['body'],); + assertEquals('New Example Body', $job->payload['status']['body']); return true; }); } @@ -93,14 +93,14 @@ public function testWebhookSendingOnDestinationChange() { $user = User::factory()->create(); $client = $this->createWebhookClient($user); $this->createWebhook($user, $client, [WebhookEvent::CHECKIN_UPDATE]); - $status = $this->createStatus($user); - $checkin = $status->checkin()->first(); - $trip = TrainCheckinController::getHafasTrip( + $status = $this->createStatus($user); + $checkin = $status->checkin()->first(); + $trip = TrainCheckinController::getHafasTrip( tripId: self::TRIP_ID, lineName: self::ICE802['line']['name'], startId: self::FRANKFURT_HBF['id'] ); - $aachen = $trip->stopovers->where('station.ibnr', self::AACHEN_HBF['id'])->first(); + $aachen = $trip->stopovers->where('station.ibnr', self::AACHEN_HBF['id'])->first(); TrainCheckinController::changeDestination($checkin, $aachen); Bus::assertDispatched(function(MonitoredCallWebhookJob $job) use ($status) { From 4133d145e1750aeb06ba88d5b382e431da60c2c7 Mon Sep 17 00:00:00 2001 From: Levin Date: Wed, 11 Dec 2024 20:48:15 +0100 Subject: [PATCH 02/11] de-tangle the hafascontroller a bit more --- app/Console/Commands/RefreshCurrentTrips.php | 3 +- app/DataProviders/DbRestInterface.php | 15 +- app/DataProviders/HafasController.php | 143 +----------------- app/DataProviders/HafasStopoverService.php | 91 +++++++++++ app/Jobs/RefreshStopover.php | 4 +- .../Feature/Transport/BackendCheckinTest.php | 19 +-- tests/Feature/Webhooks/WebhookStatusTest.php | 6 +- tests/TestHelpers/HafasHelpers.php | 46 ++++++ 8 files changed, 173 insertions(+), 154 deletions(-) create mode 100644 app/DataProviders/HafasStopoverService.php create mode 100644 tests/TestHelpers/HafasHelpers.php diff --git a/app/Console/Commands/RefreshCurrentTrips.php b/app/Console/Commands/RefreshCurrentTrips.php index ca63aaa8c..19cf43e2a 100644 --- a/app/Console/Commands/RefreshCurrentTrips.php +++ b/app/Console/Commands/RefreshCurrentTrips.php @@ -3,6 +3,7 @@ namespace App\Console\Commands; use App\DataProviders\HafasController; +use App\DataProviders\HafasStopoverService; use App\Enum\TripSource; use App\Exceptions\HafasException; use App\Models\Checkin; @@ -54,7 +55,7 @@ public function handle(): int { $trip->update(['last_refreshed' => now()]); $rawHafas = HafasController::fetchRawHafasTrip($trip->trip_id, $trip->linename); - $updatedCounts = HafasController::refreshStopovers($rawHafas); + $updatedCounts = HafasStopoverService::refreshStopovers($rawHafas); $this->info('Updated ' . $updatedCounts->stopovers . ' stopovers.'); //set duration for refreshed trips to null, so it will be recalculated diff --git a/app/DataProviders/DbRestInterface.php b/app/DataProviders/DbRestInterface.php index faf7b5239..3e47b71f8 100644 --- a/app/DataProviders/DbRestInterface.php +++ b/app/DataProviders/DbRestInterface.php @@ -2,15 +2,26 @@ namespace App\DataProviders; -use App\Models\Stopover; +use App\Enum\TravelType; +use App\Models\Station; +use Carbon\Carbon; interface DbRestInterface { - public static function refreshStopover(Stopover $stopover): void; public static function fetchHafasTrip(string $tripID, string $lineName); public static function fetchRawHafasTrip(string $tripId, string $lineName); public static function getStations(string $query, int $results); + + public static function getDepartures(Station $station, Carbon $when, int $duration, TravelType $type, bool $localtime); + + public static function fetchDepartures(Station $station, Carbon $when, int $duration, TravelType $type, bool $skipTimeShift); + + public static function getNearbyStations(float $latitude, float $longitude, int $results); + + public static function getStationByRilIdentifier(string $rilIdentifier); + + public static function getStationsByFuzzyRilIdentifier(string $rilIdentifier); } diff --git a/app/DataProviders/HafasController.php b/app/DataProviders/HafasController.php index 74675ed17..23b1e6694 100644 --- a/app/DataProviders/HafasController.php +++ b/app/DataProviders/HafasController.php @@ -22,11 +22,10 @@ use Illuminate\Support\Facades\Log; use JsonException; use PDOException; -use stdClass; abstract class HafasController extends Controller implements DbRestInterface { - public static function getHttpClient(): PendingRequest { + private static function getHttpClient(): PendingRequest { return Http::baseUrl(config('trwl.db_rest')) ->timeout(config('trwl.db_rest_timeout')); } @@ -36,18 +35,18 @@ public static function getStationByRilIdentifier(string $rilIdentifier): ?Statio if ($station !== null) { return $station; } + try { $response = self::getHttpClient() ->get("/stations/$rilIdentifier"); - if (!$response->ok() || empty($response->body()) || $response->body() === '[]') { - return null; + if ($response->ok() && !empty($response->body()) && $response->body() !== '[]') { + $data = json_decode($response->body(), false, 512, JSON_THROW_ON_ERROR); + $station = StationRepository::parseHafasStopObject($data); } - $data = json_decode($response->body(), false, 512, JSON_THROW_ON_ERROR); - return StationRepository::parseHafasStopObject($data); } catch (Exception $exception) { report($exception); } - return null; + return $station; } public static function getStationsByFuzzyRilIdentifier(string $rilIdentifier): ?Collection { @@ -234,56 +233,6 @@ public static function getDepartures( } } - /** - * Get the Stopover Model from Database - * - * @param int $ibnr - * @param string|null $name - * @param float|null $latitude - * @param float|null $longitude - * - * @return Station - * @throws HafasException - */ - public static function getStation( - int $ibnr, - string $name = null, - float $latitude = null, - float $longitude = null - ): Station { - - if ($name === null || $latitude === null || $longitude === null) { - $dbStation = Station::where('ibnr', $ibnr)->first(); - return $dbStation ?? self::fetchStation($ibnr); - } - return Station::updateOrCreate([ - 'ibnr' => $ibnr - ], [ - 'name' => $name, - 'latitude' => $latitude, - 'longitude' => $longitude - ]); - } - - /** - * Fetch from HAFAS - * - * @param int $ibnr - * - * @return Station - * @throws HafasException - */ - private static function fetchStation(int $ibnr): Station { - $response = self::getHttpClient()->get("/stops/$ibnr"); - - if (!$response->ok()) { - throw new HafasException($response->reason()); - } - - $data = json_decode($response->body()); - return StationRepository::parseHafasStopObject($data); - } - /** * @throws HafasException|JsonException */ @@ -419,84 +368,4 @@ public static function fetchHafasTrip(string $tripID, string $lineName): Trip { } return $trip; } - - public static function refreshStopovers(stdClass $rawHafas): stdClass { - $stopoversUpdated = 0; - $payloadArrival = []; - $payloadDeparture = []; - $payloadCancelled = []; - foreach ($rawHafas->stopovers ?? [] as $stopover) { - if (!isset($stopover->arrivalDelay) && !isset($stopover->departureDelay) && !isset($stopover->cancelled)) { - continue; // No realtime data present for this stopover, keep existing data - } - - $stop = Repositories\StationRepository::parseHafasStopObject($stopover->stop); - $arrivalPlanned = Carbon::parse($stopover->plannedArrival)->tz(config('app.timezone')); - $departurePlanned = Carbon::parse($stopover->plannedDeparture)->tz(config('app.timezone')); - - $basePayload = [ - 'trip_id' => $rawHafas->id, - 'train_station_id' => $stop->id, - 'arrival_planned' => isset($stopover->plannedArrival) ? $arrivalPlanned : $departurePlanned, - 'departure_planned' => isset($stopover->plannedDeparture) ? $departurePlanned : $arrivalPlanned, - ]; - - if (isset($stopover->arrivalDelay) && isset($stopover->arrival)) { - $arrivalReal = Carbon::parse($stopover->arrival)->tz(config('app.timezone')); - $payloadArrival[] = array_merge($basePayload, ['arrival_real' => $arrivalReal]); - } - - if (isset($stopover->departureDelay) && isset($stopover->departure)) { - $departureReal = Carbon::parse($stopover->departure)->tz(config('app.timezone')); - $payloadDeparture[] = array_merge($basePayload, ['departure_real' => $departureReal]); - } - - // In case of cancellation, arrivalDelay/departureDelay will be null while the cancelled attribute will be present and true - // If cancelled is false / missing while other RT data is present (see initial if expression), it will be upserted to false - // This behavior is required for potential withdrawn cancellations - $payloadCancelled[] = array_merge($basePayload, ['cancelled' => $stopover->cancelled ?? false]); - - $stopoversUpdated++; - } - - $key = ['trip_id', 'train_station_id', 'departure_planned', 'arrival_planned']; - - return (object) [ - "stopovers" => $stopoversUpdated, - "rows" => [ - "arrival" => Stopover::upsert($payloadArrival, $key, ['arrival_real']), - "departure" => Stopover::upsert($payloadDeparture, $key, ['departure_real']), - "cancelled" => Stopover::upsert($payloadCancelled, $key, ['cancelled']) - ] - ]; - } - - /** - * This function is used to refresh the departure of a trip, if the planned_departure is in the past and no - * real-time data is given. The HAFAS stationboard gives us this real-time data even for trips in the past, so give - * it a chance. - * - * This function should be called in an async job, if not needed instantly. - * - * @param Stopover $stopover - * - * @return void - * @throws HafasException - */ - public static function refreshStopover(Stopover $stopover): void { - $departure = self::getDepartures( - station: $stopover->station, - when: $stopover->departure_planned, - )->filter(function(stdClass $trip) use ($stopover) { - return $trip->tripId === $stopover->trip_id; - })->first(); - - if ($departure === null || $departure->when === null || $departure->plannedWhen === $departure->when) { - return; //do nothing, if the trip isn't found. - } - - $stopover->update([ - 'departure_real' => Carbon::parse($departure->when), - ]); - } } diff --git a/app/DataProviders/HafasStopoverService.php b/app/DataProviders/HafasStopoverService.php new file mode 100644 index 000000000..7930b6ae3 --- /dev/null +++ b/app/DataProviders/HafasStopoverService.php @@ -0,0 +1,91 @@ +stopovers ?? [] as $stopover) { + if (!isset($stopover->arrivalDelay) && !isset($stopover->departureDelay) && !isset($stopover->cancelled)) { + continue; // No realtime data present for this stopover, keep existing data + } + + $stop = Repositories\StationRepository::parseHafasStopObject($stopover->stop); + $arrivalPlanned = Carbon::parse($stopover->plannedArrival)->tz(config('app.timezone')); + $departurePlanned = Carbon::parse($stopover->plannedDeparture)->tz(config('app.timezone')); + + $basePayload = [ + 'trip_id' => $rawHafas->id, + 'train_station_id' => $stop->id, + 'arrival_planned' => isset($stopover->plannedArrival) ? $arrivalPlanned : $departurePlanned, + 'departure_planned' => isset($stopover->plannedDeparture) ? $departurePlanned : $arrivalPlanned, + ]; + + if (isset($stopover->arrivalDelay) && isset($stopover->arrival)) { + $arrivalReal = Carbon::parse($stopover->arrival)->tz(config('app.timezone')); + $payloadArrival[] = array_merge($basePayload, ['arrival_real' => $arrivalReal]); + } + + if (isset($stopover->departureDelay) && isset($stopover->departure)) { + $departureReal = Carbon::parse($stopover->departure)->tz(config('app.timezone')); + $payloadDeparture[] = array_merge($basePayload, ['departure_real' => $departureReal]); + } + + // In case of cancellation, arrivalDelay/departureDelay will be null while the cancelled attribute will be present and true + // If cancelled is false / missing while other RT data is present (see initial if expression), it will be upserted to false + // This behavior is required for potential withdrawn cancellations + $payloadCancelled[] = array_merge($basePayload, ['cancelled' => $stopover->cancelled ?? false]); + + $stopoversUpdated++; + } + + $key = ['trip_id', 'train_station_id', 'departure_planned', 'arrival_planned']; + + return (object) [ + "stopovers" => $stopoversUpdated, + "rows" => [ + "arrival" => Stopover::upsert($payloadArrival, $key, ['arrival_real']), + "departure" => Stopover::upsert($payloadDeparture, $key, ['departure_real']), + "cancelled" => Stopover::upsert($payloadCancelled, $key, ['cancelled']) + ] + ]; + } + + /** + * This function is used to refresh the departure of a trip, if the planned_departure is in the past and no + * real-time data is given. The HAFAS stationboard gives us this real-time data even for trips in the past, so give + * it a chance. + * + * This function should be called in an async job, if not needed instantly. + * + * @param Stopover $stopover + * + * @return void + * @throws HafasException + */ + public static function refreshStopover(Stopover $stopover): void { + $departure = HafasController::getDepartures( + station: $stopover->station, + when: $stopover->departure_planned, + )->filter(function(stdClass $trip) use ($stopover) { + return $trip->tripId === $stopover->trip_id; + })->first(); + + if ($departure === null || $departure->when === null || $departure->plannedWhen === $departure->when) { + return; //do nothing, if the trip isn't found. + } + + $stopover->update([ + 'departure_real' => Carbon::parse($departure->when), + ]); + } +} diff --git a/app/Jobs/RefreshStopover.php b/app/Jobs/RefreshStopover.php index 3b68ee874..83e979c49 100644 --- a/app/Jobs/RefreshStopover.php +++ b/app/Jobs/RefreshStopover.php @@ -2,7 +2,7 @@ namespace App\Jobs; -use App\DataProviders\HafasController; +use App\DataProviders\HafasStopoverService; use App\Models\Stopover; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -22,6 +22,6 @@ public function __construct(Stopover $stopover) { } public function handle(): void { - HafasController::refreshStopover($this->stopover); + HafasStopoverService::refreshStopover($this->stopover); } } diff --git a/tests/Feature/Transport/BackendCheckinTest.php b/tests/Feature/Transport/BackendCheckinTest.php index 6a766100d..c035998ec 100644 --- a/tests/Feature/Transport/BackendCheckinTest.php +++ b/tests/Feature/Transport/BackendCheckinTest.php @@ -17,6 +17,7 @@ use Illuminate\Support\Facades\Http; use Tests\FeatureTestCase; use Tests\Helpers\CheckinRequestTestHydrator; +use Tests\TestHelpers\HafasHelpers; class BackendCheckinTest extends FeatureTestCase { @@ -32,7 +33,7 @@ public function testStationNotOnTripException() { ]); $user = User::factory()->create(); - $stationHannover = HafasController::getStation(8000152); + $stationHannover = HafasHelpers::getStationById(8000152); $departures = HafasController::getDepartures( station: $stationHannover, when: Carbon::parse('2023-01-12 08:00'), @@ -49,7 +50,7 @@ public function testStationNotOnTripException() { $this->expectException(StationNotOnTripException::class); $dto = (new CheckinRequestTestHydrator($user))->hydrateFromStopovers($trip, $originStopover, null); - $dto->setDestination(HafasController::getStation(8000001)) + $dto->setDestination(HafasHelpers::getStationById(8000001)) ->setArrival($originStopover->departure_planned); TrainCheckinController::checkin($dto); } @@ -63,7 +64,7 @@ public function testSwitchedOriginAndDestinationShouldThrowException() { ]); $user = User::factory()->create(); - $station = HafasController::getStation(8000105); + $station = HafasHelpers::getStationById(8000105); $departures = HafasController::getDepartures( station: $station, when: Carbon::parse('2023-01-12 08:00'), @@ -97,7 +98,7 @@ public function testDuplicateCheckinsShouldThrowException() { ]); $user = User::factory()->create(); - $station = HafasController::getStation(8000105); + $station = HafasHelpers::getStationById(8000105); $departures = HafasController::getDepartures( station: $station, when: Carbon::parse('2023-01-12 08:00'), @@ -205,7 +206,7 @@ public function testCheckinAtBerlinRingbahnRollingOverSuedkreuz(): void { // First: Get a train that's fine for our stuff // The 10:00 train actually quits at Südkreuz, but the 10:05 does not. - $station = HafasController::getStation(8089110); + $station = HafasHelpers::getStationById(8089110); $departures = HafasController::getDepartures( station: $station, when: Carbon::parse('2023-01-16 10:00'), @@ -258,7 +259,7 @@ public function testDistanceCalculationOnRingLinesForFirstOccurrence(): void { ]); $user = User::factory()->create(); - $stationPlantagenPotsdam = HafasController::getStation(736165); + $stationPlantagenPotsdam = HafasHelpers::getStationById(736165); $departures = HafasController::getDepartures( station: $stationPlantagenPotsdam, when: Carbon::parse('2023-01-16 10:00'), @@ -312,7 +313,7 @@ public function testDistanceCalculationOnRingLinesForSecondOccurrence(): void { ]); $user = User::factory()->create(); - $stationPlantagenPotsdam = HafasController::getStation(736165); + $stationPlantagenPotsdam = HafasHelpers::getStationById(736165); $departures = HafasController::getDepartures( station: $stationPlantagenPotsdam, when: Carbon::parse('2023-01-16 10:00'), @@ -365,7 +366,7 @@ public function testBusAirAtFrankfurtAirport(): void { ]); $user = User::factory()->create(); - $station = HafasController::getStation(102932); // Flughafen Terminal 1, Frankfurt a.M. + $station = HafasHelpers::getStationById(102932); // Flughafen Terminal 1, Frankfurt a.M. $departures = HafasController::getDepartures( station: $station, when: Carbon::parse('2023-01-16 10:00'), @@ -406,7 +407,7 @@ public function testChangeTripDestination(): void { ]); $user = User::factory()->create(); - $station = HafasController::getStation(self::FRANKFURT_HBF['id']); + $station = HafasHelpers::getStationById(self::FRANKFURT_HBF['id']); $departures = HafasController::getDepartures( station: $station, when: Carbon::parse('2023-01-16 08:00'), diff --git a/tests/Feature/Webhooks/WebhookStatusTest.php b/tests/Feature/Webhooks/WebhookStatusTest.php index 5cc21c792..cf6659df2 100644 --- a/tests/Feature/Webhooks/WebhookStatusTest.php +++ b/tests/Feature/Webhooks/WebhookStatusTest.php @@ -2,7 +2,6 @@ namespace Tests\Feature\Webhooks; -use App\DataProviders\HafasController; use App\Dto\Internal\CheckInRequestDto; use App\Enum\Business; use App\Enum\StatusVisibility; @@ -17,6 +16,7 @@ use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Http; use Tests\FeatureTestCase; +use Tests\TestHelpers\HafasHelpers; use function PHPUnit\Framework\assertEquals; class WebhookStatusTest extends FeatureTestCase @@ -199,8 +199,8 @@ protected function createStatus(User $user) { startId: self::FRANKFURT_HBF['id'] ); - $origin = HafasController::getStation(self::FRANKFURT_HBF['id']); - $destination = HafasController::getStation(self::HANNOVER_HBF['id']); + $origin = HafasHelpers::getStationById(self::FRANKFURT_HBF['id']); + $destination = HafasHelpers::getStationById(self::HANNOVER_HBF['id']); $dto = new CheckInRequestDto(); $dto->setUser($user) diff --git a/tests/TestHelpers/HafasHelpers.php b/tests/TestHelpers/HafasHelpers.php new file mode 100644 index 000000000..bf22998da --- /dev/null +++ b/tests/TestHelpers/HafasHelpers.php @@ -0,0 +1,46 @@ +first(); + return $dbStation ?? self::fetchStation($ibnr); + } + + /** + * Fetch from HAFAS + * + * @param int $ibnr + * + * @return Station + * @throws HafasException + */ + public static function fetchStation(int $ibnr): Station { + $httpClient = Http::baseUrl(config('trwl.db_rest')) + ->timeout(config('trwl.db_rest_timeout')); + $response = $httpClient->get("/stops/$ibnr"); + + if (!$response->ok()) { + throw new HafasException($response->reason()); + } + + $data = json_decode($response->body()); + return StationRepository::parseHafasStopObject($data); + } +} From c3a090685bd325966360a11893f68feea6a7c7e7 Mon Sep 17 00:00:00 2001 From: Levin Date: Wed, 11 Dec 2024 21:07:31 +0100 Subject: [PATCH 03/11] Something, something, factory --- app/Console/Commands/RefreshCurrentTrips.php | 3 +- app/DataProviders/DataProviderFactory.php | 41 +++++++++++++++++++ ...nterface.php => DataProviderInterface.php} | 5 +-- app/DataProviders/HafasController.php | 2 +- app/DataProviders/HafasStopoverService.php | 2 +- .../Controllers/API/v1/EventController.php | 3 +- .../API/v1/TransportController.php | 5 ++- .../Backend/Transport/StationController.php | 5 ++- .../Frontend/Admin/EventController.php | 7 ++-- app/Http/Controllers/TransportController.php | 9 ++-- .../CheckinHydratorRepository.php | 3 +- tests/Feature/StationSearchTest.php | 5 ++- .../Feature/Transport/BackendCheckinTest.php | 17 ++++---- 13 files changed, 78 insertions(+), 29 deletions(-) create mode 100644 app/DataProviders/DataProviderFactory.php rename app/DataProviders/{DbRestInterface.php => DataProviderInterface.php} (87%) diff --git a/app/Console/Commands/RefreshCurrentTrips.php b/app/Console/Commands/RefreshCurrentTrips.php index 19cf43e2a..be8eeed7f 100644 --- a/app/Console/Commands/RefreshCurrentTrips.php +++ b/app/Console/Commands/RefreshCurrentTrips.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\DataProviders\DataProviderFactory; use App\DataProviders\HafasController; use App\DataProviders\HafasStopoverService; use App\Enum\TripSource; @@ -54,7 +55,7 @@ public function handle(): int { $this->info('Refreshing trip ' . $trip->trip_id . ' (' . $trip->linename . ')...'); $trip->update(['last_refreshed' => now()]); - $rawHafas = HafasController::fetchRawHafasTrip($trip->trip_id, $trip->linename); + $rawHafas = (new DataProviderFactory)->create(HafasController::class)::fetchRawHafasTrip($trip->trip_id, $trip->linename); $updatedCounts = HafasStopoverService::refreshStopovers($rawHafas); $this->info('Updated ' . $updatedCounts->stopovers . ' stopovers.'); diff --git a/app/DataProviders/DataProviderFactory.php b/app/DataProviders/DataProviderFactory.php new file mode 100644 index 000000000..885eccf45 --- /dev/null +++ b/app/DataProviders/DataProviderFactory.php @@ -0,0 +1,41 @@ + $class + * + * @return T + * @throws InvalidArgumentException + */ + public static function createDataProvider(string $class) { + $self = new self(); + + return $self->create($class); + } + + /** + * @template T of DataProviderInterface + * @param class-string $class + * + * @return T + * @throws InvalidArgumentException + */ + public function create(string $class) { + $this->checkClass($class); + + return new $class(); + } + + private function checkClass(string $class): void { + // check instance of DataProviderInterface + if (!class_implements($class, DataProviderInterface::class)) { + throw new InvalidArgumentException('Class must implement DataProviderInterface'); + } + } +} diff --git a/app/DataProviders/DbRestInterface.php b/app/DataProviders/DataProviderInterface.php similarity index 87% rename from app/DataProviders/DbRestInterface.php rename to app/DataProviders/DataProviderInterface.php index 3e47b71f8..b8cfd85bc 100644 --- a/app/DataProviders/DbRestInterface.php +++ b/app/DataProviders/DataProviderInterface.php @@ -6,16 +6,15 @@ use App\Models\Station; use Carbon\Carbon; -interface DbRestInterface +interface DataProviderInterface { - public static function fetchHafasTrip(string $tripID, string $lineName); public static function fetchRawHafasTrip(string $tripId, string $lineName); public static function getStations(string $query, int $results); - public static function getDepartures(Station $station, Carbon $when, int $duration, TravelType $type, bool $localtime); + public static function getDepartures(Station $station, Carbon $when, int $duration = 15, TravelType $type = null, bool $localtime = false); public static function fetchDepartures(Station $station, Carbon $when, int $duration, TravelType $type, bool $skipTimeShift); diff --git a/app/DataProviders/HafasController.php b/app/DataProviders/HafasController.php index 23b1e6694..80de81a9b 100644 --- a/app/DataProviders/HafasController.php +++ b/app/DataProviders/HafasController.php @@ -23,7 +23,7 @@ use JsonException; use PDOException; -abstract class HafasController extends Controller implements DbRestInterface +class HafasController extends Controller implements DataProviderInterface { private static function getHttpClient(): PendingRequest { return Http::baseUrl(config('trwl.db_rest')) diff --git a/app/DataProviders/HafasStopoverService.php b/app/DataProviders/HafasStopoverService.php index 7930b6ae3..e802b4e46 100644 --- a/app/DataProviders/HafasStopoverService.php +++ b/app/DataProviders/HafasStopoverService.php @@ -73,7 +73,7 @@ public static function refreshStopovers(stdClass $rawHafas): stdClass { * @throws HafasException */ public static function refreshStopover(Stopover $stopover): void { - $departure = HafasController::getDepartures( + $departure = (new DataProviderFactory)->create(HafasController::class)::getDepartures( station: $stopover->station, when: $stopover->departure_planned, )->filter(function(stdClass $trip) use ($stopover) { diff --git a/app/Http/Controllers/API/v1/EventController.php b/app/Http/Controllers/API/v1/EventController.php index 608e4aac4..07c571465 100644 --- a/app/Http/Controllers/API/v1/EventController.php +++ b/app/Http/Controllers/API/v1/EventController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\API\v1; +use App\DataProviders\DataProviderFactory; use App\DataProviders\HafasController; use App\Http\Controllers\Backend\EventController as EventBackend; use App\Http\Controllers\StatusController; @@ -250,7 +251,7 @@ public function suggest(Request $request): JsonResponse { ]); if (isset($validated['nearestStation'])) { - $stations = HafasController::getStations($validated['nearestStation'], 1); + $stations = (new DataProviderFactory)->create(HafasController::class)::getStations($validated['nearestStation'], 1); if (count($stations) === 0) { return $this->sendError(error: __('events.request.station_not_found'), code: 400); } diff --git a/app/Http/Controllers/API/v1/TransportController.php b/app/Http/Controllers/API/v1/TransportController.php index 9719a7ce0..0a82f610a 100644 --- a/app/Http/Controllers/API/v1/TransportController.php +++ b/app/Http/Controllers/API/v1/TransportController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\API\v1; +use App\DataProviders\DataProviderFactory; use App\DataProviders\HafasController; use App\Dto\Transport\Station as StationDto; use App\Enum\Business; @@ -153,7 +154,7 @@ public function getDepartures(Request $request, int $stationId): JsonResponse { $station = Station::findOrFail($stationId); try { - $departures = HafasController::getDepartures( + $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( station: $station, when: $timestamp, type: TravelType::tryFrom($validated['travelType'] ?? null), @@ -311,7 +312,7 @@ public function getNextStationByCoordinates(Request $request): JsonResponse { ]); try { - $nearestStation = HafasController::getNearbyStations( + $nearestStation = (new DataProviderFactory)->create(HafasController::class)::getNearbyStations( latitude: $validated['latitude'], longitude: $validated['longitude'], results: 1 diff --git a/app/Http/Controllers/Backend/Transport/StationController.php b/app/Http/Controllers/Backend/Transport/StationController.php index 4318505ec..fde0e72e1 100644 --- a/app/Http/Controllers/Backend/Transport/StationController.php +++ b/app/Http/Controllers/Backend/Transport/StationController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Backend\Transport; +use App\DataProviders\DataProviderFactory; use App\DataProviders\HafasController; use App\Exceptions\HafasException; use App\Http\Controllers\Controller; @@ -39,14 +40,14 @@ public static function lookupStation(string|int $query): Station { //Lookup by ril identifier if (!is_numeric($query) && strlen($query) <= 5 && ctype_upper($query)) { - $station = HafasController::getStationByRilIdentifier($query); + $station = (new DataProviderFactory)->create(HafasController::class)::getStationByRilIdentifier($query); if ($station !== null) { return $station; } } //Lookup HAFAS - $station = HafasController::getStations(query: $query, results: 1)->first(); + $station = (new DataProviderFactory)->create(HafasController::class)::getStations(query: $query, results: 1)->first(); if ($station !== null) { return $station; } diff --git a/app/Http/Controllers/Frontend/Admin/EventController.php b/app/Http/Controllers/Frontend/Admin/EventController.php index 16348d7f5..edaaa8084 100644 --- a/app/Http/Controllers/Frontend/Admin/EventController.php +++ b/app/Http/Controllers/Frontend/Admin/EventController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Frontend\Admin; +use App\DataProviders\DataProviderFactory; use App\DataProviders\HafasController; use App\Enum\EventRejectionReason; use App\Exceptions\HafasException; @@ -147,7 +148,7 @@ public function acceptSuggestion(Request $request): RedirectResponse { } if (isset($validated['nearest_station_name'])) { - $station = HafasController::getStations($validated['nearest_station_name'], 1)->first(); + $station = (new DataProviderFactory)->create(HafasController::class)::getStations($validated['nearest_station_name'], 1)->first(); if ($station === null) { return back()->with('alert-danger', 'Die Station konnte nicht gefunden werden.'); @@ -187,7 +188,7 @@ public function create(Request $request): RedirectResponse { $station = null; if (isset($validated['nearest_station_name'])) { - $station = HafasController::getStations($validated['nearest_station_name'], 1)->first(); + $station = (new DataProviderFactory)->create(HafasController::class)::getStations($validated['nearest_station_name'], 1)->first(); if ($station === null) { return back()->with('alert-danger', 'Die Station konnte nicht gefunden werden.'); @@ -219,7 +220,7 @@ public function edit(int $id, Request $request): RedirectResponse { if (strlen($validated['nearest_station_name'] ?? '') === 0) { $validated['station_id'] = null; } elseif ($validated['nearest_station_name'] && $validated['nearest_station_name'] !== $event->station->name) { - $station = HafasController::getStations($validated['nearest_station_name'], 1)->first(); + $station = (new DataProviderFactory)->create(HafasController::class)::getStations($validated['nearest_station_name'], 1)->first(); if ($station === null) { return back()->with('alert-danger', 'Die Station konnte nicht gefunden werden.'); diff --git a/app/Http/Controllers/TransportController.php b/app/Http/Controllers/TransportController.php index 93e06db0a..a0f6ece8f 100644 --- a/app/Http/Controllers/TransportController.php +++ b/app/Http/Controllers/TransportController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\DataProviders\DataProviderFactory; use App\DataProviders\HafasController; use App\Enum\TravelType; use App\Exceptions\HafasException; @@ -30,11 +31,11 @@ class TransportController extends Controller */ public static function getTrainStationAutocomplete(string $query): Collection { if (!is_numeric($query) && strlen($query) <= 5 && ctype_upper($query)) { - $stations = HafasController::getStationsByFuzzyRilIdentifier(rilIdentifier: $query); + $stations = (new DataProviderFactory)->create(HafasController::class)::getStationsByFuzzyRilIdentifier(rilIdentifier: $query); } if (!isset($stations) || $stations[0] === null) { - $stations = HafasController::getStations($query); + $stations = (new DataProviderFactory)->create(HafasController::class)::getStations($query); } return $stations->map(function(Station $station) { @@ -50,7 +51,7 @@ public static function getTrainStationAutocomplete(string $query): Collection { * * @return array * @throws HafasException - * @deprecated use HafasController::getDepartures(...) directly instead (-> less overhead) + * @deprecated use DataProviderInterface::getDepartures(...) directly instead (-> less overhead) * * @api v1 */ @@ -74,7 +75,7 @@ public static function getDepartures( 'next' => $when->clone()->addMinutes(15) ]; - $departures = HafasController::getDepartures( + $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( station: $station, when: $when, type: $travelType, diff --git a/app/Repositories/CheckinHydratorRepository.php b/app/Repositories/CheckinHydratorRepository.php index 8dc188a07..657176cf0 100644 --- a/app/Repositories/CheckinHydratorRepository.php +++ b/app/Repositories/CheckinHydratorRepository.php @@ -2,6 +2,7 @@ namespace App\Repositories; +use App\DataProviders\DataProviderFactory; use App\DataProviders\HafasController; use App\Exceptions\HafasException; use App\Models\Event; @@ -29,7 +30,7 @@ public function getHafasTrip(string $tripID, string $lineName): Trip { $trip = Trip::where('id', $tripID)->where('linename', $lineName)->first(); } $trip = $trip ?? Trip::where('trip_id', $tripID)->where('linename', $lineName)->first(); - return $trip ?? HafasController::fetchHafasTrip($tripID, $lineName); + return $trip ?? (new DataProviderFactory)->create(HafasController::class)::fetchHafasTrip($tripID, $lineName); } public function findEvent(int $id): ?Event { diff --git a/tests/Feature/StationSearchTest.php b/tests/Feature/StationSearchTest.php index b321596b7..012892181 100644 --- a/tests/Feature/StationSearchTest.php +++ b/tests/Feature/StationSearchTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature; +use App\DataProviders\DataProviderFactory; use App\DataProviders\HafasController; use App\Exceptions\HafasException; use App\Http\Controllers\Backend\Transport\StationController; @@ -61,7 +62,7 @@ public function testGetNearbyStations(): void { ["distance" => 421] )])]); - $result = HafasController::getNearbyStations( + $result = (new DataProviderFactory)->create(HafasController::class)::getNearbyStations( self::HANNOVER_HBF['location']['latitude'], self::HANNOVER_HBF['location']['longitude']); @@ -73,7 +74,7 @@ public function testGetNearbyStationFails(): void { Http::fake(Http::response(status: 503)); $this->assertThrows(function() { - HafasController::getNearbyStations( + (new DataProviderFactory)->create(HafasController::class)::getNearbyStations( self::HANNOVER_HBF['location']['latitude'], self::HANNOVER_HBF['location']['longitude']); }, HafasException::class); diff --git a/tests/Feature/Transport/BackendCheckinTest.php b/tests/Feature/Transport/BackendCheckinTest.php index c035998ec..ffcfe019d 100644 --- a/tests/Feature/Transport/BackendCheckinTest.php +++ b/tests/Feature/Transport/BackendCheckinTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature\Transport; +use App\DataProviders\DataProviderFactory; use App\DataProviders\HafasController; use App\Enum\TravelType; use App\Exceptions\CheckInCollisionException; @@ -34,7 +35,7 @@ public function testStationNotOnTripException() { $user = User::factory()->create(); $stationHannover = HafasHelpers::getStationById(8000152); - $departures = HafasController::getDepartures( + $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( station: $stationHannover, when: Carbon::parse('2023-01-12 08:00'), type: TravelType::EXPRESS, @@ -65,7 +66,7 @@ public function testSwitchedOriginAndDestinationShouldThrowException() { $user = User::factory()->create(); $station = HafasHelpers::getStationById(8000105); - $departures = HafasController::getDepartures( + $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( station: $station, when: Carbon::parse('2023-01-12 08:00'), type: TravelType::EXPRESS, @@ -99,7 +100,7 @@ public function testDuplicateCheckinsShouldThrowException() { $user = User::factory()->create(); $station = HafasHelpers::getStationById(8000105); - $departures = HafasController::getDepartures( + $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( station: $station, when: Carbon::parse('2023-01-12 08:00'), type: TravelType::EXPRESS, @@ -207,7 +208,7 @@ public function testCheckinAtBerlinRingbahnRollingOverSuedkreuz(): void { // First: Get a train that's fine for our stuff // The 10:00 train actually quits at Südkreuz, but the 10:05 does not. $station = HafasHelpers::getStationById(8089110); - $departures = HafasController::getDepartures( + $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( station: $station, when: Carbon::parse('2023-01-16 10:00'), ); @@ -260,7 +261,7 @@ public function testDistanceCalculationOnRingLinesForFirstOccurrence(): void { $user = User::factory()->create(); $stationPlantagenPotsdam = HafasHelpers::getStationById(736165); - $departures = HafasController::getDepartures( + $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( station: $stationPlantagenPotsdam, when: Carbon::parse('2023-01-16 10:00'), type: TravelType::TRAM, @@ -314,7 +315,7 @@ public function testDistanceCalculationOnRingLinesForSecondOccurrence(): void { $user = User::factory()->create(); $stationPlantagenPotsdam = HafasHelpers::getStationById(736165); - $departures = HafasController::getDepartures( + $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( station: $stationPlantagenPotsdam, when: Carbon::parse('2023-01-16 10:00'), ); @@ -367,7 +368,7 @@ public function testBusAirAtFrankfurtAirport(): void { $user = User::factory()->create(); $station = HafasHelpers::getStationById(102932); // Flughafen Terminal 1, Frankfurt a.M. - $departures = HafasController::getDepartures( + $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( station: $station, when: Carbon::parse('2023-01-16 10:00'), type: TravelType::BUS, @@ -408,7 +409,7 @@ public function testChangeTripDestination(): void { $user = User::factory()->create(); $station = HafasHelpers::getStationById(self::FRANKFURT_HBF['id']); - $departures = HafasController::getDepartures( + $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( station: $station, when: Carbon::parse('2023-01-16 08:00'), type: TravelType::EXPRESS, From 6ed73fe9c1d0e558c72063925eb40a35bfe911b4 Mon Sep 17 00:00:00 2001 From: Levin Date: Wed, 11 Dec 2024 21:38:10 +0100 Subject: [PATCH 04/11] less instantiation --- app/Console/Commands/RefreshCurrentTrips.php | 8 +- app/DataProviders/DataProviderFactory.php | 13 --- app/DataProviders/HafasStopoverService.php | 16 +++- app/Http/Controllers/API/v1/Controller.php | 10 +++ .../Controllers/API/v1/EventController.php | 4 +- .../API/v1/TransportController.php | 7 +- .../Backend/Transport/StationController.php | 35 -------- .../Frontend/Admin/CheckinController.php | 80 ++++++++++++++++++- .../FrontendTransportController.php | 29 +------ app/Http/Controllers/TransportController.php | 80 +++---------------- app/Jobs/RefreshStopover.php | 7 +- .../CheckinHydratorRepository.php | 5 +- tests/Feature/CheckinTest.php | 13 +-- tests/Feature/StationSearchTest.php | 25 ++++-- .../Feature/Transport/BackendCheckinTest.php | 27 ++++--- 15 files changed, 174 insertions(+), 185 deletions(-) diff --git a/app/Console/Commands/RefreshCurrentTrips.php b/app/Console/Commands/RefreshCurrentTrips.php index be8eeed7f..f6cc80afc 100644 --- a/app/Console/Commands/RefreshCurrentTrips.php +++ b/app/Console/Commands/RefreshCurrentTrips.php @@ -3,6 +3,7 @@ namespace App\Console\Commands; use App\DataProviders\DataProviderFactory; +use App\DataProviders\DataProviderInterface; use App\DataProviders\HafasController; use App\DataProviders\HafasStopoverService; use App\Enum\TripSource; @@ -17,6 +18,11 @@ class RefreshCurrentTrips extends Command protected $signature = 'trwl:refreshTrips'; protected $description = 'Refresh delay data from current active trips'; + private function getDataProvider(): DataProviderInterface { + // Probably only HafasController is needed here, because this Command is very Hafas specific + return (new DataProviderFactory)->create(HafasController::class); + } + public function handle(): int { $this->info('Getting trips to be refreshed...'); @@ -55,7 +61,7 @@ public function handle(): int { $this->info('Refreshing trip ' . $trip->trip_id . ' (' . $trip->linename . ')...'); $trip->update(['last_refreshed' => now()]); - $rawHafas = (new DataProviderFactory)->create(HafasController::class)::fetchRawHafasTrip($trip->trip_id, $trip->linename); + $rawHafas = $this->getDataProvider()::fetchRawHafasTrip($trip->trip_id, $trip->linename); $updatedCounts = HafasStopoverService::refreshStopovers($rawHafas); $this->info('Updated ' . $updatedCounts->stopovers . ' stopovers.'); diff --git a/app/DataProviders/DataProviderFactory.php b/app/DataProviders/DataProviderFactory.php index 885eccf45..b1ad9deb4 100644 --- a/app/DataProviders/DataProviderFactory.php +++ b/app/DataProviders/DataProviderFactory.php @@ -6,19 +6,6 @@ class DataProviderFactory { - /** - * @template T of DataProviderInterface - * @param class-string $class - * - * @return T - * @throws InvalidArgumentException - */ - public static function createDataProvider(string $class) { - $self = new self(); - - return $self->create($class); - } - /** * @template T of DataProviderInterface * @param class-string $class diff --git a/app/DataProviders/HafasStopoverService.php b/app/DataProviders/HafasStopoverService.php index e802b4e46..62e580f53 100644 --- a/app/DataProviders/HafasStopoverService.php +++ b/app/DataProviders/HafasStopoverService.php @@ -9,6 +9,18 @@ class HafasStopoverService { + private DataProviderInterface $dataProvider; + + /** + * @template T of DataProviderInterface + * @param class-string $dataProvider + * @param DataProviderFactory|null $dataProviderFactory + */ + public function __construct(string $dataProvider, ?DataProviderFactory $dataProviderFactory = null) { + $dataProviderFactory ??= new DataProviderFactory(); + $this->dataProvider = $dataProviderFactory->create($dataProvider); + } + public static function refreshStopovers(stdClass $rawHafas): stdClass { $stopoversUpdated = 0; $payloadArrival = []; @@ -72,8 +84,8 @@ public static function refreshStopovers(stdClass $rawHafas): stdClass { * @return void * @throws HafasException */ - public static function refreshStopover(Stopover $stopover): void { - $departure = (new DataProviderFactory)->create(HafasController::class)::getDepartures( + public function refreshStopover(Stopover $stopover): void { + $departure = $this->dataProvider::getDepartures( station: $stopover->station, when: $stopover->departure_planned, )->filter(function(stdClass $trip) use ($stopover) { diff --git a/app/Http/Controllers/API/v1/Controller.php b/app/Http/Controllers/API/v1/Controller.php index 484fe166a..87dfcaaa7 100644 --- a/app/Http/Controllers/API/v1/Controller.php +++ b/app/Http/Controllers/API/v1/Controller.php @@ -2,6 +2,9 @@ namespace App\Http\Controllers\API\v1; +use App\DataProviders\DataProviderFactory; +use App\DataProviders\DataProviderInterface; +use App\DataProviders\HafasController; use App\Models\OAuthClient; use App\Models\User; use Illuminate\Contracts\Auth\Authenticatable; @@ -96,6 +99,13 @@ */ class Controller extends \App\Http\Controllers\Controller { + protected DataProviderInterface $dataProvider; + + public function __construct() { + // todo: set data provider based on user settings + $this->dataProvider = (new DataProviderFactory())->create(HafasController::class); + } + public function sendResponse( mixed $data = null, int $code = 200, diff --git a/app/Http/Controllers/API/v1/EventController.php b/app/Http/Controllers/API/v1/EventController.php index 07c571465..14fc68a57 100644 --- a/app/Http/Controllers/API/v1/EventController.php +++ b/app/Http/Controllers/API/v1/EventController.php @@ -2,8 +2,6 @@ namespace App\Http\Controllers\API\v1; -use App\DataProviders\DataProviderFactory; -use App\DataProviders\HafasController; use App\Http\Controllers\Backend\EventController as EventBackend; use App\Http\Controllers\StatusController; use App\Http\Resources\EventDetailsResource; @@ -251,7 +249,7 @@ public function suggest(Request $request): JsonResponse { ]); if (isset($validated['nearestStation'])) { - $stations = (new DataProviderFactory)->create(HafasController::class)::getStations($validated['nearestStation'], 1); + $stations = $this->dataProvider::getStations($validated['nearestStation'], 1); if (count($stations) === 0) { return $this->sendError(error: __('events.request.station_not_found'), code: 400); } diff --git a/app/Http/Controllers/API/v1/TransportController.php b/app/Http/Controllers/API/v1/TransportController.php index 0a82f610a..f04170ecf 100644 --- a/app/Http/Controllers/API/v1/TransportController.php +++ b/app/Http/Controllers/API/v1/TransportController.php @@ -2,7 +2,6 @@ namespace App\Http\Controllers\API\v1; -use App\DataProviders\DataProviderFactory; use App\DataProviders\HafasController; use App\Dto\Transport\Station as StationDto; use App\Enum\Business; @@ -154,7 +153,7 @@ public function getDepartures(Request $request, int $stationId): JsonResponse { $station = Station::findOrFail($stationId); try { - $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( + $departures = $this->dataProvider::getDepartures( station: $station, when: $timestamp, type: TravelType::tryFrom($validated['travelType'] ?? null), @@ -312,7 +311,7 @@ public function getNextStationByCoordinates(Request $request): JsonResponse { ]); try { - $nearestStation = (new DataProviderFactory)->create(HafasController::class)::getNearbyStations( + $nearestStation = $this->dataProvider::getNearbyStations( latitude: $validated['latitude'], longitude: $validated['longitude'], results: 1 @@ -514,7 +513,7 @@ public function setHome(int $stationId): JsonResponse { */ public function getTrainStationAutocomplete(string $query): JsonResponse { try { - $trainAutocompleteResponse = TransportBackend::getTrainStationAutocomplete($query); + $trainAutocompleteResponse = (new TransportBackend(HafasController::class))->getTrainStationAutocomplete($query); return $this->sendResponse($trainAutocompleteResponse); } catch (HafasException) { return $this->sendError("There has been an error with our data provider", 503); diff --git a/app/Http/Controllers/Backend/Transport/StationController.php b/app/Http/Controllers/Backend/Transport/StationController.php index fde0e72e1..a7fd77e20 100644 --- a/app/Http/Controllers/Backend/Transport/StationController.php +++ b/app/Http/Controllers/Backend/Transport/StationController.php @@ -2,17 +2,12 @@ namespace App\Http\Controllers\Backend\Transport; -use App\DataProviders\DataProviderFactory; -use App\DataProviders\HafasController; -use App\Exceptions\HafasException; use App\Http\Controllers\Controller; use App\Http\Resources\StationResource; use App\Models\Checkin; -use App\Models\Station; use App\Models\Stopover; use App\Models\User; use App\Repositories\StationRepository; -use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\Resources\Json\AnonymousResourceCollection; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; @@ -25,36 +20,6 @@ public function __construct(?StationRepository $stationRepository = null) { $this->stationRepository = $stationRepository ?? new StationRepository(); } - /** - * @throws HafasException - * @throws ModelNotFoundException - */ - public static function lookupStation(string|int $query): Station { - //Lookup by station ibnr - if (is_numeric($query)) { - $station = Station::where('ibnr', $query)->first(); - if ($station !== null) { - return $station; - } - } - - //Lookup by ril identifier - if (!is_numeric($query) && strlen($query) <= 5 && ctype_upper($query)) { - $station = (new DataProviderFactory)->create(HafasController::class)::getStationByRilIdentifier($query); - if ($station !== null) { - return $station; - } - } - - //Lookup HAFAS - $station = (new DataProviderFactory)->create(HafasController::class)::getStations(query: $query, results: 1)->first(); - if ($station !== null) { - return $station; - } - - throw new ModelNotFoundException; - } - /** * Get the latest Stations the user is arrived. * diff --git a/app/Http/Controllers/Frontend/Admin/CheckinController.php b/app/Http/Controllers/Frontend/Admin/CheckinController.php index b21b102b5..16a16200a 100644 --- a/app/Http/Controllers/Frontend/Admin/CheckinController.php +++ b/app/Http/Controllers/Frontend/Admin/CheckinController.php @@ -2,6 +2,8 @@ namespace App\Http\Controllers\Frontend\Admin; +use App\DataProviders\DataProviderFactory; +use App\DataProviders\HafasController; use App\Enum\Business; use App\Enum\StatusVisibility; use App\Enum\TravelType; @@ -10,22 +12,96 @@ use App\Exceptions\StationNotOnTripException; use App\Exceptions\TrainCheckinAlreadyExistException; use App\Http\Controllers\Backend\Transport\TrainCheckinController; -use App\Http\Controllers\TransportController as TransportBackend; use App\Hydrators\CheckinRequestHydrator; use App\Models\Event; +use App\Models\Station; use App\Models\Status; use App\Models\User; use Carbon\Carbon; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Validation\Rules\Enum; use Illuminate\View\View; +use JetBrains\PhpStorm\ArrayShape; use Throwable; class CheckinController { + /** + * @throws HafasException + * @throws ModelNotFoundException + * @deprecated adapt admin panel to api endpoints + */ + public static function lookupStation(string|int $query): Station { + //Lookup by station ibnr + if (is_numeric($query)) { + $station = Station::where('ibnr', $query)->first(); + if ($station !== null) { + return $station; + } + } + + //Lookup by ril identifier + if (!is_numeric($query) && strlen($query) <= 5 && ctype_upper($query)) { + $station = (new DataProviderFactory)->create(HafasController::class)::getStationByRilIdentifier($query); + if ($station !== null) { + return $station; + } + } + + //Lookup HAFAS + $station = (new DataProviderFactory)->create(HafasController::class)::getStations(query: $query, results: 1)->first(); + if ($station !== null) { + return $station; + } + + throw new ModelNotFoundException; + } + + /** + * @param string|int $stationQuery + * @param Carbon|null $when + * @param TravelType|null $travelType + * @param bool $localtime + * + * @return array + * @throws HafasException + * @deprecated use DataProviderInterface::getDepartures(...) directly instead (-> less overhead) + */ + #[ArrayShape([ + 'station' => Station::class, + 'departures' => Collection::class, + 'times' => "array" + ])] + public static function getDepartures( + string|int $stationQuery, + Carbon $when = null, + TravelType $travelType = null, + bool $localtime = false + ): array { + $station = self::lookupStation($stationQuery); + + $when = $when ?? Carbon::now()->subMinutes(5); + $times = [ + 'now' => $when, + 'prev' => $when->clone()->subMinutes(15), + 'next' => $when->clone()->addMinutes(15) + ]; + + $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( + station: $station, + when: $when, + type: $travelType, + localtime: $localtime + )->sortBy(function($departure) { + return $departure->when ?? $departure->plannedWhen; + }); + + return ['station' => $station, 'departures' => $departures->values(), 'times' => $times]; + } public function renderStationboard(Request $request): View|RedirectResponse { $validated = $request->validate([ @@ -52,7 +128,7 @@ public function renderStationboard(Request $request): View|RedirectResponse { if (isset($validated['station'])) { try { - $trainStationboardResponse = TransportBackend::getDepartures( + $trainStationboardResponse = self::getDepartures( stationQuery: $validated['station'], when: $when, travelType: TravelType::tryFrom($validated['filter'] ?? null), diff --git a/app/Http/Controllers/FrontendTransportController.php b/app/Http/Controllers/FrontendTransportController.php index 136757016..2afef4779 100644 --- a/app/Http/Controllers/FrontendTransportController.php +++ b/app/Http/Controllers/FrontendTransportController.php @@ -2,33 +2,10 @@ namespace App\Http\Controllers; -use App\Dto\CheckinSuccess; -use App\Enum\Business; -use App\Enum\StatusVisibility; -use App\Enum\TravelType; -use App\Exceptions\Checkin\AlreadyCheckedInException; -use App\Exceptions\CheckInCollisionException; +use App\DataProviders\HafasController; use App\Exceptions\HafasException; -use App\Exceptions\StationNotOnTripException; -use App\Exceptions\TrainCheckinAlreadyExistException; -use App\Http\Controllers\Backend\Helper\StatusHelper; -use App\Http\Controllers\Backend\Transport\HomeController; -use App\Http\Controllers\Backend\Transport\StationController; -use App\Http\Controllers\Backend\Transport\TrainCheckinController; use App\Http\Controllers\TransportController as TransportBackend; -use App\Models\Event; -use App\Models\Station; -use App\Models\Stopover; -use App\Models\Trip; -use Carbon\Carbon; -use Illuminate\Contracts\Support\Renderable; -use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\JsonResponse; -use Illuminate\Http\RedirectResponse; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; -use Illuminate\Validation\Rules\Enum; -use Throwable; /** * @deprecated Content will be moved to the backend/frontend/API packages soon, please don't add new functions here! @@ -37,7 +14,9 @@ class FrontendTransportController extends Controller { public function TrainAutocomplete(string $station): JsonResponse { try { - $trainAutocompleteResponse = TransportBackend::getTrainStationAutocomplete($station); + //todo: adapt data provider to users preferences + $provider = new TransportBackend(HafasController::class); + $trainAutocompleteResponse = $provider->getTrainStationAutocomplete($station); return response()->json($trainAutocompleteResponse); } catch (HafasException $e) { abort(503, $e->getMessage()); diff --git a/app/Http/Controllers/TransportController.php b/app/Http/Controllers/TransportController.php index a0f6ece8f..b9378bb3e 100644 --- a/app/Http/Controllers/TransportController.php +++ b/app/Http/Controllers/TransportController.php @@ -3,10 +3,9 @@ namespace App\Http\Controllers; use App\DataProviders\DataProviderFactory; +use App\DataProviders\DataProviderInterface; use App\DataProviders\HafasController; -use App\Enum\TravelType; use App\Exceptions\HafasException; -use App\Http\Controllers\Backend\Transport\StationController; use App\Http\Resources\StationResource; use App\Models\Checkin; use App\Models\PolyLine; @@ -14,13 +13,21 @@ use App\Models\User; use Carbon\Carbon; use Illuminate\Support\Collection; -use JetBrains\PhpStorm\ArrayShape; /** * @deprecated Content will be moved to the backend/frontend/API packages soon, please don't add new functions here! */ class TransportController extends Controller { + private DataProviderInterface $dataProvider; + + /** + * @template T of DataProviderInterface + * @param class-string $dataProvider + */ + public function __construct(string $dataProvider) { + $this->dataProvider = (new DataProviderFactory())->create($dataProvider); + } /** * @param string $query @@ -29,7 +36,7 @@ class TransportController extends Controller * @throws HafasException * @api v1 */ - public static function getTrainStationAutocomplete(string $query): Collection { + public function getTrainStationAutocomplete(string $query): Collection { if (!is_numeric($query) && strlen($query) <= 5 && ctype_upper($query)) { $stations = (new DataProviderFactory)->create(HafasController::class)::getStationsByFuzzyRilIdentifier(rilIdentifier: $query); } @@ -43,71 +50,6 @@ public static function getTrainStationAutocomplete(string $query): Collection { }); } - /** - * @param string|int $stationQuery - * @param Carbon|null $when - * @param TravelType|null $travelType - * @param bool $localtime - * - * @return array - * @throws HafasException - * @deprecated use DataProviderInterface::getDepartures(...) directly instead (-> less overhead) - * - * @api v1 - */ - #[ArrayShape([ - 'station' => Station::class, - 'departures' => Collection::class, - 'times' => "array" - ])] - public static function getDepartures( - string|int $stationQuery, - Carbon $when = null, - TravelType $travelType = null, - bool $localtime = false - ): array { - $station = StationController::lookupStation($stationQuery); - - $when = $when ?? Carbon::now()->subMinutes(5); - $times = [ - 'now' => $when, - 'prev' => $when->clone()->subMinutes(15), - 'next' => $when->clone()->addMinutes(15) - ]; - - $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( - station: $station, - when: $when, - type: $travelType, - localtime: $localtime - )->sortBy(function($departure) { - return $departure->when ?? $departure->plannedWhen; - }); - - return ['station' => $station, 'departures' => $departures->values(), 'times' => $times]; - } - - // Train with cancelled stops show up in the stationboard sometimes with when == 0. - // However, they will have a scheduledWhen. This snippet will sort the departures - // by actualWhen or use scheduledWhen if actual is empty. - public static function sortByWhenOrScheduledWhen(array $departuresList): array { - uasort($departuresList, function($a, $b) { - $dateA = $a->when; - if ($dateA == null) { - $dateA = $a->scheduledWhen; - } - - $dateB = $b->when; - if ($dateB == null) { - $dateB = $b->scheduledWhen; - } - - return ($dateA < $dateB) ? -1 : 1; - }); - - return $departuresList; - } - /** * Check if there are colliding CheckIns * diff --git a/app/Jobs/RefreshStopover.php b/app/Jobs/RefreshStopover.php index 83e979c49..407bb3f0f 100644 --- a/app/Jobs/RefreshStopover.php +++ b/app/Jobs/RefreshStopover.php @@ -2,7 +2,9 @@ namespace App\Jobs; +use App\DataProviders\HafasController; use App\DataProviders\HafasStopoverService; +use App\Exceptions\HafasException; use App\Models\Stopover; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -21,7 +23,10 @@ public function __construct(Stopover $stopover) { $this->stopover = $stopover; } + /** + * @throws HafasException + */ public function handle(): void { - HafasStopoverService::refreshStopover($this->stopover); + (new HafasStopoverService(HafasController::class))->refreshStopover($this->stopover); } } diff --git a/app/Repositories/CheckinHydratorRepository.php b/app/Repositories/CheckinHydratorRepository.php index 657176cf0..005178564 100644 --- a/app/Repositories/CheckinHydratorRepository.php +++ b/app/Repositories/CheckinHydratorRepository.php @@ -26,11 +26,14 @@ public function getOneStation(string $searchKey, string|int $id): ?Station { * @throws JsonException */ public function getHafasTrip(string $tripID, string $lineName): Trip { + // todo: create trip IDs with a prefix, to distinguish between different data providers + $dataProvider = (new DataProviderFactory)->create(HafasController::class); + if (is_numeric($tripID)) { $trip = Trip::where('id', $tripID)->where('linename', $lineName)->first(); } $trip = $trip ?? Trip::where('trip_id', $tripID)->where('linename', $lineName)->first(); - return $trip ?? (new DataProviderFactory)->create(HafasController::class)::fetchHafasTrip($tripID, $lineName); + return $trip ?? $dataProvider::fetchHafasTrip($tripID, $lineName); } public function findEvent(int $id): ?Event { diff --git a/tests/Feature/CheckinTest.php b/tests/Feature/CheckinTest.php index 75f251e72..765d45f5c 100644 --- a/tests/Feature/CheckinTest.php +++ b/tests/Feature/CheckinTest.php @@ -2,23 +2,14 @@ namespace Tests\Feature; -use App\Dto\CheckinSuccess; -use App\Dto\Internal\CheckInRequestDto; -use App\Enum\Business; -use App\Enum\PointReason; -use App\Enum\StatusVisibility; -use App\Enum\TravelType; use App\Exceptions\CheckInCollisionException; use App\Exceptions\HafasException; -use App\Http\Controllers\Backend\Helper\StatusHelper; use App\Http\Controllers\Backend\Transport\TrainCheckinController; -use App\Http\Controllers\TransportController; -use App\Hydrators\CheckinRequestHydrator; +use App\Http\Controllers\Frontend\Admin\CheckinController; use App\Models\Station; use App\Models\Trip; use App\Models\User; use Carbon\Carbon; -use Illuminate\Database\Eloquent\Collection; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Http; use Tests\FeatureTestCase; @@ -43,7 +34,7 @@ public function stationboardTest(): void { $requestDate = Carbon::parse(self::DEPARTURE_TIME); - $trainStationboard = TransportController::getDepartures( + $trainStationboard = CheckinController::getDepartures( stationQuery: self::FRANKFURT_HBF['name'], when: $requestDate ); diff --git a/tests/Feature/StationSearchTest.php b/tests/Feature/StationSearchTest.php index 012892181..646a396d3 100644 --- a/tests/Feature/StationSearchTest.php +++ b/tests/Feature/StationSearchTest.php @@ -3,9 +3,10 @@ namespace Tests\Feature; use App\DataProviders\DataProviderFactory; +use App\DataProviders\DataProviderInterface; use App\DataProviders\HafasController; use App\Exceptions\HafasException; -use App\Http\Controllers\Backend\Transport\StationController; +use App\Http\Controllers\Frontend\Admin\CheckinController; use App\Models\Station; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -14,13 +15,21 @@ class StationSearchTest extends FeatureTestCase { + private DataProviderInterface $dataProvider; + + public function setUp(): void { + parent::setUp(); + $this->dataProvider = (new DataProviderFactory())->create(HafasController::class); + } + + use RefreshDatabase; public function testStringSearch(): void { $searchResults = [self::HANNOVER_HBF]; Http::fake(["*" => Http::response($searchResults)]); - $station = StationController::lookupStation(self::HANNOVER_HBF['name']); + $station = CheckinController::lookupStation(self::HANNOVER_HBF['name']); $this->assertEquals(self::HANNOVER_HBF['name'], $station->name); } @@ -28,14 +37,14 @@ public function testNameNotFound(): void { Http::fake(["*" => Http::response([], 200)]); $this->assertThrows(function() { - StationController::lookupStation("Bielefeld Hbf"); + CheckinController::lookupStation("Bielefeld Hbf"); }, ModelNotFoundException::class); } public function testDs100Search(): void { Http::fake(["*/stations/" . self::HANNOVER_HBF['ril100'] => Http::response(self::HANNOVER_HBF)]); - $station = StationController::lookupStation(self::HANNOVER_HBF['ril100']); + $station = CheckinController::lookupStation(self::HANNOVER_HBF['ril100']); $this->assertEquals(self::HANNOVER_HBF['name'], $station->name); } @@ -43,7 +52,7 @@ public function testDs100NotFound(): void { Http::fake(["*" => Http::response([], 200)]); $this->assertThrows(function() { - StationController::lookupStation("EBIL"); + CheckinController::lookupStation("EBIL"); }, ModelNotFoundException::class); } @@ -52,7 +61,7 @@ public function testIbnrLocalSearch(): void { $expected = Station::factory()->make(); $expected->save(); - $station = StationController::lookupStation(str($expected->ibnr)); + $station = CheckinController::lookupStation(str($expected->ibnr)); $this->assertEquals(Station::find($expected->id)->name, $station->name); } @@ -62,7 +71,7 @@ public function testGetNearbyStations(): void { ["distance" => 421] )])]); - $result = (new DataProviderFactory)->create(HafasController::class)::getNearbyStations( + $result = $this->dataProvider::getNearbyStations( self::HANNOVER_HBF['location']['latitude'], self::HANNOVER_HBF['location']['longitude']); @@ -74,7 +83,7 @@ public function testGetNearbyStationFails(): void { Http::fake(Http::response(status: 503)); $this->assertThrows(function() { - (new DataProviderFactory)->create(HafasController::class)::getNearbyStations( + $this->dataProvider::getNearbyStations( self::HANNOVER_HBF['location']['latitude'], self::HANNOVER_HBF['location']['longitude']); }, HafasException::class); diff --git a/tests/Feature/Transport/BackendCheckinTest.php b/tests/Feature/Transport/BackendCheckinTest.php index ffcfe019d..4ebeed85e 100644 --- a/tests/Feature/Transport/BackendCheckinTest.php +++ b/tests/Feature/Transport/BackendCheckinTest.php @@ -3,13 +3,14 @@ namespace Tests\Feature\Transport; use App\DataProviders\DataProviderFactory; +use App\DataProviders\DataProviderInterface; use App\DataProviders\HafasController; use App\Enum\TravelType; use App\Exceptions\CheckInCollisionException; use App\Exceptions\HafasException; use App\Exceptions\StationNotOnTripException; use App\Http\Controllers\Backend\Transport\TrainCheckinController; -use App\Http\Controllers\TransportController; +use App\Http\Controllers\Frontend\Admin\CheckinController; use App\Models\Stopover; use App\Models\User; use App\Repositories\CheckinHydratorRepository; @@ -22,6 +23,12 @@ class BackendCheckinTest extends FeatureTestCase { + private DataProviderInterface $dataProvider; + + public function setUp(): void { + parent::setUp(); + $this->dataProvider = (new DataProviderFactory())->create(HafasController::class); + } use RefreshDatabase; @@ -35,7 +42,7 @@ public function testStationNotOnTripException() { $user = User::factory()->create(); $stationHannover = HafasHelpers::getStationById(8000152); - $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( + $departures = $this->dataProvider::getDepartures( station: $stationHannover, when: Carbon::parse('2023-01-12 08:00'), type: TravelType::EXPRESS, @@ -66,7 +73,7 @@ public function testSwitchedOriginAndDestinationShouldThrowException() { $user = User::factory()->create(); $station = HafasHelpers::getStationById(8000105); - $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( + $departures = $this->dataProvider::getDepartures( station: $station, when: Carbon::parse('2023-01-12 08:00'), type: TravelType::EXPRESS, @@ -100,7 +107,7 @@ public function testDuplicateCheckinsShouldThrowException() { $user = User::factory()->create(); $station = HafasHelpers::getStationById(8000105); - $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( + $departures = $this->dataProvider::getDepartures( station: $station, when: Carbon::parse('2023-01-12 08:00'), type: TravelType::EXPRESS, @@ -141,7 +148,7 @@ public function testCheckinAtBus603Potsdam(): void { // First: Get a train that's fine for our stuff $timestamp = Carbon::parse("2023-01-15 10:15"); try { - $trainStationboard = TransportController::getDepartures( + $trainStationboard = CheckinController::getDepartures( stationQuery: 'Schloss Cecilienhof, Potsdam', when: $timestamp, travelType: TravelType::BUS @@ -208,7 +215,7 @@ public function testCheckinAtBerlinRingbahnRollingOverSuedkreuz(): void { // First: Get a train that's fine for our stuff // The 10:00 train actually quits at Südkreuz, but the 10:05 does not. $station = HafasHelpers::getStationById(8089110); - $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( + $departures = $this->dataProvider::getDepartures( station: $station, when: Carbon::parse('2023-01-16 10:00'), ); @@ -261,7 +268,7 @@ public function testDistanceCalculationOnRingLinesForFirstOccurrence(): void { $user = User::factory()->create(); $stationPlantagenPotsdam = HafasHelpers::getStationById(736165); - $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( + $departures = $this->dataProvider::getDepartures( station: $stationPlantagenPotsdam, when: Carbon::parse('2023-01-16 10:00'), type: TravelType::TRAM, @@ -315,7 +322,7 @@ public function testDistanceCalculationOnRingLinesForSecondOccurrence(): void { $user = User::factory()->create(); $stationPlantagenPotsdam = HafasHelpers::getStationById(736165); - $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( + $departures = $this->dataProvider::getDepartures( station: $stationPlantagenPotsdam, when: Carbon::parse('2023-01-16 10:00'), ); @@ -368,7 +375,7 @@ public function testBusAirAtFrankfurtAirport(): void { $user = User::factory()->create(); $station = HafasHelpers::getStationById(102932); // Flughafen Terminal 1, Frankfurt a.M. - $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( + $departures = $this->dataProvider::getDepartures( station: $station, when: Carbon::parse('2023-01-16 10:00'), type: TravelType::BUS, @@ -409,7 +416,7 @@ public function testChangeTripDestination(): void { $user = User::factory()->create(); $station = HafasHelpers::getStationById(self::FRANKFURT_HBF['id']); - $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( + $departures = $this->dataProvider::getDepartures( station: $station, when: Carbon::parse('2023-01-16 08:00'), type: TravelType::EXPRESS, From 6d086e7f39f6ac5d85a6a9be49b4cc0a5b831fa2 Mon Sep 17 00:00:00 2001 From: Levin Date: Wed, 11 Dec 2024 21:53:47 +0100 Subject: [PATCH 05/11] FULLY PREPARED FOR MULTIPLE DATA SOURCES --- app/Console/Commands/RefreshCurrentTrips.php | 2 +- app/DataProviders/DataProviderInterface.php | 16 ++--- app/DataProviders/HafasController.php | 71 ++++++++++--------- app/DataProviders/HafasStopoverService.php | 2 +- .../Repositories/StationRepository.php | 7 +- .../Controllers/API/v1/EventController.php | 2 +- .../API/v1/TransportController.php | 4 +- .../Frontend/Admin/CheckinController.php | 14 ++-- .../Frontend/Admin/EventController.php | 13 +++- app/Http/Controllers/TransportController.php | 5 +- .../CheckinHydratorRepository.php | 2 +- tests/Feature/CheckinTest.php | 2 +- tests/Feature/StationSearchTest.php | 7 +- .../Feature/Transport/BackendCheckinTest.php | 18 ++--- 14 files changed, 89 insertions(+), 76 deletions(-) diff --git a/app/Console/Commands/RefreshCurrentTrips.php b/app/Console/Commands/RefreshCurrentTrips.php index f6cc80afc..437e133ad 100644 --- a/app/Console/Commands/RefreshCurrentTrips.php +++ b/app/Console/Commands/RefreshCurrentTrips.php @@ -61,7 +61,7 @@ public function handle(): int { $this->info('Refreshing trip ' . $trip->trip_id . ' (' . $trip->linename . ')...'); $trip->update(['last_refreshed' => now()]); - $rawHafas = $this->getDataProvider()::fetchRawHafasTrip($trip->trip_id, $trip->linename); + $rawHafas = $this->getDataProvider()->fetchRawHafasTrip($trip->trip_id, $trip->linename); $updatedCounts = HafasStopoverService::refreshStopovers($rawHafas); $this->info('Updated ' . $updatedCounts->stopovers . ' stopovers.'); diff --git a/app/DataProviders/DataProviderInterface.php b/app/DataProviders/DataProviderInterface.php index b8cfd85bc..6dba69387 100644 --- a/app/DataProviders/DataProviderInterface.php +++ b/app/DataProviders/DataProviderInterface.php @@ -8,19 +8,17 @@ interface DataProviderInterface { - public static function fetchHafasTrip(string $tripID, string $lineName); + public function fetchHafasTrip(string $tripID, string $lineName); - public static function fetchRawHafasTrip(string $tripId, string $lineName); + public function fetchRawHafasTrip(string $tripId, string $lineName); - public static function getStations(string $query, int $results); + public function getStations(string $query, int $results); - public static function getDepartures(Station $station, Carbon $when, int $duration = 15, TravelType $type = null, bool $localtime = false); + public function getDepartures(Station $station, Carbon $when, int $duration = 15, TravelType $type = null, bool $localtime = false); - public static function fetchDepartures(Station $station, Carbon $when, int $duration, TravelType $type, bool $skipTimeShift); + public function getNearbyStations(float $latitude, float $longitude, int $results); - public static function getNearbyStations(float $latitude, float $longitude, int $results); + public function getStationByRilIdentifier(string $rilIdentifier); - public static function getStationByRilIdentifier(string $rilIdentifier); - - public static function getStationsByFuzzyRilIdentifier(string $rilIdentifier); + public function getStationsByFuzzyRilIdentifier(string $rilIdentifier); } diff --git a/app/DataProviders/HafasController.php b/app/DataProviders/HafasController.php index 80de81a9b..ac9833756 100644 --- a/app/DataProviders/HafasController.php +++ b/app/DataProviders/HafasController.php @@ -25,20 +25,20 @@ class HafasController extends Controller implements DataProviderInterface { - private static function getHttpClient(): PendingRequest { + + private function client(): PendingRequest { return Http::baseUrl(config('trwl.db_rest')) ->timeout(config('trwl.db_rest_timeout')); } - public static function getStationByRilIdentifier(string $rilIdentifier): ?Station { + public function getStationByRilIdentifier(string $rilIdentifier): ?Station { $station = Station::where('rilIdentifier', $rilIdentifier)->first(); if ($station !== null) { return $station; } try { - $response = self::getHttpClient() - ->get("/stations/$rilIdentifier"); + $response = $this->client()->get("/stations/$rilIdentifier"); if ($response->ok() && !empty($response->body()) && $response->body() !== '[]') { $data = json_decode($response->body(), false, 512, JSON_THROW_ON_ERROR); $station = StationRepository::parseHafasStopObject($data); @@ -49,29 +49,30 @@ public static function getStationByRilIdentifier(string $rilIdentifier): ?Statio return $station; } - public static function getStationsByFuzzyRilIdentifier(string $rilIdentifier): ?Collection { + public function getStationsByFuzzyRilIdentifier(string $rilIdentifier): ?Collection { $stations = Station::where('rilIdentifier', 'LIKE', "$rilIdentifier%")->orderBy('rilIdentifier')->get(); if ($stations->count() > 0) { return $stations; } - return collect([self::getStationByRilIdentifier(rilIdentifier: $rilIdentifier)]); + return collect([$this->getStationByRilIdentifier(rilIdentifier: $rilIdentifier)]); } /** * @throws HafasException */ - public static function getStations(string $query, int $results = 10): Collection { + public function getStations(string $query, int $results = 10): Collection { try { - $response = self::getHttpClient() - ->get("/locations", - [ - 'query' => $query, - 'fuzzy' => 'true', - 'stops' => 'true', - 'addresses' => 'false', - 'poi' => 'false', - 'results' => $results - ]); + $response = $this->client()->get( + "/locations", + [ + 'query' => $query, + 'fuzzy' => 'true', + 'stops' => 'true', + 'addresses' => 'false', + 'poi' => 'false', + 'results' => $results + ] + ); $data = json_decode($response->body(), false, 512, JSON_THROW_ON_ERROR); if (empty($data) || !$response->ok()) { @@ -87,13 +88,16 @@ public static function getStations(string $query, int $results = 10): Collection /** * @throws HafasException */ - public static function getNearbyStations(float $latitude, float $longitude, int $results = 8): Collection { + public function getNearbyStations(float $latitude, float $longitude, int $results = 8): Collection { try { - $response = self::getHttpClient()->get("/stops/nearby", [ - 'latitude' => $latitude, - 'longitude' => $longitude, - 'results' => $results - ]); + $response = $this->client()->get( + "/stops/nearby", + [ + 'latitude' => $latitude, + 'longitude' => $longitude, + 'results' => $results + ] + ); if (!$response->ok()) { throw new HafasException(__('messages.exception.generalHafas')); @@ -117,14 +121,13 @@ public static function getNearbyStations(float $latitude, float $longitude, int * @throws HafasException * @throws JsonException */ - public static function fetchDepartures( + private function fetchDepartures( Station $station, Carbon $when, int $duration = 15, TravelType $type = null, bool $skipTimeShift = false ) { - $client = self::getHttpClient(); $time = $skipTimeShift ? $when : (clone $when)->shiftTimezone("Europe/Berlin"); $query = [ 'when' => $time->toIso8601String(), @@ -140,7 +143,7 @@ public static function fetchDepartures( HTT::TRAM->value => FptfHelper::checkTravelType($type, TravelType::TRAM), HTT::TAXI->value => FptfHelper::checkTravelType($type, TravelType::TAXI), ]; - $response = $client->get('/stops/' . $station->ibnr . '/departures', $query); + $response = $this->client()->get('/stops/' . $station->ibnr . '/departures', $query); if (!$response->ok()) { throw new HafasException(__('messages.exception.generalHafas')); @@ -159,7 +162,7 @@ public static function fetchDepartures( * @return Collection * @throws HafasException */ - public static function getDepartures( + public function getDepartures( Station $station, Carbon $when, int $duration = 15, @@ -169,7 +172,7 @@ public static function getDepartures( try { $requestTime = is_null($station->time_offset) || $localtime ? $when : (clone $when)->subHours($station->time_offset); - $data = self::fetchDepartures( + $data = $this->fetchDepartures( $station, $requestTime, $duration, @@ -188,14 +191,14 @@ public static function getDepartures( // Check if the timezone for this station is equal in its offset to Europe/Berlin. // If so, fetch again **without** adjusting the timezone if ($timezone === CarbonTimeZone::create("Europe/Berlin")->toOffsetName()) { - $data = self::fetchDepartures($station, $when, $duration, $type, true); + $data = $this->fetchDepartures($station, $when, $duration, $type, true); $station->shift_time = false; $station->save(); break; } // if the timezone is not equal to Europe/Berlin, fetch the offset - $data = self::fetchDepartures($station, (clone $when)->subHours($offset), $duration, $type); + $data = $this->fetchDepartures($station, (clone $when)->subHours($offset), $duration, $type); $station->time_offset = $offset; $station->save(); @@ -236,8 +239,8 @@ public static function getDepartures( /** * @throws HafasException|JsonException */ - public static function fetchRawHafasTrip(string $tripId, string $lineName) { - $tripResponse = self::getHttpClient()->get("trips/" . rawurlencode($tripId), [ + public function fetchRawHafasTrip(string $tripId, string $lineName) { + $tripResponse = $this->client()->get("trips/" . rawurlencode($tripId), [ 'lineName' => $lineName, 'polyline' => 'true', 'stopovers' => 'true' @@ -265,8 +268,8 @@ public static function fetchRawHafasTrip(string $tripId, string $lineName) { * @return Trip * @throws HafasException|JsonException */ - public static function fetchHafasTrip(string $tripID, string $lineName): Trip { - $tripJson = self::fetchRawHafasTrip($tripID, $lineName); + public function fetchHafasTrip(string $tripID, string $lineName): Trip { + $tripJson = $this->fetchRawHafasTrip($tripID, $lineName); $origin = Repositories\StationRepository::parseHafasStopObject($tripJson->origin); $destination = Repositories\StationRepository::parseHafasStopObject($tripJson->destination); $operator = null; diff --git a/app/DataProviders/HafasStopoverService.php b/app/DataProviders/HafasStopoverService.php index 62e580f53..0bc726ae1 100644 --- a/app/DataProviders/HafasStopoverService.php +++ b/app/DataProviders/HafasStopoverService.php @@ -85,7 +85,7 @@ public static function refreshStopovers(stdClass $rawHafas): stdClass { * @throws HafasException */ public function refreshStopover(Stopover $stopover): void { - $departure = $this->dataProvider::getDepartures( + $departure = $this->dataProvider->getDepartures( station: $stopover->station, when: $stopover->departure_planned, )->filter(function(stdClass $trip) use ($stopover) { diff --git a/app/DataProviders/Repositories/StationRepository.php b/app/DataProviders/Repositories/StationRepository.php index 1e3836d6a..55bcd28fa 100644 --- a/app/DataProviders/Repositories/StationRepository.php +++ b/app/DataProviders/Repositories/StationRepository.php @@ -28,9 +28,10 @@ public static function parseHafasStopObject(stdClass $hafasStop): Station { $data['rilIdentifier'] = $hafasStop->ril100; } - return Station::updateOrCreate([ - 'ibnr' => $hafasStop->id - ], $data); + return Station::updateOrCreate( + ['ibnr' => $hafasStop->id], + $data + ); } public static function parseHafasStops(array $hafasResponse): Collection { diff --git a/app/Http/Controllers/API/v1/EventController.php b/app/Http/Controllers/API/v1/EventController.php index 14fc68a57..50404045b 100644 --- a/app/Http/Controllers/API/v1/EventController.php +++ b/app/Http/Controllers/API/v1/EventController.php @@ -249,7 +249,7 @@ public function suggest(Request $request): JsonResponse { ]); if (isset($validated['nearestStation'])) { - $stations = $this->dataProvider::getStations($validated['nearestStation'], 1); + $stations = $this->dataProvider->getStations($validated['nearestStation'], 1); if (count($stations) === 0) { return $this->sendError(error: __('events.request.station_not_found'), code: 400); } diff --git a/app/Http/Controllers/API/v1/TransportController.php b/app/Http/Controllers/API/v1/TransportController.php index f04170ecf..bd44e4158 100644 --- a/app/Http/Controllers/API/v1/TransportController.php +++ b/app/Http/Controllers/API/v1/TransportController.php @@ -153,7 +153,7 @@ public function getDepartures(Request $request, int $stationId): JsonResponse { $station = Station::findOrFail($stationId); try { - $departures = $this->dataProvider::getDepartures( + $departures = $this->dataProvider->getDepartures( station: $station, when: $timestamp, type: TravelType::tryFrom($validated['travelType'] ?? null), @@ -311,7 +311,7 @@ public function getNextStationByCoordinates(Request $request): JsonResponse { ]); try { - $nearestStation = $this->dataProvider::getNearbyStations( + $nearestStation = $this->dataProvider->getNearbyStations( latitude: $validated['latitude'], longitude: $validated['longitude'], results: 1 diff --git a/app/Http/Controllers/Frontend/Admin/CheckinController.php b/app/Http/Controllers/Frontend/Admin/CheckinController.php index 16a16200a..ace48b5d9 100644 --- a/app/Http/Controllers/Frontend/Admin/CheckinController.php +++ b/app/Http/Controllers/Frontend/Admin/CheckinController.php @@ -36,6 +36,8 @@ class CheckinController * @deprecated adapt admin panel to api endpoints */ public static function lookupStation(string|int $query): Station { + $dataProvider = (new DataProviderFactory)->create(HafasController::class); + //Lookup by station ibnr if (is_numeric($query)) { $station = Station::where('ibnr', $query)->first(); @@ -46,14 +48,14 @@ public static function lookupStation(string|int $query): Station { //Lookup by ril identifier if (!is_numeric($query) && strlen($query) <= 5 && ctype_upper($query)) { - $station = (new DataProviderFactory)->create(HafasController::class)::getStationByRilIdentifier($query); + $station = $dataProvider->getStationByRilIdentifier($query); if ($station !== null) { return $station; } } //Lookup HAFAS - $station = (new DataProviderFactory)->create(HafasController::class)::getStations(query: $query, results: 1)->first(); + $station = $dataProvider->getStations(query: $query, results: 1)->first(); if ($station !== null) { return $station; } @@ -69,14 +71,14 @@ public static function lookupStation(string|int $query): Station { * * @return array * @throws HafasException - * @deprecated use DataProviderInterface::getDepartures(...) directly instead (-> less overhead) + * @deprecated use DataProviderInterface->getDepartures(...) directly instead (-> less overhead) */ #[ArrayShape([ 'station' => Station::class, 'departures' => Collection::class, 'times' => "array" ])] - public static function getDepartures( + public static function getDeprecatedDepartures( string|int $stationQuery, Carbon $when = null, TravelType $travelType = null, @@ -91,7 +93,7 @@ public static function getDepartures( 'next' => $when->clone()->addMinutes(15) ]; - $departures = (new DataProviderFactory)->create(HafasController::class)::getDepartures( + $departures = (new DataProviderFactory)->create(HafasController::class)->getDepartures( station: $station, when: $when, type: $travelType, @@ -128,7 +130,7 @@ public function renderStationboard(Request $request): View|RedirectResponse { if (isset($validated['station'])) { try { - $trainStationboardResponse = self::getDepartures( + $trainStationboardResponse = self::getDeprecatedDepartures( stationQuery: $validated['station'], when: $when, travelType: TravelType::tryFrom($validated['filter'] ?? null), diff --git a/app/Http/Controllers/Frontend/Admin/EventController.php b/app/Http/Controllers/Frontend/Admin/EventController.php index edaaa8084..fd74a9fb9 100644 --- a/app/Http/Controllers/Frontend/Admin/EventController.php +++ b/app/Http/Controllers/Frontend/Admin/EventController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Frontend\Admin; use App\DataProviders\DataProviderFactory; +use App\DataProviders\DataProviderInterface; use App\DataProviders\HafasController; use App\Enum\EventRejectionReason; use App\Exceptions\HafasException; @@ -21,6 +22,12 @@ class EventController extends Controller { + private DataProviderInterface $dataProvider; + + public function __construct(string $dataProvider = null) { + $dataProvider ??= HafasController::class; + $this->dataProvider = (new DataProviderFactory())->create($dataProvider); + } private const VALIDATOR_RULES = [ 'name' => ['required', 'max:255'], @@ -148,7 +155,7 @@ public function acceptSuggestion(Request $request): RedirectResponse { } if (isset($validated['nearest_station_name'])) { - $station = (new DataProviderFactory)->create(HafasController::class)::getStations($validated['nearest_station_name'], 1)->first(); + $station = $this->dataProvider->getStations($validated['nearest_station_name'], 1)->first(); if ($station === null) { return back()->with('alert-danger', 'Die Station konnte nicht gefunden werden.'); @@ -188,7 +195,7 @@ public function create(Request $request): RedirectResponse { $station = null; if (isset($validated['nearest_station_name'])) { - $station = (new DataProviderFactory)->create(HafasController::class)::getStations($validated['nearest_station_name'], 1)->first(); + $station = $this->dataProvider->getStations($validated['nearest_station_name'], 1)->first(); if ($station === null) { return back()->with('alert-danger', 'Die Station konnte nicht gefunden werden.'); @@ -220,7 +227,7 @@ public function edit(int $id, Request $request): RedirectResponse { if (strlen($validated['nearest_station_name'] ?? '') === 0) { $validated['station_id'] = null; } elseif ($validated['nearest_station_name'] && $validated['nearest_station_name'] !== $event->station->name) { - $station = (new DataProviderFactory)->create(HafasController::class)::getStations($validated['nearest_station_name'], 1)->first(); + $station = $this->dataProvider->getStations($validated['nearest_station_name'], 1)->first(); if ($station === null) { return back()->with('alert-danger', 'Die Station konnte nicht gefunden werden.'); diff --git a/app/Http/Controllers/TransportController.php b/app/Http/Controllers/TransportController.php index b9378bb3e..c4ffc8103 100644 --- a/app/Http/Controllers/TransportController.php +++ b/app/Http/Controllers/TransportController.php @@ -4,7 +4,6 @@ use App\DataProviders\DataProviderFactory; use App\DataProviders\DataProviderInterface; -use App\DataProviders\HafasController; use App\Exceptions\HafasException; use App\Http\Resources\StationResource; use App\Models\Checkin; @@ -38,11 +37,11 @@ public function __construct(string $dataProvider) { */ public function getTrainStationAutocomplete(string $query): Collection { if (!is_numeric($query) && strlen($query) <= 5 && ctype_upper($query)) { - $stations = (new DataProviderFactory)->create(HafasController::class)::getStationsByFuzzyRilIdentifier(rilIdentifier: $query); + $stations = $this->dataProvider->getStationsByFuzzyRilIdentifier(rilIdentifier: $query); } if (!isset($stations) || $stations[0] === null) { - $stations = (new DataProviderFactory)->create(HafasController::class)::getStations($query); + $stations = $this->dataProvider->getStations($query); } return $stations->map(function(Station $station) { diff --git a/app/Repositories/CheckinHydratorRepository.php b/app/Repositories/CheckinHydratorRepository.php index 005178564..1bd1a7868 100644 --- a/app/Repositories/CheckinHydratorRepository.php +++ b/app/Repositories/CheckinHydratorRepository.php @@ -33,7 +33,7 @@ public function getHafasTrip(string $tripID, string $lineName): Trip { $trip = Trip::where('id', $tripID)->where('linename', $lineName)->first(); } $trip = $trip ?? Trip::where('trip_id', $tripID)->where('linename', $lineName)->first(); - return $trip ?? $dataProvider::fetchHafasTrip($tripID, $lineName); + return $trip ?? $dataProvider->fetchHafasTrip($tripID, $lineName); } public function findEvent(int $id): ?Event { diff --git a/tests/Feature/CheckinTest.php b/tests/Feature/CheckinTest.php index 765d45f5c..d9d1df3fd 100644 --- a/tests/Feature/CheckinTest.php +++ b/tests/Feature/CheckinTest.php @@ -34,7 +34,7 @@ public function stationboardTest(): void { $requestDate = Carbon::parse(self::DEPARTURE_TIME); - $trainStationboard = CheckinController::getDepartures( + $trainStationboard = CheckinController::getDeprecatedDepartures( stationQuery: self::FRANKFURT_HBF['name'], when: $requestDate ); diff --git a/tests/Feature/StationSearchTest.php b/tests/Feature/StationSearchTest.php index 646a396d3..2db2ad9fa 100644 --- a/tests/Feature/StationSearchTest.php +++ b/tests/Feature/StationSearchTest.php @@ -65,13 +65,16 @@ public function testIbnrLocalSearch(): void { $this->assertEquals(Station::find($expected->id)->name, $station->name); } + /** + * @throws HafasException + */ public function testGetNearbyStations(): void { Http::fake(["*/stops/nearby*" => Http::response([array_merge( self::HANNOVER_HBF, ["distance" => 421] )])]); - $result = $this->dataProvider::getNearbyStations( + $result = $this->dataProvider->getNearbyStations( self::HANNOVER_HBF['location']['latitude'], self::HANNOVER_HBF['location']['longitude']); @@ -83,7 +86,7 @@ public function testGetNearbyStationFails(): void { Http::fake(Http::response(status: 503)); $this->assertThrows(function() { - $this->dataProvider::getNearbyStations( + $this->dataProvider->getNearbyStations( self::HANNOVER_HBF['location']['latitude'], self::HANNOVER_HBF['location']['longitude']); }, HafasException::class); diff --git a/tests/Feature/Transport/BackendCheckinTest.php b/tests/Feature/Transport/BackendCheckinTest.php index 4ebeed85e..3527caa46 100644 --- a/tests/Feature/Transport/BackendCheckinTest.php +++ b/tests/Feature/Transport/BackendCheckinTest.php @@ -42,7 +42,7 @@ public function testStationNotOnTripException() { $user = User::factory()->create(); $stationHannover = HafasHelpers::getStationById(8000152); - $departures = $this->dataProvider::getDepartures( + $departures = $this->dataProvider->getDepartures( station: $stationHannover, when: Carbon::parse('2023-01-12 08:00'), type: TravelType::EXPRESS, @@ -73,7 +73,7 @@ public function testSwitchedOriginAndDestinationShouldThrowException() { $user = User::factory()->create(); $station = HafasHelpers::getStationById(8000105); - $departures = $this->dataProvider::getDepartures( + $departures = $this->dataProvider->getDepartures( station: $station, when: Carbon::parse('2023-01-12 08:00'), type: TravelType::EXPRESS, @@ -107,7 +107,7 @@ public function testDuplicateCheckinsShouldThrowException() { $user = User::factory()->create(); $station = HafasHelpers::getStationById(8000105); - $departures = $this->dataProvider::getDepartures( + $departures = $this->dataProvider->getDepartures( station: $station, when: Carbon::parse('2023-01-12 08:00'), type: TravelType::EXPRESS, @@ -148,7 +148,7 @@ public function testCheckinAtBus603Potsdam(): void { // First: Get a train that's fine for our stuff $timestamp = Carbon::parse("2023-01-15 10:15"); try { - $trainStationboard = CheckinController::getDepartures( + $trainStationboard = CheckinController::getDeprecatedDepartures( stationQuery: 'Schloss Cecilienhof, Potsdam', when: $timestamp, travelType: TravelType::BUS @@ -215,7 +215,7 @@ public function testCheckinAtBerlinRingbahnRollingOverSuedkreuz(): void { // First: Get a train that's fine for our stuff // The 10:00 train actually quits at Südkreuz, but the 10:05 does not. $station = HafasHelpers::getStationById(8089110); - $departures = $this->dataProvider::getDepartures( + $departures = $this->dataProvider->getDepartures( station: $station, when: Carbon::parse('2023-01-16 10:00'), ); @@ -268,7 +268,7 @@ public function testDistanceCalculationOnRingLinesForFirstOccurrence(): void { $user = User::factory()->create(); $stationPlantagenPotsdam = HafasHelpers::getStationById(736165); - $departures = $this->dataProvider::getDepartures( + $departures = $this->dataProvider->getDepartures( station: $stationPlantagenPotsdam, when: Carbon::parse('2023-01-16 10:00'), type: TravelType::TRAM, @@ -322,7 +322,7 @@ public function testDistanceCalculationOnRingLinesForSecondOccurrence(): void { $user = User::factory()->create(); $stationPlantagenPotsdam = HafasHelpers::getStationById(736165); - $departures = $this->dataProvider::getDepartures( + $departures = $this->dataProvider->getDepartures( station: $stationPlantagenPotsdam, when: Carbon::parse('2023-01-16 10:00'), ); @@ -375,7 +375,7 @@ public function testBusAirAtFrankfurtAirport(): void { $user = User::factory()->create(); $station = HafasHelpers::getStationById(102932); // Flughafen Terminal 1, Frankfurt a.M. - $departures = $this->dataProvider::getDepartures( + $departures = $this->dataProvider->getDepartures( station: $station, when: Carbon::parse('2023-01-16 10:00'), type: TravelType::BUS, @@ -416,7 +416,7 @@ public function testChangeTripDestination(): void { $user = User::factory()->create(); $station = HafasHelpers::getStationById(self::FRANKFURT_HBF['id']); - $departures = $this->dataProvider::getDepartures( + $departures = $this->dataProvider->getDepartures( station: $station, when: Carbon::parse('2023-01-16 08:00'), type: TravelType::EXPRESS, From 9d75e93d30b1506fdb8c107f21d2fd665ea16a68 Mon Sep 17 00:00:00 2001 From: Levin Date: Wed, 11 Dec 2024 22:14:00 +0100 Subject: [PATCH 06/11] Rename HafasController --- app/Console/Commands/RefreshCurrentTrips.php | 4 ++-- app/DataProviders/{HafasController.php => Hafas.php} | 2 +- app/Http/Controllers/API/v1/Controller.php | 4 ++-- app/Http/Controllers/API/v1/TransportController.php | 4 ++-- app/Http/Controllers/Frontend/Admin/CheckinController.php | 6 +++--- app/Http/Controllers/Frontend/Admin/EventController.php | 4 ++-- app/Http/Controllers/FrontendTransportController.php | 4 ++-- app/Jobs/RefreshStopover.php | 4 ++-- app/Repositories/CheckinHydratorRepository.php | 4 ++-- tests/Feature/StationSearchTest.php | 4 ++-- tests/Feature/Transport/BackendCheckinTest.php | 4 ++-- 11 files changed, 22 insertions(+), 22 deletions(-) rename app/DataProviders/{HafasController.php => Hafas.php} (99%) diff --git a/app/Console/Commands/RefreshCurrentTrips.php b/app/Console/Commands/RefreshCurrentTrips.php index 437e133ad..2fb29a385 100644 --- a/app/Console/Commands/RefreshCurrentTrips.php +++ b/app/Console/Commands/RefreshCurrentTrips.php @@ -4,7 +4,7 @@ use App\DataProviders\DataProviderFactory; use App\DataProviders\DataProviderInterface; -use App\DataProviders\HafasController; +use App\DataProviders\Hafas; use App\DataProviders\HafasStopoverService; use App\Enum\TripSource; use App\Exceptions\HafasException; @@ -20,7 +20,7 @@ class RefreshCurrentTrips extends Command private function getDataProvider(): DataProviderInterface { // Probably only HafasController is needed here, because this Command is very Hafas specific - return (new DataProviderFactory)->create(HafasController::class); + return (new DataProviderFactory)->create(Hafas::class); } public function handle(): int { diff --git a/app/DataProviders/HafasController.php b/app/DataProviders/Hafas.php similarity index 99% rename from app/DataProviders/HafasController.php rename to app/DataProviders/Hafas.php index ac9833756..9e47faec2 100644 --- a/app/DataProviders/HafasController.php +++ b/app/DataProviders/Hafas.php @@ -23,7 +23,7 @@ use JsonException; use PDOException; -class HafasController extends Controller implements DataProviderInterface +class Hafas extends Controller implements DataProviderInterface { private function client(): PendingRequest { diff --git a/app/Http/Controllers/API/v1/Controller.php b/app/Http/Controllers/API/v1/Controller.php index 87dfcaaa7..f16649298 100644 --- a/app/Http/Controllers/API/v1/Controller.php +++ b/app/Http/Controllers/API/v1/Controller.php @@ -4,7 +4,7 @@ use App\DataProviders\DataProviderFactory; use App\DataProviders\DataProviderInterface; -use App\DataProviders\HafasController; +use App\DataProviders\Hafas; use App\Models\OAuthClient; use App\Models\User; use Illuminate\Contracts\Auth\Authenticatable; @@ -103,7 +103,7 @@ class Controller extends \App\Http\Controllers\Controller public function __construct() { // todo: set data provider based on user settings - $this->dataProvider = (new DataProviderFactory())->create(HafasController::class); + $this->dataProvider = (new DataProviderFactory())->create(Hafas::class); } public function sendResponse( diff --git a/app/Http/Controllers/API/v1/TransportController.php b/app/Http/Controllers/API/v1/TransportController.php index bd44e4158..a6b8a9f6b 100644 --- a/app/Http/Controllers/API/v1/TransportController.php +++ b/app/Http/Controllers/API/v1/TransportController.php @@ -2,7 +2,7 @@ namespace App\Http\Controllers\API\v1; -use App\DataProviders\HafasController; +use App\DataProviders\Hafas; use App\Dto\Transport\Station as StationDto; use App\Enum\Business; use App\Enum\StatusVisibility; @@ -513,7 +513,7 @@ public function setHome(int $stationId): JsonResponse { */ public function getTrainStationAutocomplete(string $query): JsonResponse { try { - $trainAutocompleteResponse = (new TransportBackend(HafasController::class))->getTrainStationAutocomplete($query); + $trainAutocompleteResponse = (new TransportBackend(Hafas::class))->getTrainStationAutocomplete($query); return $this->sendResponse($trainAutocompleteResponse); } catch (HafasException) { return $this->sendError("There has been an error with our data provider", 503); diff --git a/app/Http/Controllers/Frontend/Admin/CheckinController.php b/app/Http/Controllers/Frontend/Admin/CheckinController.php index ace48b5d9..6664280d4 100644 --- a/app/Http/Controllers/Frontend/Admin/CheckinController.php +++ b/app/Http/Controllers/Frontend/Admin/CheckinController.php @@ -3,7 +3,7 @@ namespace App\Http\Controllers\Frontend\Admin; use App\DataProviders\DataProviderFactory; -use App\DataProviders\HafasController; +use App\DataProviders\Hafas; use App\Enum\Business; use App\Enum\StatusVisibility; use App\Enum\TravelType; @@ -36,7 +36,7 @@ class CheckinController * @deprecated adapt admin panel to api endpoints */ public static function lookupStation(string|int $query): Station { - $dataProvider = (new DataProviderFactory)->create(HafasController::class); + $dataProvider = (new DataProviderFactory)->create(Hafas::class); //Lookup by station ibnr if (is_numeric($query)) { @@ -93,7 +93,7 @@ public static function getDeprecatedDepartures( 'next' => $when->clone()->addMinutes(15) ]; - $departures = (new DataProviderFactory)->create(HafasController::class)->getDepartures( + $departures = (new DataProviderFactory)->create(Hafas::class)->getDepartures( station: $station, when: $when, type: $travelType, diff --git a/app/Http/Controllers/Frontend/Admin/EventController.php b/app/Http/Controllers/Frontend/Admin/EventController.php index fd74a9fb9..b19c8eeb7 100644 --- a/app/Http/Controllers/Frontend/Admin/EventController.php +++ b/app/Http/Controllers/Frontend/Admin/EventController.php @@ -4,7 +4,7 @@ use App\DataProviders\DataProviderFactory; use App\DataProviders\DataProviderInterface; -use App\DataProviders\HafasController; +use App\DataProviders\Hafas; use App\Enum\EventRejectionReason; use App\Exceptions\HafasException; use App\Http\Controllers\Backend\Admin\EventController as AdminEventBackend; @@ -25,7 +25,7 @@ class EventController extends Controller private DataProviderInterface $dataProvider; public function __construct(string $dataProvider = null) { - $dataProvider ??= HafasController::class; + $dataProvider ??= Hafas::class; $this->dataProvider = (new DataProviderFactory())->create($dataProvider); } diff --git a/app/Http/Controllers/FrontendTransportController.php b/app/Http/Controllers/FrontendTransportController.php index 2afef4779..0b6b06b6f 100644 --- a/app/Http/Controllers/FrontendTransportController.php +++ b/app/Http/Controllers/FrontendTransportController.php @@ -2,7 +2,7 @@ namespace App\Http\Controllers; -use App\DataProviders\HafasController; +use App\DataProviders\Hafas; use App\Exceptions\HafasException; use App\Http\Controllers\TransportController as TransportBackend; use Illuminate\Http\JsonResponse; @@ -15,7 +15,7 @@ class FrontendTransportController extends Controller public function TrainAutocomplete(string $station): JsonResponse { try { //todo: adapt data provider to users preferences - $provider = new TransportBackend(HafasController::class); + $provider = new TransportBackend(Hafas::class); $trainAutocompleteResponse = $provider->getTrainStationAutocomplete($station); return response()->json($trainAutocompleteResponse); } catch (HafasException $e) { diff --git a/app/Jobs/RefreshStopover.php b/app/Jobs/RefreshStopover.php index 407bb3f0f..9d43328bb 100644 --- a/app/Jobs/RefreshStopover.php +++ b/app/Jobs/RefreshStopover.php @@ -2,7 +2,7 @@ namespace App\Jobs; -use App\DataProviders\HafasController; +use App\DataProviders\Hafas; use App\DataProviders\HafasStopoverService; use App\Exceptions\HafasException; use App\Models\Stopover; @@ -27,6 +27,6 @@ public function __construct(Stopover $stopover) { * @throws HafasException */ public function handle(): void { - (new HafasStopoverService(HafasController::class))->refreshStopover($this->stopover); + (new HafasStopoverService(Hafas::class))->refreshStopover($this->stopover); } } diff --git a/app/Repositories/CheckinHydratorRepository.php b/app/Repositories/CheckinHydratorRepository.php index 1bd1a7868..d7d8d881e 100644 --- a/app/Repositories/CheckinHydratorRepository.php +++ b/app/Repositories/CheckinHydratorRepository.php @@ -3,7 +3,7 @@ namespace App\Repositories; use App\DataProviders\DataProviderFactory; -use App\DataProviders\HafasController; +use App\DataProviders\Hafas; use App\Exceptions\HafasException; use App\Models\Event; use App\Models\Station; @@ -27,7 +27,7 @@ public function getOneStation(string $searchKey, string|int $id): ?Station { */ public function getHafasTrip(string $tripID, string $lineName): Trip { // todo: create trip IDs with a prefix, to distinguish between different data providers - $dataProvider = (new DataProviderFactory)->create(HafasController::class); + $dataProvider = (new DataProviderFactory)->create(Hafas::class); if (is_numeric($tripID)) { $trip = Trip::where('id', $tripID)->where('linename', $lineName)->first(); diff --git a/tests/Feature/StationSearchTest.php b/tests/Feature/StationSearchTest.php index 2db2ad9fa..339c2bbb8 100644 --- a/tests/Feature/StationSearchTest.php +++ b/tests/Feature/StationSearchTest.php @@ -4,7 +4,7 @@ use App\DataProviders\DataProviderFactory; use App\DataProviders\DataProviderInterface; -use App\DataProviders\HafasController; +use App\DataProviders\Hafas; use App\Exceptions\HafasException; use App\Http\Controllers\Frontend\Admin\CheckinController; use App\Models\Station; @@ -19,7 +19,7 @@ class StationSearchTest extends FeatureTestCase public function setUp(): void { parent::setUp(); - $this->dataProvider = (new DataProviderFactory())->create(HafasController::class); + $this->dataProvider = (new DataProviderFactory())->create(Hafas::class); } diff --git a/tests/Feature/Transport/BackendCheckinTest.php b/tests/Feature/Transport/BackendCheckinTest.php index 3527caa46..76d193e00 100644 --- a/tests/Feature/Transport/BackendCheckinTest.php +++ b/tests/Feature/Transport/BackendCheckinTest.php @@ -4,7 +4,7 @@ use App\DataProviders\DataProviderFactory; use App\DataProviders\DataProviderInterface; -use App\DataProviders\HafasController; +use App\DataProviders\Hafas; use App\Enum\TravelType; use App\Exceptions\CheckInCollisionException; use App\Exceptions\HafasException; @@ -27,7 +27,7 @@ class BackendCheckinTest extends FeatureTestCase public function setUp(): void { parent::setUp(); - $this->dataProvider = (new DataProviderFactory())->create(HafasController::class); + $this->dataProvider = (new DataProviderFactory())->create(Hafas::class); } use RefreshDatabase; From a156b4c63f92475a9e67d5488ec9c1a12aa8bade Mon Sep 17 00:00:00 2001 From: Levin Date: Thu, 12 Dec 2024 20:58:17 +0100 Subject: [PATCH 07/11] Add error to departures --- app/DataProviders/Hafas.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/DataProviders/Hafas.php b/app/DataProviders/Hafas.php index 9e47faec2..51f6680f1 100644 --- a/app/DataProviders/Hafas.php +++ b/app/DataProviders/Hafas.php @@ -128,8 +128,8 @@ private function fetchDepartures( TravelType $type = null, bool $skipTimeShift = false ) { - $time = $skipTimeShift ? $when : (clone $when)->shiftTimezone("Europe/Berlin"); - $query = [ + $time = $skipTimeShift ? $when : (clone $when)->shiftTimezone("Europe/Berlin"); + $query = [ 'when' => $time->toIso8601String(), 'duration' => $duration, HTT::NATIONAL_EXPRESS->value => FptfHelper::checkTravelType($type, TravelType::EXPRESS), @@ -143,7 +143,11 @@ private function fetchDepartures( HTT::TRAM->value => FptfHelper::checkTravelType($type, TravelType::TRAM), HTT::TAXI->value => FptfHelper::checkTravelType($type, TravelType::TAXI), ]; - $response = $this->client()->get('/stops/' . $station->ibnr . '/departures', $query); + try { + $response = $this->client()->get('/stops/' . $station->ibnr . '/departures', $query); + } catch (Exception $exception) { + throw new HafasException($exception->getMessage()); + } if (!$response->ok()) { throw new HafasException(__('messages.exception.generalHafas')); From 97f0d5345b44a94e86ecc556d8fb5a55c1ef5945 Mon Sep 17 00:00:00 2001 From: Levin Date: Thu, 12 Dec 2024 23:15:38 +0100 Subject: [PATCH 08/11] Change factory to builder --- app/Console/Commands/RefreshCurrentTrips.php | 5 ++-- app/DataProviders/DataProviderBuilder.php | 10 +++++++ app/DataProviders/DataProviderFactory.php | 28 ------------------- app/DataProviders/HafasStopoverService.php | 8 +++--- app/Http/Controllers/API/v1/Controller.php | 5 ++-- .../Frontend/Admin/CheckinController.php | 7 ++--- .../Frontend/Admin/EventController.php | 4 +-- app/Http/Controllers/TransportController.php | 4 +-- .../CheckinHydratorRepository.php | 5 ++-- tests/Feature/StationSearchTest.php | 5 ++-- .../Feature/Transport/BackendCheckinTest.php | 5 ++-- 11 files changed, 31 insertions(+), 55 deletions(-) create mode 100644 app/DataProviders/DataProviderBuilder.php delete mode 100644 app/DataProviders/DataProviderFactory.php diff --git a/app/Console/Commands/RefreshCurrentTrips.php b/app/Console/Commands/RefreshCurrentTrips.php index 2fb29a385..11ad93fe9 100644 --- a/app/Console/Commands/RefreshCurrentTrips.php +++ b/app/Console/Commands/RefreshCurrentTrips.php @@ -2,9 +2,8 @@ namespace App\Console\Commands; -use App\DataProviders\DataProviderFactory; +use App\DataProviders\DataProviderBuilder; use App\DataProviders\DataProviderInterface; -use App\DataProviders\Hafas; use App\DataProviders\HafasStopoverService; use App\Enum\TripSource; use App\Exceptions\HafasException; @@ -20,7 +19,7 @@ class RefreshCurrentTrips extends Command private function getDataProvider(): DataProviderInterface { // Probably only HafasController is needed here, because this Command is very Hafas specific - return (new DataProviderFactory)->create(Hafas::class); + return (new DataProviderBuilder)->build(); } public function handle(): int { diff --git a/app/DataProviders/DataProviderBuilder.php b/app/DataProviders/DataProviderBuilder.php new file mode 100644 index 000000000..ff11dbecf --- /dev/null +++ b/app/DataProviders/DataProviderBuilder.php @@ -0,0 +1,10 @@ + $class - * - * @return T - * @throws InvalidArgumentException - */ - public function create(string $class) { - $this->checkClass($class); - - return new $class(); - } - - private function checkClass(string $class): void { - // check instance of DataProviderInterface - if (!class_implements($class, DataProviderInterface::class)) { - throw new InvalidArgumentException('Class must implement DataProviderInterface'); - } - } -} diff --git a/app/DataProviders/HafasStopoverService.php b/app/DataProviders/HafasStopoverService.php index 0bc726ae1..5deca73e7 100644 --- a/app/DataProviders/HafasStopoverService.php +++ b/app/DataProviders/HafasStopoverService.php @@ -14,11 +14,11 @@ class HafasStopoverService /** * @template T of DataProviderInterface * @param class-string $dataProvider - * @param DataProviderFactory|null $dataProviderFactory + * @param DataProviderBuilder|null $dataProviderFactory */ - public function __construct(string $dataProvider, ?DataProviderFactory $dataProviderFactory = null) { - $dataProviderFactory ??= new DataProviderFactory(); - $this->dataProvider = $dataProviderFactory->create($dataProvider); + public function __construct(string $dataProvider, ?DataProviderBuilder $dataProviderFactory = null) { + $dataProviderFactory ??= new DataProviderBuilder(); + $this->dataProvider = $dataProviderFactory->build($dataProvider); } public static function refreshStopovers(stdClass $rawHafas): stdClass { diff --git a/app/Http/Controllers/API/v1/Controller.php b/app/Http/Controllers/API/v1/Controller.php index f16649298..8c0ec19fe 100644 --- a/app/Http/Controllers/API/v1/Controller.php +++ b/app/Http/Controllers/API/v1/Controller.php @@ -2,9 +2,8 @@ namespace App\Http\Controllers\API\v1; -use App\DataProviders\DataProviderFactory; +use App\DataProviders\DataProviderBuilder; use App\DataProviders\DataProviderInterface; -use App\DataProviders\Hafas; use App\Models\OAuthClient; use App\Models\User; use Illuminate\Contracts\Auth\Authenticatable; @@ -103,7 +102,7 @@ class Controller extends \App\Http\Controllers\Controller public function __construct() { // todo: set data provider based on user settings - $this->dataProvider = (new DataProviderFactory())->create(Hafas::class); + $this->dataProvider = (new DataProviderBuilder())->build(); } public function sendResponse( diff --git a/app/Http/Controllers/Frontend/Admin/CheckinController.php b/app/Http/Controllers/Frontend/Admin/CheckinController.php index 6664280d4..6ecdbc5f8 100644 --- a/app/Http/Controllers/Frontend/Admin/CheckinController.php +++ b/app/Http/Controllers/Frontend/Admin/CheckinController.php @@ -2,8 +2,7 @@ namespace App\Http\Controllers\Frontend\Admin; -use App\DataProviders\DataProviderFactory; -use App\DataProviders\Hafas; +use App\DataProviders\DataProviderBuilder; use App\Enum\Business; use App\Enum\StatusVisibility; use App\Enum\TravelType; @@ -36,7 +35,7 @@ class CheckinController * @deprecated adapt admin panel to api endpoints */ public static function lookupStation(string|int $query): Station { - $dataProvider = (new DataProviderFactory)->create(Hafas::class); + $dataProvider = (new DataProviderBuilder)->build(); //Lookup by station ibnr if (is_numeric($query)) { @@ -93,7 +92,7 @@ public static function getDeprecatedDepartures( 'next' => $when->clone()->addMinutes(15) ]; - $departures = (new DataProviderFactory)->create(Hafas::class)->getDepartures( + $departures = (new DataProviderBuilder)->build()->getDepartures( station: $station, when: $when, type: $travelType, diff --git a/app/Http/Controllers/Frontend/Admin/EventController.php b/app/Http/Controllers/Frontend/Admin/EventController.php index b19c8eeb7..25efd543e 100644 --- a/app/Http/Controllers/Frontend/Admin/EventController.php +++ b/app/Http/Controllers/Frontend/Admin/EventController.php @@ -2,7 +2,7 @@ namespace App\Http\Controllers\Frontend\Admin; -use App\DataProviders\DataProviderFactory; +use App\DataProviders\DataProviderBuilder; use App\DataProviders\DataProviderInterface; use App\DataProviders\Hafas; use App\Enum\EventRejectionReason; @@ -26,7 +26,7 @@ class EventController extends Controller public function __construct(string $dataProvider = null) { $dataProvider ??= Hafas::class; - $this->dataProvider = (new DataProviderFactory())->create($dataProvider); + $this->dataProvider = (new DataProviderBuilder())->build($dataProvider); } private const VALIDATOR_RULES = [ diff --git a/app/Http/Controllers/TransportController.php b/app/Http/Controllers/TransportController.php index c4ffc8103..40f94c326 100644 --- a/app/Http/Controllers/TransportController.php +++ b/app/Http/Controllers/TransportController.php @@ -2,7 +2,7 @@ namespace App\Http\Controllers; -use App\DataProviders\DataProviderFactory; +use App\DataProviders\DataProviderBuilder; use App\DataProviders\DataProviderInterface; use App\Exceptions\HafasException; use App\Http\Resources\StationResource; @@ -25,7 +25,7 @@ class TransportController extends Controller * @param class-string $dataProvider */ public function __construct(string $dataProvider) { - $this->dataProvider = (new DataProviderFactory())->create($dataProvider); + $this->dataProvider = (new DataProviderBuilder())->build($dataProvider); } /** diff --git a/app/Repositories/CheckinHydratorRepository.php b/app/Repositories/CheckinHydratorRepository.php index d7d8d881e..a6f76e687 100644 --- a/app/Repositories/CheckinHydratorRepository.php +++ b/app/Repositories/CheckinHydratorRepository.php @@ -2,8 +2,7 @@ namespace App\Repositories; -use App\DataProviders\DataProviderFactory; -use App\DataProviders\Hafas; +use App\DataProviders\DataProviderBuilder; use App\Exceptions\HafasException; use App\Models\Event; use App\Models\Station; @@ -27,7 +26,7 @@ public function getOneStation(string $searchKey, string|int $id): ?Station { */ public function getHafasTrip(string $tripID, string $lineName): Trip { // todo: create trip IDs with a prefix, to distinguish between different data providers - $dataProvider = (new DataProviderFactory)->create(Hafas::class); + $dataProvider = (new DataProviderBuilder)->build(); if (is_numeric($tripID)) { $trip = Trip::where('id', $tripID)->where('linename', $lineName)->first(); diff --git a/tests/Feature/StationSearchTest.php b/tests/Feature/StationSearchTest.php index 339c2bbb8..04404fa66 100644 --- a/tests/Feature/StationSearchTest.php +++ b/tests/Feature/StationSearchTest.php @@ -2,9 +2,8 @@ namespace Tests\Feature; -use App\DataProviders\DataProviderFactory; +use App\DataProviders\DataProviderBuilder; use App\DataProviders\DataProviderInterface; -use App\DataProviders\Hafas; use App\Exceptions\HafasException; use App\Http\Controllers\Frontend\Admin\CheckinController; use App\Models\Station; @@ -19,7 +18,7 @@ class StationSearchTest extends FeatureTestCase public function setUp(): void { parent::setUp(); - $this->dataProvider = (new DataProviderFactory())->create(Hafas::class); + $this->dataProvider = (new DataProviderBuilder())->build(); } diff --git a/tests/Feature/Transport/BackendCheckinTest.php b/tests/Feature/Transport/BackendCheckinTest.php index 76d193e00..148ee26fc 100644 --- a/tests/Feature/Transport/BackendCheckinTest.php +++ b/tests/Feature/Transport/BackendCheckinTest.php @@ -2,9 +2,8 @@ namespace Tests\Feature\Transport; -use App\DataProviders\DataProviderFactory; +use App\DataProviders\DataProviderBuilder; use App\DataProviders\DataProviderInterface; -use App\DataProviders\Hafas; use App\Enum\TravelType; use App\Exceptions\CheckInCollisionException; use App\Exceptions\HafasException; @@ -27,7 +26,7 @@ class BackendCheckinTest extends FeatureTestCase public function setUp(): void { parent::setUp(); - $this->dataProvider = (new DataProviderFactory())->create(Hafas::class); + $this->dataProvider = (new DataProviderBuilder())->build(); } use RefreshDatabase; From d380358a93f09fbf765cc8d23e3c87e4fd10f8ad Mon Sep 17 00:00:00 2001 From: Levin Date: Thu, 12 Dec 2024 23:53:35 +0100 Subject: [PATCH 09/11] This... this seems to be working? --- app/DataProviders/CachedHafas.php | 89 +++++++++++++++++++++++ app/DataProviders/DataProviderBuilder.php | 6 +- app/Helpers/CacheKey.php | 35 +++++++-- config/trwl.php | 5 +- 4 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 app/DataProviders/CachedHafas.php diff --git a/app/DataProviders/CachedHafas.php b/app/DataProviders/CachedHafas.php new file mode 100644 index 000000000..6963bb9ac --- /dev/null +++ b/app/DataProviders/CachedHafas.php @@ -0,0 +1,89 @@ +remember($key, now()->addMinutes(15), function() use ($tripID, $lineName) { + return parent::fetchHafasTrip($tripID, $lineName); + }); + } + + public function getStations(string $query, int $results = 10): Collection { + $key = CacheKey::getHafasStationsKey($query); + + return $this->remember($key, now()->addMinutes(15), function() use ($query, $results) { + return parent::getStations($query, $results); + }); + } + + public function getDepartures(Station $station, Carbon $when, int $duration = 15, TravelType $type = null, bool $localtime = false): Collection { + $filterWhen = clone $when; + $when = clone $when; + $when->subMinutes(2); + // set cache when minutes to 0, 15, 30 or 45 + $when->minute = floor($when->minute / 15) * 15; + $when->second = 0; + + // set duration longer than 15 minutes + $duration = $duration < 15 ? 30 : $duration; + + $key = CacheKey::getHafasDeparturesKey($station->id, $when, $localtime); + + $departures = $this->remember($key, now()->addMinutes(15), function() use ($station, $when, $duration, $type, $localtime) { + return parent::getDepartures($station, $when, $duration, $type, $localtime); + }); + + // filter entries by when and duration + return $departures->filter(function($departure) use ($filterWhen, $duration) { + $depWhen = Carbon::parse($departure->when); + return $depWhen->between($filterWhen, $filterWhen->copy()->addMinutes($duration)); + }); + } + + public function getStationByRilIdentifier(string $rilIdentifier): ?Station { + $key = CacheKey::getHafasByRilIdentifierKey($rilIdentifier); + + return $this->remember($key, now()->addMinutes(15), function() use ($rilIdentifier) { + return parent::getStationByRilIdentifier($rilIdentifier); + }); + } + + public function getStationsByFuzzyRilIdentifier(string $rilIdentifier): ?Collection { + $key = CacheKey::getHafasStationsFuzzyKey($rilIdentifier); + + return $this->remember($key, now()->addMinutes(15), function() use ($rilIdentifier) { + return parent::getStationsByFuzzyRilIdentifier($rilIdentifier); + }); + } + + private function remember(string $key, Carbon $expires, callable $callback): mixed { + if (Cache::has($key)) { + CacheKey::increment('hafas_cache_hit'); + return Cache::get($key); + } + + try { + $result = $callback(); + Cache::put($key, $result, $expires); + CacheKey::increment('hafas_cache_set'); + return $result; + } catch (Throwable $e) { + Cache::put($key, null, $expires); + throw $e; + } + } +} diff --git a/app/DataProviders/DataProviderBuilder.php b/app/DataProviders/DataProviderBuilder.php index ff11dbecf..0f14fea79 100644 --- a/app/DataProviders/DataProviderBuilder.php +++ b/app/DataProviders/DataProviderBuilder.php @@ -4,7 +4,11 @@ class DataProviderBuilder { - public function build(): DataProviderInterface { + public function build(?bool $cache = null): DataProviderInterface { + if ($cache === true || ($cache === null && config('trwl.cache.hafas'))) { + return new CachedHafas(); + } + return new Hafas(); } } diff --git a/app/Helpers/CacheKey.php b/app/Helpers/CacheKey.php index 4a08c23ff..7c585a7da 100644 --- a/app/Helpers/CacheKey.php +++ b/app/Helpers/CacheKey.php @@ -19,13 +19,38 @@ class CacheKey public const string LEADERBOARD_GLOBAL_DISTANCE = 'LeaderboardGlobalDistance'; // dynamic keys - private const string LEADERBOARD_FRIENDS = 'LeaderboardFriends'; - private const string LEADERBOARD_MONTH = 'LeaderboardMonth'; - private const string STATISTICS_GLOBAL = 'StatisticsGlobal'; + private const string LEADERBOARD_FRIENDS = 'LeaderboardFriends'; + private const string LEADERBOARD_MONTH = 'LeaderboardMonth'; + private const string STATISTICS_GLOBAL = 'StatisticsGlobal'; + private const string HAFAS_TRIP = '_HafasTrip'; + private const string HAFAS_STATIONS = '_HafasStations'; + private const string HAFAS_DEPARTURES = '_HafasDepartures_%d_%s_%s'; + private const string HAFAFS_STATION_RIL = '_HafasStationRil'; + private const string HAFAS_STATIONS_FUZZY = '_HafasStationsFuzzy'; // formatting keys - private const string FOR = '%s-for-%s'; - private const string FROM_TO = '%s-from-%s-to-%s'; + private const string FOR = '%s-for-%s'; + private const string FROM_TO = '%s-from-%s-to-%s'; + + public static function getHafasTripKey(string $tripId): string { + return sprintf(self::FOR, self::HAFAS_TRIP, $tripId); + } + + public static function getHafasStationsKey(string $query): string { + return sprintf(self::FOR, self::HAFAS_STATIONS, $query); + } + + public static function getHafasDeparturesKey(string $stationId, Carbon $when, bool $localtime): string { + return sprintf(self::HAFAS_DEPARTURES, $stationId, $when->toTimeString(), $localtime ? 'local' : 'utc'); + } + + public static function getHafasByRilIdentifierKey(string $rilIdentifier): string { + return sprintf(self::FOR, self::HAFAFS_STATION_RIL, $rilIdentifier); + } + + public static function getHafasStationsFuzzyKey(string $rilIdentifier): string { + return sprintf(self::FOR, self::HAFAS_STATIONS_FUZZY, $rilIdentifier); + } public static function getFriendsLeaderboardKey(int $userId): string { return sprintf(self::FOR, self::LEADERBOARD_FRIENDS, $userId); diff --git a/config/trwl.php b/config/trwl.php index 6d8de23ba..8fb26eb79 100644 --- a/config/trwl.php +++ b/config/trwl.php @@ -12,7 +12,7 @@ 'mastodon_timeout_seconds' => env("MASTODON_TIMEOUT_SECONDS", 5), # Brouter - 'brouter' => env('BROUTER', true), + 'brouter' => env('BROUTER', true), 'brouter_url' => env('BROUTER_URL', 'https://brouter.de/'), 'brouter_timeout' => env('BROUTER_TIMEOUT', 10), @@ -55,7 +55,8 @@ ], 'cache' => [ 'global-statistics-retention-seconds' => env('GLOBAL_STATISTICS_CACHE_RETENTION_SECONDS', 60 * 60), - 'leaderboard-retention-seconds' => env('LEADERBOARD_CACHE_RETENTION_SECONDS', 5 * 60) + 'leaderboard-retention-seconds' => env('LEADERBOARD_CACHE_RETENTION_SECONDS', 5 * 60), + 'hafas' => env('HAFAS_CACHE', true), ], 'year_in_review' => [ 'alert' => env('YEAR_IN_REVIEW_ALERT', false), From 5dc27dff2d7e7eff27512ec2235074e65ed2cf00 Mon Sep 17 00:00:00 2001 From: Levin Date: Fri, 13 Dec 2024 00:08:04 +0100 Subject: [PATCH 10/11] something, something, default values --- app/DataProviders/CachedHafas.php | 2 +- app/Helpers/CacheKey.php | 7 ++++--- config/trwl.php | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/DataProviders/CachedHafas.php b/app/DataProviders/CachedHafas.php index 6963bb9ac..619367823 100644 --- a/app/DataProviders/CachedHafas.php +++ b/app/DataProviders/CachedHafas.php @@ -15,7 +15,7 @@ class CachedHafas extends Hafas implements DataProviderInterface { public function fetchHafasTrip(string $tripID, string $lineName): Trip { - $key = CacheKey::getHafasTripKey($tripID); + $key = CacheKey::getHafasTripKey($tripID, $lineName); return $this->remember($key, now()->addMinutes(15), function() use ($tripID, $lineName) { return parent::fetchHafasTrip($tripID, $lineName); diff --git a/app/Helpers/CacheKey.php b/app/Helpers/CacheKey.php index 7c585a7da..e4f75295c 100644 --- a/app/Helpers/CacheKey.php +++ b/app/Helpers/CacheKey.php @@ -22,7 +22,7 @@ class CacheKey private const string LEADERBOARD_FRIENDS = 'LeaderboardFriends'; private const string LEADERBOARD_MONTH = 'LeaderboardMonth'; private const string STATISTICS_GLOBAL = 'StatisticsGlobal'; - private const string HAFAS_TRIP = '_HafasTrip'; + private const string HAFAS_TRIP = '_HafasTrip_%s_%s'; private const string HAFAS_STATIONS = '_HafasStations'; private const string HAFAS_DEPARTURES = '_HafasDepartures_%d_%s_%s'; private const string HAFAFS_STATION_RIL = '_HafasStationRil'; @@ -32,8 +32,9 @@ class CacheKey private const string FOR = '%s-for-%s'; private const string FROM_TO = '%s-from-%s-to-%s'; - public static function getHafasTripKey(string $tripId): string { - return sprintf(self::FOR, self::HAFAS_TRIP, $tripId); + public static function getHafasTripKey(string $tripId, string $lineName): string { + $tripId = sha1($tripId); + return sprintf(self::HAFAS_TRIP, $tripId, $lineName); } public static function getHafasStationsKey(string $query): string { diff --git a/config/trwl.php b/config/trwl.php index 8fb26eb79..afb183b2a 100644 --- a/config/trwl.php +++ b/config/trwl.php @@ -56,7 +56,7 @@ 'cache' => [ 'global-statistics-retention-seconds' => env('GLOBAL_STATISTICS_CACHE_RETENTION_SECONDS', 60 * 60), 'leaderboard-retention-seconds' => env('LEADERBOARD_CACHE_RETENTION_SECONDS', 5 * 60), - 'hafas' => env('HAFAS_CACHE', true), + 'hafas' => env('HAFAS_CACHE', false), ], 'year_in_review' => [ 'alert' => env('YEAR_IN_REVIEW_ALERT', false), From 0949842925f012f6899c5b24c328818570052923 Mon Sep 17 00:00:00 2001 From: Levin Date: Fri, 13 Dec 2024 00:33:44 +0100 Subject: [PATCH 11/11] prometheus HafasCache Stats --- app/DataProviders/CachedHafas.php | 63 ++++++++++++++------- app/Helpers/CacheKey.php | 12 ++++ app/Helpers/HCK.php | 1 + app/Providers/PrometheusServiceProvider.php | 26 +++++++++ 4 files changed, 83 insertions(+), 19 deletions(-) diff --git a/app/DataProviders/CachedHafas.php b/app/DataProviders/CachedHafas.php index 619367823..77a65f02a 100644 --- a/app/DataProviders/CachedHafas.php +++ b/app/DataProviders/CachedHafas.php @@ -4,6 +4,7 @@ use App\Enum\TravelType; use App\Helpers\CacheKey; +use App\Helpers\HCK; use App\Models\Station; use App\Models\Trip; use Carbon\Carbon; @@ -13,21 +14,30 @@ class CachedHafas extends Hafas implements DataProviderInterface { - public function fetchHafasTrip(string $tripID, string $lineName): Trip { $key = CacheKey::getHafasTripKey($tripID, $lineName); - return $this->remember($key, now()->addMinutes(15), function() use ($tripID, $lineName) { - return parent::fetchHafasTrip($tripID, $lineName); - }); + return $this->remember( + $key, + now()->addMinutes(15), + function() use ($tripID, $lineName) { + return parent::fetchHafasTrip($tripID, $lineName); + }, + HCK::TRIPS_SUCCESS + ); } public function getStations(string $query, int $results = 10): Collection { $key = CacheKey::getHafasStationsKey($query); - return $this->remember($key, now()->addMinutes(15), function() use ($query, $results) { - return parent::getStations($query, $results); - }); + return $this->remember( + $key, + now()->addMinutes(15), + function() use ($query, $results) { + return parent::getStations($query, $results); + }, + HCK::LOCATIONS_SUCCESS + ); } public function getDepartures(Station $station, Carbon $when, int $duration = 15, TravelType $type = null, bool $localtime = false): Collection { @@ -43,9 +53,14 @@ public function getDepartures(Station $station, Carbon $when, int $duration = 15 $key = CacheKey::getHafasDeparturesKey($station->id, $when, $localtime); - $departures = $this->remember($key, now()->addMinutes(15), function() use ($station, $when, $duration, $type, $localtime) { - return parent::getDepartures($station, $when, $duration, $type, $localtime); - }); + $departures = $this->remember( + $key, + now()->addMinutes(15), + function() use ($station, $when, $duration, $type, $localtime) { + return parent::getDepartures($station, $when, $duration, $type, $localtime); + }, + HCK::DEPARTURES_SUCCESS + ); // filter entries by when and duration return $departures->filter(function($departure) use ($filterWhen, $duration) { @@ -57,29 +72,39 @@ public function getDepartures(Station $station, Carbon $when, int $duration = 15 public function getStationByRilIdentifier(string $rilIdentifier): ?Station { $key = CacheKey::getHafasByRilIdentifierKey($rilIdentifier); - return $this->remember($key, now()->addMinutes(15), function() use ($rilIdentifier) { - return parent::getStationByRilIdentifier($rilIdentifier); - }); + return $this->remember( + $key, + now()->addMinutes(15), + function() use ($rilIdentifier) { + return parent::getStationByRilIdentifier($rilIdentifier); + }, + HCK::STATIONS_SUCCESS + ); } public function getStationsByFuzzyRilIdentifier(string $rilIdentifier): ?Collection { $key = CacheKey::getHafasStationsFuzzyKey($rilIdentifier); - return $this->remember($key, now()->addMinutes(15), function() use ($rilIdentifier) { - return parent::getStationsByFuzzyRilIdentifier($rilIdentifier); - }); + return $this->remember( + $key, + now()->addMinutes(15), + function() use ($rilIdentifier) { + return parent::getStationsByFuzzyRilIdentifier($rilIdentifier); + }, + HCK::STATIONS_SUCCESS + ); } - private function remember(string $key, Carbon $expires, callable $callback): mixed { + private function remember(string $key, Carbon $expires, callable $callback, ?string $ident = null): mixed { if (Cache::has($key)) { - CacheKey::increment('hafas_cache_hit'); + CacheKey::increment(CacheKey::getHafasCacheHitKey($ident)); return Cache::get($key); } try { $result = $callback(); + CacheKey::increment(CacheKey::getHafasCacheSetKey($ident)); Cache::put($key, $result, $expires); - CacheKey::increment('hafas_cache_set'); return $result; } catch (Throwable $e) { Cache::put($key, null, $expires); diff --git a/app/Helpers/CacheKey.php b/app/Helpers/CacheKey.php index e4f75295c..8c73b842b 100644 --- a/app/Helpers/CacheKey.php +++ b/app/Helpers/CacheKey.php @@ -27,11 +27,23 @@ class CacheKey private const string HAFAS_DEPARTURES = '_HafasDepartures_%d_%s_%s'; private const string HAFAFS_STATION_RIL = '_HafasStationRil'; private const string HAFAS_STATIONS_FUZZY = '_HafasStationsFuzzy'; + private const string HAFAS_CACHE_HIT = '_HafasCacheHit_%s'; + private const string HAFAS_CACHE_SET = '_HafasCacheSet_%s'; // formatting keys private const string FOR = '%s-for-%s'; private const string FROM_TO = '%s-from-%s-to-%s'; + public static function getHafasCacheHitKey(string $key): string { + $key = str_replace('monitoring-counter-', '', $key); + return sprintf(self::HAFAS_CACHE_HIT, $key); + } + + public static function getHafasCacheSetKey(string $key): string { + $key = str_replace('monitoring-counter-', '', $key); + return sprintf(self::HAFAS_CACHE_SET, $key); + } + public static function getHafasTripKey(string $tripId, string $lineName): string { $tripId = sha1($tripId); return sprintf(self::HAFAS_TRIP, $tripId, $lineName); diff --git a/app/Helpers/HCK.php b/app/Helpers/HCK.php index 80afc9e6f..951417fc6 100644 --- a/app/Helpers/HCK.php +++ b/app/Helpers/HCK.php @@ -33,6 +33,7 @@ public static function getFailures(): array { return [ self::DEPARTURES_FAILURE => 'Departures', self::TRIPS_FAILURE => 'Trips', + self::TRIPS_502 => 'Trips502', self::STOPS_FAILURE => 'Stops', self::STATIONS_FAILURE => 'Stations', self::LOCATIONS_FAILURE => 'Locations', diff --git a/app/Providers/PrometheusServiceProvider.php b/app/Providers/PrometheusServiceProvider.php index 0c78a9c19..be0ec1c67 100644 --- a/app/Providers/PrometheusServiceProvider.php +++ b/app/Providers/PrometheusServiceProvider.php @@ -115,6 +115,32 @@ public function register() { return $this->getHafasByType(HCK::getSuccesses()); }); + Prometheus::addGauge("hafas_cache_hits") + ->helpText("How many hafas requests have been served from cache?") + ->labels(["request_name"]) + ->value(function() { + $values = []; + foreach (HCK::getSuccesses() as $key => $name) { + $key = CacheKey::getHafasCacheHitKey($key); + $values[$name] = Cache::get($key, 0); + } + + return array_map(fn($value, $key) => [$value, [$key]], $values, array_keys($values)); + }); + + Prometheus::addGauge("hafas_cache_sets") + ->helpText("How many hafas requests have been stored in cache?") + ->labels(["request_name"]) + ->value(function() { + $values = []; + foreach (HCK::getSuccesses() as $key => $name) { + $key = CacheKey::getHafasCacheSetKey($key); + $values[$name] = Cache::get($key, 0); + } + + return array_map(fn($value, $key) => [$value, [$key]], $values, array_keys($values)); + }); + Prometheus::addGauge("completed_jobs_count") ->helpText("How many jobs are done? Old items from queue monitor table are deleted after 7 days.") ->labels(["job_name", "status", "queue"])