Trip controller for accessing stops scheduled in trip

This commit is contained in:
Kacper Donat 2020-01-22 20:40:39 +01:00
parent 2c9a795756
commit 1bdea1926d
21 changed files with 303 additions and 59 deletions

View File

@ -68,6 +68,19 @@ services:
ProxyManager\Configuration: '@proxy.config' 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 # serializer configuration
App\Service\SerializerContextFactory: App\Service\SerializerContextFactory:
arguments: arguments:

View File

@ -1,33 +1,38 @@
<li class="departure"> <li>
<div class="departure__line"> <div class="departure">
<line-symbol :line="departure.line"/> <div class="departure__line">
<div class="line__display">{{ departure.display }}</div> <line-symbol :line="departure.line"/>
</div> <div class="line__display">{{ departure.display }}</div>
</div>
<div class="departure__time"> <div class="departure__time">
<fa-layers v-if="!departure.estimated" class="mr-1" title="Czas rozkładowy, nieuwzględniający aktualnej sytuacji komunikacyjnej."> <fa-layers v-if="!departure.estimated" class="mr-1" title="Czas rozkładowy, nieuwzględniający aktualnej sytuacji komunikacyjnej.">
<fa :icon="['far', 'clock']"/> <fa :icon="['far', 'clock']"/>
<fa :icon="['fas', 'exclamation-triangle']" transform="shrink-5 down-4 right-6"/> <fa :icon="['fas', 'exclamation-triangle']" transform="shrink-5 down-4 right-6"/>
</fa-layers> </fa-layers>
<span :class="[ 'departure__time', 'departure__time--delayed']" v-if="timeDiffers">
{{ departure.scheduled.format('HH:mm') }} <span :class="[ 'departure__time', 'departure__time--delayed']" v-if="timeDiffers">
</span> {{ departure.scheduled.format('HH:mm') }}
<span class="badge" :class="[departure.delay < 0 ? 'badge-danger' : 'badge-warning']" </span>
v-if="departure.delay < 0 || departure.delay > 30"> <span class="badge" :class="[departure.delay < 0 ? 'badge-danger' : 'badge-warning']"
v-if="departure.delay < 0 || departure.delay > 30">
{{ departure.delay|signed }}s {{ departure.delay|signed }}s
</span> </span>
<span class="departure__time">{{ time.format('HH:mm') }}</span> <span class="departure__time">{{ time.format('HH:mm') }}</span>
</div> </div>
<div class="departure__stop"> <div class="departure__stop">
<fa :icon="['fal', 'sign']" fixed-width class="mr-1 flex-shrink-0"/> <fa :icon="['fal', 'sign']" fixed-width class="mr-1 flex-shrink-0"/>
<stop :stop="departure.stop"/> <stop :stop="departure.stop"/>
<div class="stop__actions flex-space-left"> <div class="stop__actions flex-space-left">
<button class="btn btn-action"> <button class="btn btn-action" @click="showTrip = !showTrip">
<fa :icon="['far', 'code-commit']" rotation="90" /> <fa :icon="['far', 'code-commit']" rotation="90" />
</button> </button>
</div>
</div> </div>
</div> </div>
<fold :visible="showTrip">
</fold>
</li> </li>

View File

@ -8,17 +8,17 @@ const { State } = namespace('departures');
@Component({ template: require("../../components/departures.html"), store }) @Component({ template: require("../../components/departures.html"), store })
export class DeparturesComponent extends Vue { export class DeparturesComponent extends Vue {
@State @State departures: Departure[];
departures: Departure[];
@Prop(Array) @Prop(Array)
stops: Stop[]; stops: Stop[];
} }
@Component({ template: require("../../components/departures/departure.html"), store }) @Component({ template: require("../../components/departures/departure.html") })
export class DepartureComponent extends Vue { export class DepartureComponent extends Vue {
@Prop(Object) @Prop(Object) departure: Departure;
departure: Departure;
showTrip: boolean = false;
get timeDiffers() { get timeDiffers() {
const departure = this.departure; const departure = this.departure;

View File

@ -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,
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Controller\Api\v1;
use App\Controller\Controller;
use App\Model\Trip;
use App\Provider\TripRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/trips")
*/
class TripController extends Controller
{
/**
* @Route("/{id}")
*/
public function one($id, TripRepository $repository)
{
$trip = $repository->getById($id);
return $this->json($trip, Response::HTTP_OK, [], $this->serializerContextFactory->create(Trip::class));
}
}

View File

@ -4,7 +4,7 @@ namespace App\Model;
use Carbon\Carbon; use Carbon\Carbon;
class ScheduleStop implements Fillable class ScheduledStop implements Fillable
{ {
use FillTrait; use FillTrait;

View File

@ -2,6 +2,8 @@
namespace App\Model; namespace App\Model;
use App\Serialization\SerializeAs;
use JMS\Serializer\Annotation as Serializer;
use Tightenco\Collect\Support\Collection; use Tightenco\Collect\Support\Collection;
class Trip implements Referable, Fillable class Trip implements Referable, Fillable
@ -10,6 +12,7 @@ class Trip implements Referable, Fillable
/** /**
* Line variant describing trip, for example 'a' * Line variant describing trip, for example 'a'
* @Serializer\Type("string")
* @var string|null * @var string|null
* *
*/ */
@ -18,18 +21,22 @@ class Trip implements Referable, Fillable
/** /**
* Trip description * Trip description
* @var string|null * @var string|null
* @Serializer\Type("string")
*/ */
private $description; private $description;
/** /**
* Line reference * Line reference
* @var ?Track * @var ?Track
* @Serializer\Type("App\Model\Track")
* @SerializeAs({"Default": "Identity"})
*/ */
private $track; private $track;
/** /**
* Stops in track * Stops in track
* @var Collection<ScheduleStop> * @Serializer\Type("Collection<App\Model\ScheduledStop>")
* @var Collection<ScheduledStop>
*/ */
private $schedule; private $schedule;

View File

@ -5,7 +5,7 @@ namespace App\Provider\Database;
use App\Entity\Entity; use App\Entity\Entity;
use App\Entity\ProviderEntity; use App\Entity\ProviderEntity;
use App\Model\Referable; use App\Model\Referable;
use App\Service\EntityConverter; use App\Service\Converter;
use App\Service\IdUtils; use App\Service\IdUtils;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Kadet\Functional as f; use Kadet\Functional as f;
@ -21,7 +21,7 @@ class DatabaseRepository
/** @var IdUtils */ /** @var IdUtils */
protected $id; protected $id;
/** @var EntityConverter */ /** @var Converter */
protected $converter; protected $converter;
/** /**
@ -29,7 +29,7 @@ class DatabaseRepository
* *
* @param EntityManagerInterface $em * @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->em = $em;
$this->id = $id; $this->id = $id;

View File

@ -0,0 +1,27 @@
<?php
namespace App\Provider\Database;
use App\Entity\TripEntity;
use App\Model\Trip;
use App\Provider\TripRepository;
class GenericTripRepository extends DatabaseRepository implements TripRepository
{
public function getById(string $id): Trip
{
$id = $this->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);
}
}

View File

@ -9,6 +9,7 @@ use App\Provider\MessageRepository;
use App\Provider\Provider; use App\Provider\Provider;
use App\Provider\StopRepository; use App\Provider\StopRepository;
use App\Provider\TrackRepository; use App\Provider\TrackRepository;
use App\Provider\TripRepository;
use Carbon\Carbon; use Carbon\Carbon;
class DummyProvider implements Provider class DummyProvider implements Provider
@ -76,4 +77,9 @@ class DummyProvider implements Provider
{ {
return null; return null;
} }
public function getTripRepository(): TripRepository
{
throw new NotSupportedException();
}
} }

View File

@ -11,6 +11,7 @@ interface Provider
public function getStopRepository(): StopRepository; public function getStopRepository(): StopRepository;
public function getMessageRepository(): MessageRepository; public function getMessageRepository(): MessageRepository;
public function getTrackRepository(): TrackRepository; public function getTrackRepository(): TrackRepository;
public function getTripRepository(): TripRepository;
public function getName(): string; public function getName(): string;
public function getShortName(): string; public function getShortName(): string;

View File

@ -0,0 +1,10 @@
<?php
namespace App\Provider;
use App\Model\Trip;
interface TripRepository
{
public function getById(string $id): Trip;
}

View File

@ -8,6 +8,7 @@ use App\Provider\Database\GenericLineRepository;
use App\Provider\Database\GenericScheduleRepository; use App\Provider\Database\GenericScheduleRepository;
use App\Provider\Database\GenericStopRepository; use App\Provider\Database\GenericStopRepository;
use App\Provider\Database\GenericTrackRepository; use App\Provider\Database\GenericTrackRepository;
use App\Provider\Database\GenericTripRepository;
use App\Provider\DepartureRepository; use App\Provider\DepartureRepository;
use App\Provider\LineRepository; use App\Provider\LineRepository;
use App\Provider\MessageRepository; use App\Provider\MessageRepository;
@ -15,6 +16,7 @@ use App\Provider\Provider;
use App\Provider\ScheduleRepository; use App\Provider\ScheduleRepository;
use App\Provider\StopRepository; use App\Provider\StopRepository;
use App\Provider\TrackRepository; use App\Provider\TrackRepository;
use App\Provider\TripRepository;
use App\Provider\ZtmGdansk\{ZtmGdanskDepartureRepository, ZtmGdanskMessageRepository}; use App\Provider\ZtmGdansk\{ZtmGdanskDepartureRepository, ZtmGdanskMessageRepository};
use App\Service\Proxy\ReferenceFactory; use App\Service\Proxy\ReferenceFactory;
use Carbon\Carbon; use Carbon\Carbon;
@ -30,6 +32,7 @@ class ZtmGdanskProvider implements Provider
/** @var ProviderEntity */ /** @var ProviderEntity */
private $entity; private $entity;
private $trips;
public function getName(): string public function getName(): string
{ {
@ -57,6 +60,7 @@ class ZtmGdanskProvider implements Provider
GenericStopRepository $stops, GenericStopRepository $stops,
GenericTrackRepository $tracks, GenericTrackRepository $tracks,
GenericScheduleRepository $schedule, GenericScheduleRepository $schedule,
GenericTripRepository $trips,
ZtmGdanskMessageRepository $messages, ZtmGdanskMessageRepository $messages,
ReferenceFactory $referenceFactory ReferenceFactory $referenceFactory
) { ) {
@ -66,6 +70,7 @@ class ZtmGdanskProvider implements Provider
$stops = $stops->withProvider($provider); $stops = $stops->withProvider($provider);
$tracks = $tracks->withProvider($provider); $tracks = $tracks->withProvider($provider);
$schedule = $schedule->withProvider($provider); $schedule = $schedule->withProvider($provider);
$trips = $trips->withProvider($provider);
$this->lines = $lines; $this->lines = $lines;
$this->departures = new ZtmGdanskDepartureRepository($lines, $schedule, $referenceFactory); $this->departures = new ZtmGdanskDepartureRepository($lines, $schedule, $referenceFactory);
@ -73,6 +78,7 @@ class ZtmGdanskProvider implements Provider
$this->messages = $messages; $this->messages = $messages;
$this->tracks = $tracks; $this->tracks = $tracks;
$this->entity = $provider; $this->entity = $provider;
$this->trips = $trips;
} }
public function getDepartureRepository(): DepartureRepository public function getDepartureRepository(): DepartureRepository
@ -100,6 +106,11 @@ class ZtmGdanskProvider implements Provider
return $this->tracks; return $this->tracks;
} }
public function getTripRepository(): TripRepository
{
return $this->trips;
}
public function getLastUpdate(): ?Carbon public function getLastUpdate(): ?Carbon
{ {
return $this->entity->getUpdateDate(); return $this->entity->getUpdateDate();

View File

@ -0,0 +1,43 @@
<?php
namespace App\Service;
use Hoa\Iterator\Recursive\Recursive;
use Symfony\Component\DependencyInjection\ServiceLocator;
use function Kadet\Functional\Predicates\equals;
use function Kadet\Functional\Predicates\method;
class AggregateConverter implements Converter
{
private $converters;
public function __construct(iterable $converters)
{
$this->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);
});
}
}

17
src/Service/Converter.php Normal file
View File

@ -0,0 +1,17 @@
<?php
namespace App\Service;
use App\Entity\Entity;
use App\Model\Line;
use App\Model\Operator;
use App\Model\ScheduledStop;
use App\Model\Stop;
use App\Model\Track;
use App\Model\Trip;
interface Converter
{
public function convert($entity);
public function supports($entity);
}

View File

@ -3,7 +3,7 @@
namespace App\Service; namespace App\Service;
use App\Entity\{Entity, LineEntity, OperatorEntity, StopEntity, TrackEntity, TripEntity, TripStopEntity}; use App\Entity\{Entity, LineEntity, OperatorEntity, StopEntity, TrackEntity, TripEntity, TripStopEntity};
use App\Model\{Line, Location, Operator, ScheduleStop, Stop, Track, Trip}; use App\Model\{Line, Location, Operator, ScheduledStop, Stop, Track, Trip};
use App\Service\Proxy\ReferenceFactory; use App\Service\Proxy\ReferenceFactory;
use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Proxy\Proxy; use Doctrine\ORM\Proxy\Proxy;
@ -11,8 +11,10 @@ use Kadet\Functional as f;
use Kadet\Functional\Transforms as t; use Kadet\Functional\Transforms as t;
use const Kadet\Functional\_; use const Kadet\Functional\_;
final class EntityConverter final class EntityConverter implements Converter, RecursiveConverter
{ {
use RecursiveConverterTrait;
private $id; private $id;
private $reference; private $reference;
@ -26,9 +28,9 @@ final class EntityConverter
* @param Entity $entity * @param Entity $entity
* @param array $cache * @param array $cache
* *
* @return Line|Track|Stop|Operator|Trip|ScheduleStop * @return Line|Track|Stop|Operator|Trip|ScheduledStop
*/ */
public function convert(Entity $entity, array $cache = []) 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]; return $cache[$key];
@ -40,7 +42,11 @@ final class EntityConverter
$result = $this->create($entity); $result = $this->create($entity);
$cache = $cache + [$key => $result]; $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) { switch (true) {
case $entity instanceof OperatorEntity: case $entity instanceof OperatorEntity:
@ -95,15 +101,6 @@ final class EntityConverter
'track' => $convert($entity->getTrack()), 'track' => $convert($entity->getTrack()),
]); ]);
break; break;
case $entity instanceof TripStopEntity:
$result->fill([
'arrival' => $entity->getArrival(),
'departure' => $entity->getDeparture(),
'stop' => $convert($entity->getStop()),
'order' => $convert($entity->getOrder()),
]);
break;
} }
return $result; return $result;
@ -149,9 +146,6 @@ final class EntityConverter
case $entity instanceof TripEntity: case $entity instanceof TripEntity:
return Trip::class; return Trip::class;
case $entity instanceof TripStopEntity:
return ScheduleStop::class;
default: default:
return false; return false;
} }
@ -172,4 +166,9 @@ final class EntityConverter
return $this->reference->get($class, ['id' => $id]); return $this->reference->get($class, ['id' => $id]);
} }
public function supports($entity)
{
return $entity instanceof Entity;
}
} }

View File

@ -0,0 +1,9 @@
<?php
namespace App\Service;
interface RecursiveConverter extends Converter
{
public function setParent(?Converter $converter);
public function getParent(): ?Converter;
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Service;
trait RecursiveConverterTrait
{
/**
* @var Converter
*/
private $parent;
public function setParent(?Converter $converter)
{
$this->parent = $converter;
}
public function getParent(): ?Converter
{
return $this->parent;
}
}

View File

@ -10,6 +10,7 @@ use App\Provider\LineRepository;
use App\Provider\MessageRepository; use App\Provider\MessageRepository;
use App\Provider\StopRepository; use App\Provider\StopRepository;
use App\Provider\TrackRepository; use App\Provider\TrackRepository;
use App\Provider\TripRepository;
use const Kadet\Functional\_; use const Kadet\Functional\_;
use function Kadet\Functional\any; use function Kadet\Functional\any;
use function Kadet\Functional\curry; use function Kadet\Functional\curry;
@ -64,6 +65,10 @@ class RepositoryParameterConverter implements ParamConverterInterface
$request->attributes->set($configuration->getName(), $provider->getTrackRepository()); $request->attributes->set($configuration->getName(), $provider->getTrackRepository());
break; break;
case is_a($class, TripRepository::class, true):
$request->attributes->set($configuration->getName(), $provider->getTripRepository());
break;
default: default:
return false; return false;
} }
@ -82,6 +87,7 @@ class RepositoryParameterConverter implements ParamConverterInterface
DepartureRepository::class, DepartureRepository::class,
MessageRepository::class, MessageRepository::class,
TrackRepository::class, TrackRepository::class,
TripRepository::class,
])); ]));
return $supports($configuration->getClass()); return $supports($configuration->getClass());

View File

@ -0,0 +1,28 @@
<?php
namespace App\Service;
use App\Entity\TripStopEntity;
use App\Model\ScheduledStop;
class ScheduledStopConverter implements Converter, RecursiveConverter
{
use RecursiveConverterTrait;
public function convert($entity)
{
/** @var ScheduledStop $entity */
return ScheduledStop::createFromArray([
'arrival' => $entity->getArrival(),
'departure' => $entity->getDeparture(),
'stop' => $this->parent->convert($entity->getStop()),
'order' => $entity->getOrder(),
]);
}
public function supports($entity)
{
return $entity instanceof TripStopEntity;
}
}

View File

@ -21,7 +21,7 @@ final class SerializerContextFactory
$this->reader = $reader; $this->reader = $reader;
} }
public function create($subject, array $groups) public function create($subject, array $groups = ['Default'])
{ {
return SerializationContext::create()->setGroups($this->groups($subject, $groups)); return SerializationContext::create()->setGroups($this->groups($subject, $groups));
} }