From 1bdea1926d6970e3b70ee1219089a9960f40e820 Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Wed, 22 Jan 2020 20:40:39 +0100 Subject: [PATCH] Trip controller for accessing stops scheduled in trip --- config/services.yaml | 13 +++++ .../components/departures/departure.html | 55 ++++++++++--------- resources/ts/components/departures.ts | 10 ++-- resources/ts/model/trip.ts | 16 ++++++ src/Controller/Api/v1/TripController.php | 25 +++++++++ .../{ScheduleStop.php => ScheduledStop.php} | 4 +- src/Model/Trip.php | 11 +++- src/Provider/Database/DatabaseRepository.php | 8 +-- .../Database/GenericTripRepository.php | 27 +++++++++ src/Provider/Dummy/DummyProvider.php | 8 ++- src/Provider/Provider.php | 3 +- src/Provider/TripRepository.php | 10 ++++ src/Provider/ZtmGdansk/ZtmGdanskProvider.php | 11 ++++ src/Service/AggregateConverter.php | 43 +++++++++++++++ src/Service/Converter.php | 17 ++++++ src/Service/EntityConverter.php | 33 ++++++----- src/Service/RecursiveConverter.php | 9 +++ src/Service/RecursiveConverterTrait.php | 21 +++++++ src/Service/RepositoryParameterConverter.php | 8 ++- src/Service/ScheduledStopConverter.php | 28 ++++++++++ src/Service/SerializerContextFactory.php | 2 +- 21 files changed, 303 insertions(+), 59 deletions(-) create mode 100644 resources/ts/model/trip.ts create mode 100644 src/Controller/Api/v1/TripController.php rename src/Model/{ScheduleStop.php => ScheduledStop.php} (96%) create mode 100644 src/Provider/Database/GenericTripRepository.php create mode 100644 src/Provider/TripRepository.php create mode 100644 src/Service/AggregateConverter.php create mode 100644 src/Service/Converter.php create mode 100644 src/Service/RecursiveConverter.php create mode 100644 src/Service/RecursiveConverterTrait.php create mode 100644 src/Service/ScheduledStopConverter.php diff --git a/config/services.yaml b/config/services.yaml index 4bcbebc..853ef0a 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -68,6 +68,19 @@ services: ProxyManager\Configuration: '@proxy.config' + # converter + App\Service\AggregateConverter: + arguments: + - !tagged_iterator app.converter + + App\Service\Converter: '@App\Service\AggregateConverter' + + App\Service\EntityConverter: + tags: ['app.converter'] + + App\Service\ScheduledStopConverter: + tags: ['app.converter'] + # serializer configuration App\Service\SerializerContextFactory: arguments: diff --git a/resources/components/departures/departure.html b/resources/components/departures/departure.html index 11542bb..f14a0c2 100644 --- a/resources/components/departures/departure.html +++ b/resources/components/departures/departure.html @@ -1,33 +1,38 @@ -
  • -
    - -
    {{ departure.display }}
    -
    +
  • +
    +
    + +
    {{ departure.display }}
    +
    -
    - - - - - - {{ departure.scheduled.format('HH:mm') }} - - +
    + + + + + + + {{ departure.scheduled.format('HH:mm') }} + + {{ departure.delay|signed }}s - + - {{ time.format('HH:mm') }} -
    + {{ time.format('HH:mm') }} +
    -
    - - +
    + + -
    - +
    + +
    + +
  • diff --git a/resources/ts/components/departures.ts b/resources/ts/components/departures.ts index 99114e4..7e864b6 100644 --- a/resources/ts/components/departures.ts +++ b/resources/ts/components/departures.ts @@ -8,17 +8,17 @@ const { State } = namespace('departures'); @Component({ template: require("../../components/departures.html"), store }) export class DeparturesComponent extends Vue { - @State - departures: Departure[]; + @State departures: Departure[]; @Prop(Array) stops: Stop[]; } -@Component({ template: require("../../components/departures/departure.html"), store }) +@Component({ template: require("../../components/departures/departure.html") }) export class DepartureComponent extends Vue { - @Prop(Object) - departure: Departure; + @Prop(Object) departure: Departure; + + showTrip: boolean = false; get timeDiffers() { const departure = this.departure; diff --git a/resources/ts/model/trip.ts b/resources/ts/model/trip.ts new file mode 100644 index 0000000..ad21786 --- /dev/null +++ b/resources/ts/model/trip.ts @@ -0,0 +1,16 @@ +import { Stop } from "./stop"; +import { Moment } from "moment"; + +export type ScheduledStop = { + stop: Stop, + departure: Moment, + arrival: Moment, + order: number, +} + +export type Trip = { + id: string, + schedule: ScheduledStop[], + variant: string, + description: string, +} diff --git a/src/Controller/Api/v1/TripController.php b/src/Controller/Api/v1/TripController.php new file mode 100644 index 0000000..9f1583e --- /dev/null +++ b/src/Controller/Api/v1/TripController.php @@ -0,0 +1,25 @@ +getById($id); + + return $this->json($trip, Response::HTTP_OK, [], $this->serializerContextFactory->create(Trip::class)); + } +} diff --git a/src/Model/ScheduleStop.php b/src/Model/ScheduledStop.php similarity index 96% rename from src/Model/ScheduleStop.php rename to src/Model/ScheduledStop.php index 067df7f..abfda2d 100644 --- a/src/Model/ScheduleStop.php +++ b/src/Model/ScheduledStop.php @@ -4,7 +4,7 @@ namespace App\Model; use Carbon\Carbon; -class ScheduleStop implements Fillable +class ScheduledStop implements Fillable { use FillTrait; @@ -71,4 +71,4 @@ class ScheduleStop implements Fillable { $this->departure = $departure; } -} \ No newline at end of file +} diff --git a/src/Model/Trip.php b/src/Model/Trip.php index dafb13f..eaea219 100644 --- a/src/Model/Trip.php +++ b/src/Model/Trip.php @@ -2,6 +2,8 @@ namespace App\Model; +use App\Serialization\SerializeAs; +use JMS\Serializer\Annotation as Serializer; use Tightenco\Collect\Support\Collection; class Trip implements Referable, Fillable @@ -10,6 +12,7 @@ class Trip implements Referable, Fillable /** * Line variant describing trip, for example 'a' + * @Serializer\Type("string") * @var string|null * */ @@ -18,18 +21,22 @@ class Trip implements Referable, Fillable /** * Trip description * @var string|null + * @Serializer\Type("string") */ private $description; /** * Line reference * @var ?Track + * @Serializer\Type("App\Model\Track") + * @SerializeAs({"Default": "Identity"}) */ private $track; /** * Stops in track - * @var Collection + * @Serializer\Type("Collection") + * @var Collection */ private $schedule; @@ -80,4 +87,4 @@ class Trip implements Referable, Fillable { return $this->schedule = collect($schedule); } -} \ No newline at end of file +} diff --git a/src/Provider/Database/DatabaseRepository.php b/src/Provider/Database/DatabaseRepository.php index 0afafc0..3a97eff 100644 --- a/src/Provider/Database/DatabaseRepository.php +++ b/src/Provider/Database/DatabaseRepository.php @@ -5,7 +5,7 @@ namespace App\Provider\Database; use App\Entity\Entity; use App\Entity\ProviderEntity; use App\Model\Referable; -use App\Service\EntityConverter; +use App\Service\Converter; use App\Service\IdUtils; use Doctrine\ORM\EntityManagerInterface; use Kadet\Functional as f; @@ -21,7 +21,7 @@ class DatabaseRepository /** @var IdUtils */ protected $id; - /** @var EntityConverter */ + /** @var Converter */ protected $converter; /** @@ -29,7 +29,7 @@ class DatabaseRepository * * @param EntityManagerInterface $em */ - public function __construct(EntityManagerInterface $em, IdUtils $id, EntityConverter $converter) + public function __construct(EntityManagerInterface $em, IdUtils $id, Converter $converter) { $this->em = $em; $this->id = $id; @@ -56,4 +56,4 @@ class DatabaseRepository return $this->em->getReference($class, $id); } -} \ No newline at end of file +} diff --git a/src/Provider/Database/GenericTripRepository.php b/src/Provider/Database/GenericTripRepository.php new file mode 100644 index 0000000..eab38cb --- /dev/null +++ b/src/Provider/Database/GenericTripRepository.php @@ -0,0 +1,27 @@ +id->generate($this->provider, $id); + + $trip = $this->em + ->createQueryBuilder() + ->from(TripEntity::class, 't') + ->join('t.stops', 'ts') + ->select('t', 'ts') + ->where('t.id = :id') + ->getQuery() + ->setParameter('id', $id) + ->getOneOrNullResult(); + + return $this->convert($trip); + } +} diff --git a/src/Provider/Dummy/DummyProvider.php b/src/Provider/Dummy/DummyProvider.php index 0a996da..722c03a 100644 --- a/src/Provider/Dummy/DummyProvider.php +++ b/src/Provider/Dummy/DummyProvider.php @@ -9,6 +9,7 @@ use App\Provider\MessageRepository; use App\Provider\Provider; use App\Provider\StopRepository; use App\Provider\TrackRepository; +use App\Provider\TripRepository; use Carbon\Carbon; class DummyProvider implements Provider @@ -76,4 +77,9 @@ class DummyProvider implements Provider { return null; } -} \ No newline at end of file + + public function getTripRepository(): TripRepository + { + throw new NotSupportedException(); + } +} diff --git a/src/Provider/Provider.php b/src/Provider/Provider.php index f82d412..d270290 100644 --- a/src/Provider/Provider.php +++ b/src/Provider/Provider.php @@ -11,6 +11,7 @@ interface Provider public function getStopRepository(): StopRepository; public function getMessageRepository(): MessageRepository; public function getTrackRepository(): TrackRepository; + public function getTripRepository(): TripRepository; public function getName(): string; public function getShortName(): string; @@ -18,4 +19,4 @@ interface Provider public function getAttribution(): ?string; public function getLastUpdate(): ?Carbon; -} \ No newline at end of file +} diff --git a/src/Provider/TripRepository.php b/src/Provider/TripRepository.php new file mode 100644 index 0000000..4a82521 --- /dev/null +++ b/src/Provider/TripRepository.php @@ -0,0 +1,10 @@ +withProvider($provider); $tracks = $tracks->withProvider($provider); $schedule = $schedule->withProvider($provider); + $trips = $trips->withProvider($provider); $this->lines = $lines; $this->departures = new ZtmGdanskDepartureRepository($lines, $schedule, $referenceFactory); @@ -73,6 +78,7 @@ class ZtmGdanskProvider implements Provider $this->messages = $messages; $this->tracks = $tracks; $this->entity = $provider; + $this->trips = $trips; } public function getDepartureRepository(): DepartureRepository @@ -100,6 +106,11 @@ class ZtmGdanskProvider implements Provider return $this->tracks; } + public function getTripRepository(): TripRepository + { + return $this->trips; + } + public function getLastUpdate(): ?Carbon { return $this->entity->getUpdateDate(); diff --git a/src/Service/AggregateConverter.php b/src/Service/AggregateConverter.php new file mode 100644 index 0000000..45cc308 --- /dev/null +++ b/src/Service/AggregateConverter.php @@ -0,0 +1,43 @@ +converters = collect($converters)->each(function (Converter $converter) { + if ($converter instanceof RecursiveConverter) { + $converter->setParent($this); + } + }); + } + + public function convert($entity) + { + /** @var Converter $converter */ + $converter = $this->converters->first(function (Converter $converter) use ($entity) { + return $converter->supports($entity); + }); + + if ($converter == null) { + throw new \InvalidArgumentException(sprintf('Cannot convert entity of type %s.', is_object($entity) ? get_class($entity) : gettype($entity))); + } + + return $converter->convert($entity); + } + + public function supports($entity) + { + return $this->converters->some(function (Converter $converter) use ($entity) { + return $converter->supports($entity); + }); + } +} diff --git a/src/Service/Converter.php b/src/Service/Converter.php new file mode 100644 index 0000000..f2de3b7 --- /dev/null +++ b/src/Service/Converter.php @@ -0,0 +1,17 @@ +getId($entity), $cache)) { return $cache[$key]; @@ -40,7 +42,11 @@ final class EntityConverter $result = $this->create($entity); $cache = $cache + [$key => $result]; - $convert = f\partial([$this, 'convert'], _, $cache); + $convert = function ($entity) use ($cache) { + return $this->supports($entity) + ? $this->convert($entity, $cache) + : $this->parent->convert($entity); + }; switch (true) { case $entity instanceof OperatorEntity: @@ -95,15 +101,6 @@ final class EntityConverter 'track' => $convert($entity->getTrack()), ]); break; - - case $entity instanceof TripStopEntity: - $result->fill([ - 'arrival' => $entity->getArrival(), - 'departure' => $entity->getDeparture(), - 'stop' => $convert($entity->getStop()), - 'order' => $convert($entity->getOrder()), - ]); - break; } return $result; @@ -149,9 +146,6 @@ final class EntityConverter case $entity instanceof TripEntity: return Trip::class; - case $entity instanceof TripStopEntity: - return ScheduleStop::class; - default: return false; } @@ -172,4 +166,9 @@ final class EntityConverter return $this->reference->get($class, ['id' => $id]); } + + public function supports($entity) + { + return $entity instanceof Entity; + } } diff --git a/src/Service/RecursiveConverter.php b/src/Service/RecursiveConverter.php new file mode 100644 index 0000000..971ebff --- /dev/null +++ b/src/Service/RecursiveConverter.php @@ -0,0 +1,9 @@ +parent = $converter; + } + + public function getParent(): ?Converter + { + return $this->parent; + } +} diff --git a/src/Service/RepositoryParameterConverter.php b/src/Service/RepositoryParameterConverter.php index d5b6704..d360307 100644 --- a/src/Service/RepositoryParameterConverter.php +++ b/src/Service/RepositoryParameterConverter.php @@ -10,6 +10,7 @@ use App\Provider\LineRepository; use App\Provider\MessageRepository; use App\Provider\StopRepository; use App\Provider\TrackRepository; +use App\Provider\TripRepository; use const Kadet\Functional\_; use function Kadet\Functional\any; use function Kadet\Functional\curry; @@ -64,6 +65,10 @@ class RepositoryParameterConverter implements ParamConverterInterface $request->attributes->set($configuration->getName(), $provider->getTrackRepository()); break; + case is_a($class, TripRepository::class, true): + $request->attributes->set($configuration->getName(), $provider->getTripRepository()); + break; + default: return false; } @@ -82,8 +87,9 @@ class RepositoryParameterConverter implements ParamConverterInterface DepartureRepository::class, MessageRepository::class, TrackRepository::class, + TripRepository::class, ])); return $supports($configuration->getClass()); } -} \ No newline at end of file +} diff --git a/src/Service/ScheduledStopConverter.php b/src/Service/ScheduledStopConverter.php new file mode 100644 index 0000000..da20f28 --- /dev/null +++ b/src/Service/ScheduledStopConverter.php @@ -0,0 +1,28 @@ + $entity->getArrival(), + 'departure' => $entity->getDeparture(), + 'stop' => $this->parent->convert($entity->getStop()), + 'order' => $entity->getOrder(), + ]); + } + + public function supports($entity) + { + return $entity instanceof TripStopEntity; + } +} diff --git a/src/Service/SerializerContextFactory.php b/src/Service/SerializerContextFactory.php index 04bff01..9ce181f 100644 --- a/src/Service/SerializerContextFactory.php +++ b/src/Service/SerializerContextFactory.php @@ -21,7 +21,7 @@ final class SerializerContextFactory $this->reader = $reader; } - public function create($subject, array $groups) + public function create($subject, array $groups = ['Default']) { return SerializationContext::create()->setGroups($this->groups($subject, $groups)); }