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'
# 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:

View File

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

View File

@ -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;

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;
class ScheduleStop implements Fillable
class ScheduledStop implements Fillable
{
use FillTrait;
@ -71,4 +71,4 @@ class ScheduleStop implements Fillable
{
$this->departure = $departure;
}
}
}

View File

@ -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<ScheduleStop>
* @Serializer\Type("Collection<App\Model\ScheduledStop>")
* @var Collection<ScheduledStop>
*/
private $schedule;
@ -80,4 +87,4 @@ class Trip implements Referable, Fillable
{
return $this->schedule = collect($schedule);
}
}
}

View File

@ -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);
}
}
}

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\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;
}
}
public function getTripRepository(): TripRepository
{
throw new NotSupportedException();
}
}

View File

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

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\GenericStopRepository;
use App\Provider\Database\GenericTrackRepository;
use App\Provider\Database\GenericTripRepository;
use App\Provider\DepartureRepository;
use App\Provider\LineRepository;
use App\Provider\MessageRepository;
@ -15,6 +16,7 @@ use App\Provider\Provider;
use App\Provider\ScheduleRepository;
use App\Provider\StopRepository;
use App\Provider\TrackRepository;
use App\Provider\TripRepository;
use App\Provider\ZtmGdansk\{ZtmGdanskDepartureRepository, ZtmGdanskMessageRepository};
use App\Service\Proxy\ReferenceFactory;
use Carbon\Carbon;
@ -30,6 +32,7 @@ class ZtmGdanskProvider implements Provider
/** @var ProviderEntity */
private $entity;
private $trips;
public function getName(): string
{
@ -57,6 +60,7 @@ class ZtmGdanskProvider implements Provider
GenericStopRepository $stops,
GenericTrackRepository $tracks,
GenericScheduleRepository $schedule,
GenericTripRepository $trips,
ZtmGdanskMessageRepository $messages,
ReferenceFactory $referenceFactory
) {
@ -66,6 +70,7 @@ class ZtmGdanskProvider implements Provider
$stops = $stops->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();

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;
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 Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Proxy\Proxy;
@ -11,8 +11,10 @@ use Kadet\Functional as f;
use Kadet\Functional\Transforms as t;
use const Kadet\Functional\_;
final class EntityConverter
final class EntityConverter implements Converter, RecursiveConverter
{
use RecursiveConverterTrait;
private $id;
private $reference;
@ -26,9 +28,9 @@ final class EntityConverter
* @param Entity $entity
* @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)) {
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;
}
}

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\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());
}
}
}

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;
}
public function create($subject, array $groups)
public function create($subject, array $groups = ['Default'])
{
return SerializationContext::create()->setGroups($this->groups($subject, $groups));
}