From 9506881792d48a13a4ca341b1e527fb8554420b1 Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Sun, 15 Mar 2020 12:50:38 +0100 Subject: [PATCH] #35 - WIP - Add scheduled stop repository --- .../Database/FieldFilterDatabaseHandler.php | 5 + .../Database/GenericWithDatabaseHandler.php | 99 +++++++++++++++++++ .../RelatedFilterDatabaseGenericHandler.php | 6 ++ src/Model/ScheduledStop.php | 20 +++- src/Model/Track.php | 20 +++- src/Model/Trip.php | 16 +++ src/Provider/Database/DatabaseRepository.php | 3 + .../Database/GenericScheduleRepository.php | 17 +++- .../Database/GenericStopRepository.php | 3 +- src/Provider/ScheduleRepository.php | 2 +- .../ZtmGdanskDepartureRepository.php | 65 ++++++++++-- src/Service/EntityConverter.php | 7 +- src/Service/ScheduledStopConverter.php | 6 +- 13 files changed, 247 insertions(+), 22 deletions(-) create mode 100644 src/Handler/Database/GenericWithDatabaseHandler.php diff --git a/src/Handler/Database/FieldFilterDatabaseHandler.php b/src/Handler/Database/FieldFilterDatabaseHandler.php index 0bfc83c..e7e8b1a 100644 --- a/src/Handler/Database/FieldFilterDatabaseHandler.php +++ b/src/Handler/Database/FieldFilterDatabaseHandler.php @@ -5,6 +5,7 @@ namespace App\Handler\Database; use App\Event\HandleDatabaseModifierEvent; use App\Event\HandleModifierEvent; use App\Handler\ModifierHandler; +use App\Model\ScheduledStop; use App\Model\Stop; use App\Modifier\FieldFilter; use function App\Functions\encapsulate; @@ -15,6 +16,10 @@ class FieldFilterDatabaseHandler implements ModifierHandler Stop::class => [ 'name' => 'name', ], + ScheduledStop::class => [ + 'departure' => 'departure', + 'arrival' => 'arrival', + ] ]; public function process(HandleModifierEvent $event) diff --git a/src/Handler/Database/GenericWithDatabaseHandler.php b/src/Handler/Database/GenericWithDatabaseHandler.php new file mode 100644 index 0000000..35dc3b9 --- /dev/null +++ b/src/Handler/Database/GenericWithDatabaseHandler.php @@ -0,0 +1,99 @@ + [ + 'line' => 'line', + 'stops' => 'stopsInTrack', + ], + TrackStop::class => [ + 'track' => 'track', + ], + ScheduledStop::class => [ + 'trip' => 'trip', + 'track' => 'trip.track', + 'destination' => 'trip.track.final', + ], + ]; + + private $em; + private $id; + private $references; + + public function __construct( + EntityManagerInterface $em, + IdUtils $idUtils, + EntityReferenceFactory $references + ) { + $this->em = $em; + $this->id = $idUtils; + $this->references = $references; + } + + public function process(HandleModifierEvent $event) + { + if (!$event instanceof HandleDatabaseModifierEvent) { + return; + } + + /** @var RelatedFilter $modifier */ + $modifier = $event->getModifier(); + $builder = $event->getBuilder(); + $alias = $event->getMeta()['alias']; + $type = $event->getMeta()['type']; + + if (!array_key_exists($modifier->getRelationship(), $this->mapping[$type])) { + throw new \InvalidArgumentException( + sprintf("Relationship %s is not supported for .", $type) + ); + } + + $relationship = $this->mapping[$type][$modifier->getRelationship()]; + + foreach ($this->getRelationships($relationship, $alias) as [$relationshipPath, $relationshipAlias]) { + $selected = collect($builder->getDQLPart('select'))->flatMap(property('parts')); + + if ($selected->contains($relationshipAlias)) { + continue; + } + + $builder + ->join($relationshipPath, $relationshipAlias) + ->addSelect($relationshipAlias); + } + } + + /** + * @inheritDoc + */ + public static function getSubscribedServices() + { + return [ + TrackByStopDatabaseHandler::class, + ]; + } + + private function getRelationships($relationship, $alias) + { + $relationships = explode('.', $relationship); + + foreach ($relationships as $current) { + yield [sprintf("%s.%s", $alias, $current), $alias = sprintf('%s_%s', $alias, $current)]; + } + } +} diff --git a/src/Handler/Database/RelatedFilterDatabaseGenericHandler.php b/src/Handler/Database/RelatedFilterDatabaseGenericHandler.php index 315b99f..dbaa48d 100644 --- a/src/Handler/Database/RelatedFilterDatabaseGenericHandler.php +++ b/src/Handler/Database/RelatedFilterDatabaseGenericHandler.php @@ -6,9 +6,11 @@ use App\Event\HandleDatabaseModifierEvent; use App\Event\HandleModifierEvent; use App\Handler\ModifierHandler; use App\Model\Line; +use App\Model\ScheduledStop; use App\Model\Stop; use App\Model\Track; use App\Model\TrackStop; +use App\Model\Trip; use App\Modifier\RelatedFilter; use App\Service\IdUtils; use App\Service\EntityReferenceFactory; @@ -27,6 +29,10 @@ class RelatedFilterDatabaseGenericHandler implements ModifierHandler, ServiceSub Stop::class => 'stop', Track::class => 'track', ], + ScheduledStop::class => [ + Stop::class => 'stop', + Trip::class => 'trip', + ], ]; private $em; diff --git a/src/Model/ScheduledStop.php b/src/Model/ScheduledStop.php index 2ae78f2..5042a49 100644 --- a/src/Model/ScheduledStop.php +++ b/src/Model/ScheduledStop.php @@ -7,17 +7,23 @@ use Carbon\Carbon; class ScheduledStop extends TrackStop { /** - * Arrival time + * Arrival time. * @var Carbon */ private $arrival; /** - * Departure time + * Departure time. * @var Carbon */ private $departure; + /** + * Exact trip that this scheduled stop is part of. + * @var Trip|null + */ + private $trip; + public function getArrival(): Carbon { return $this->arrival; @@ -37,4 +43,14 @@ class ScheduledStop extends TrackStop { $this->departure = $departure; } + + public function getTrip(): ?Trip + { + return $this->trip; + } + + public function setTrip(?Trip $trip): void + { + $this->trip = $trip; + } } diff --git a/src/Model/Track.php b/src/Model/Track.php index bd62451..acb26f0 100644 --- a/src/Model/Track.php +++ b/src/Model/Track.php @@ -42,6 +42,14 @@ class Track implements Referable, Fillable */ private $stops; + /** + * Destination stop of this track + * @var Stop|null + * @Serializer\Type(Stop::class) + * @SWG\Property(ref=@Model(type=Stop::class)) + */ + private $destination; + /** * Track constructor. */ @@ -89,4 +97,14 @@ class Track implements Referable, Fillable { return $this->stops = collect($stops); } -} \ No newline at end of file + + public function getDestination(): ?Stop + { + return $this->destination; + } + + public function setDestination(?Stop $destination): void + { + $this->destination = $destination; + } +} diff --git a/src/Model/Trip.php b/src/Model/Trip.php index eaea219..f9da46f 100644 --- a/src/Model/Trip.php +++ b/src/Model/Trip.php @@ -40,6 +40,12 @@ class Trip implements Referable, Fillable */ private $schedule; + /** + * Destination stop of this trip + * @var Stop|null + */ + private $destination; + /** * Track constructor. */ @@ -87,4 +93,14 @@ class Trip implements Referable, Fillable { return $this->schedule = collect($schedule); } + + public function getDestination(): ?Stop + { + return $this->destination; + } + + public function setDestination(?Stop $destination): void + { + $this->destination = $destination; + } } diff --git a/src/Provider/Database/DatabaseRepository.php b/src/Provider/Database/DatabaseRepository.php index e4021ec..898a431 100644 --- a/src/Provider/Database/DatabaseRepository.php +++ b/src/Provider/Database/DatabaseRepository.php @@ -9,6 +9,7 @@ use App\Handler\Database\FieldFilterDatabaseHandler; use App\Handler\Database\IdFilterDatabaseHandler; use App\Handler\Database\LimitDatabaseHandler; use App\Handler\Database\RelatedFilterDatabaseGenericHandler; +use App\Handler\Database\GenericWithDatabaseHandler; use App\Handler\ModifierHandler; use App\Handler\PostProcessingHandler; use App\Model\Referable; @@ -17,6 +18,7 @@ use App\Modifier\IdFilter; use App\Modifier\Limit; use App\Modifier\Modifier; use App\Modifier\RelatedFilter; +use App\Modifier\With; use App\Provider\Repository; use App\Service\Converter; use App\Service\HandlerProvider; @@ -64,6 +66,7 @@ abstract class DatabaseRepository implements Repository Limit::class => LimitDatabaseHandler::class, FieldFilter::class => FieldFilterDatabaseHandler::class, RelatedFilter::class => RelatedFilterDatabaseGenericHandler::class, + With::class => GenericWithDatabaseHandler::class, ], static::getHandlers())); } diff --git a/src/Provider/Database/GenericScheduleRepository.php b/src/Provider/Database/GenericScheduleRepository.php index ed7adbe..10c4bdd 100644 --- a/src/Provider/Database/GenericScheduleRepository.php +++ b/src/Provider/Database/GenericScheduleRepository.php @@ -9,8 +9,10 @@ use App\Entity\TripEntity; use App\Entity\TripStopEntity; use App\Model\Departure; use App\Model\Line; +use App\Model\ScheduledStop; use App\Model\Stop; use App\Model\Vehicle; +use App\Modifier\Modifier; use App\Provider\ScheduleRepository; use Carbon\Carbon; use Tightenco\Collect\Support\Collection; @@ -71,8 +73,19 @@ class GenericScheduleRepository extends DatabaseRepository implements ScheduleRe }); } - protected static function getHandlers() + public function all(Modifier ...$modifiers): Collection { - return []; + $builder = $this->em + ->createQueryBuilder() + ->select('trip_stop') + ->from(TripStopEntity::class, 'trip_stop') + ->orderBy('trip_stop.departure', 'ASC') + ; + + return $this->allFromQueryBuilder($builder, $modifiers, [ + 'alias' => 'trip_stop', + 'type' => ScheduledStop::class, + 'entity' => TripStopEntity::class, + ]); } } diff --git a/src/Provider/Database/GenericStopRepository.php b/src/Provider/Database/GenericStopRepository.php index 82e41e6..7610402 100644 --- a/src/Provider/Database/GenericStopRepository.php +++ b/src/Provider/Database/GenericStopRepository.php @@ -3,6 +3,7 @@ namespace App\Provider\Database; use App\Entity\StopEntity; +use App\Handler\Database\GenericWithDatabaseHandler; use App\Handler\Database\WithDestinationsDatabaseHandler; use App\Model\Stop; use App\Modifier\Modifier; @@ -33,7 +34,7 @@ class GenericStopRepository extends DatabaseRepository implements StopRepository With::class => function (With $modifier) { return $modifier->getRelationship() === 'destinations' ? WithDestinationsDatabaseHandler::class - : GenericWithHandler::class; + : GenericWithDatabaseHandler::class; }, ]); } diff --git a/src/Provider/ScheduleRepository.php b/src/Provider/ScheduleRepository.php index 951a20f..e650d22 100644 --- a/src/Provider/ScheduleRepository.php +++ b/src/Provider/ScheduleRepository.php @@ -6,7 +6,7 @@ use App\Model\Stop; use Carbon\Carbon; use Tightenco\Collect\Support\Collection; -interface ScheduleRepository +interface ScheduleRepository extends FluentRepository { const DEFAULT_DEPARTURES_COUNT = 16; diff --git a/src/Provider/ZtmGdansk/ZtmGdanskDepartureRepository.php b/src/Provider/ZtmGdansk/ZtmGdanskDepartureRepository.php index e7f7dc6..48fefc2 100644 --- a/src/Provider/ZtmGdansk/ZtmGdanskDepartureRepository.php +++ b/src/Provider/ZtmGdansk/ZtmGdanskDepartureRepository.php @@ -4,9 +4,14 @@ namespace App\Provider\ZtmGdansk; use App\Model\Departure; use App\Model\Line; +use App\Model\ScheduledStop; use App\Model\Stop; use App\Model\Vehicle; +use App\Modifier\FieldFilter; use App\Modifier\IdFilter; +use App\Modifier\Limit; +use App\Modifier\RelatedFilter; +use App\Modifier\With; use App\Provider\Database\GenericScheduleRepository; use App\Provider\DepartureRepository; use App\Provider\LineRepository; @@ -90,13 +95,33 @@ class ZtmGdanskDepartureRepository implements DepartureRepository private function getScheduledDepartures(Stop $stop, Carbon $time) { - return $this->schedule->getDeparturesForStop($stop, $time); + return $this->schedule->all( + new RelatedFilter($stop), + new FieldFilter('departure', $time, '>='), + new With('track'), + new With('destination'), + Limit::count(16) + ); } private function pair(Collection $schedule, Collection $real) { - $key = function (Departure $departure) { - return sprintf("%s::%s", $departure->getLine()->getSymbol(), $departure->getScheduled()->format("H:i")); + $key = function ($departure) { + if ($departure instanceof Departure) { + return sprintf( + "%s::%s", + $departure->getLine()->getId(), + $departure->getScheduled()->format("H:i") + ); + } elseif ($departure instanceof ScheduledStop) { + return sprintf( + "%s::%s", + $departure->getTrack()->getLine()->getId(), + $departure->getDeparture()->format("H:i") + ); + } else { + throw new \Exception(); + } }; $schedule = $schedule->keyBy($key)->all(); @@ -110,21 +135,27 @@ class ZtmGdanskDepartureRepository implements DepartureRepository unset($schedule[$key]); } - return [ $real, $scheduled ]; - })->merge(collect($schedule)->map(function (Departure $scheduled) { - return [ null, $scheduled ]; + return [ + 'estimated' => $real, + 'scheduled' => $scheduled, + ]; + })->merge(collect($schedule)->map(function (ScheduledStop $scheduled) { + return [ + 'estimated' => null, + 'scheduled' => $scheduled, + ]; }))->map(function ($pair) { - return $this->merge(...$pair); + return $this->merge($pair['estimated'], $pair['scheduled']); })->sortBy(function (Departure $departure) { $time = $departure->getEstimated() ?? $departure->getScheduled(); return $time->getTimestamp(); }); } - private function merge(?Departure $real, ?Departure $scheduled) + private function merge(?Departure $real, ?ScheduledStop $scheduled) { if (!$real) { - return $scheduled; + return $this->convertScheduledStopToDeparture($scheduled); } if (!$scheduled) { @@ -132,10 +163,24 @@ class ZtmGdanskDepartureRepository implements DepartureRepository } $departure = clone $real; - $departure->setDisplay($scheduled->getDisplay()); + $departure->setDisplay($real->getDisplay()); $departure->setTrack($scheduled->getTrack()); $departure->setTrip($scheduled->getTrip()); return $departure; } + + private function convertScheduledStopToDeparture(ScheduledStop $stop): Departure + { + $converted = new Departure(); + + $converted->setDisplay($stop->getTrack()->getDestination()->getName()); + $converted->setLine($stop->getTrack()->getLine()); + $converted->setTrack($stop->getTrack()); + $converted->setTrip($stop->getTrip()); + $converted->setScheduled($stop->getDeparture()); + $converted->setStop($stop->getStop()); + + return $converted; + } } diff --git a/src/Service/EntityConverter.php b/src/Service/EntityConverter.php index 222a7c8..3ec6d4b 100644 --- a/src/Service/EntityConverter.php +++ b/src/Service/EntityConverter.php @@ -32,7 +32,7 @@ final class EntityConverter implements Converter, RecursiveConverter */ public function convert($entity, array $cache = []) { - if (array_key_exists($key = get_class($entity).':'.$this->getId($entity), $cache)) { + if (array_key_exists($key = get_class($entity) . ':' . $this->getId($entity), $cache)) { return $cache[$key]; } @@ -78,6 +78,7 @@ final class EntityConverter implements Converter, RecursiveConverter ->map(t\property('stop')) ->map($convert), 'line' => $convert($entity->getLine()), + 'destination' => $convert($entity->getFinal()->getStop()), ]); break; @@ -154,7 +155,7 @@ final class EntityConverter implements Converter, RecursiveConverter private function create(Entity $entity) { - $id = $this->id->of($entity); + $id = $this->id->of($entity); $class = $this->getModelClassForEntity($entity); return $class::createFromArray(['id' => $id]); @@ -162,7 +163,7 @@ final class EntityConverter implements Converter, RecursiveConverter private function reference(Entity $entity) { - $id = $this->id->strip($this->getId($entity)); + $id = $this->id->strip($this->getId($entity)); $class = $this->getModelClassForEntity($entity); return $this->reference->get($class, ['id' => $id]); diff --git a/src/Service/ScheduledStopConverter.php b/src/Service/ScheduledStopConverter.php index c00faae..d4cfc97 100644 --- a/src/Service/ScheduledStopConverter.php +++ b/src/Service/ScheduledStopConverter.php @@ -5,6 +5,7 @@ namespace App\Service; use App\Entity\TrackStopEntity; use App\Entity\TripStopEntity; use App\Model\ScheduledStop; +use App\Model\TrackStop; class ScheduledStopConverter implements Converter, RecursiveConverter { @@ -12,9 +13,8 @@ class ScheduledStopConverter implements Converter, RecursiveConverter public function convert($entity) { - if ($entity instanceof TrackStopEntity) { - return ScheduledStop::createFromArray([ + return TrackStop::createFromArray([ 'stop' => $this->parent->convert($entity->getStop()), 'track' => $this->parent->convert($entity->getTrack()), 'order' => $entity->getOrder(), @@ -27,6 +27,8 @@ class ScheduledStopConverter implements Converter, RecursiveConverter 'departure' => $entity->getDeparture(), 'stop' => $this->parent->convert($entity->getStop()), 'order' => $entity->getOrder(), + 'track' => $this->parent->convert($entity->getTrip()->getTrack()), + 'trip' => $this->parent->convert($entity->getTrip()), ]); } }