#35 - Track stops fluent quering

This commit is contained in:
Kacper Donat 2020-02-20 17:33:31 +01:00
parent f4e3ee6f55
commit 42ee6e091d
16 changed files with 169 additions and 100 deletions

View File

@ -4,7 +4,7 @@ namespace App\Controller\Api\v1;
use App\Controller\Controller; use App\Controller\Controller;
use App\Model\Stop; use App\Model\Stop;
use App\Model\Track; use App\Model\TrackStop;
use App\Model\StopGroup; use App\Model\StopGroup;
use App\Modifier\IdFilter; use App\Modifier\IdFilter;
use App\Modifier\FieldFilter; use App\Modifier\FieldFilter;
@ -12,7 +12,6 @@ use App\Modifier\IncludeDestinations;
use App\Modifier\RelatedFilter; use App\Modifier\RelatedFilter;
use App\Provider\StopRepository; use App\Provider\StopRepository;
use App\Provider\TrackRepository; use App\Provider\TrackRepository;
use App\Service\Proxy\ReferenceFactory;
use Nelmio\ApiDocBundle\Annotation\Model; use Nelmio\ApiDocBundle\Annotation\Model;
use Swagger\Annotations as SWG; use Swagger\Annotations as SWG;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -105,21 +104,12 @@ class StopsController extends Controller
* @SWG\Response( * @SWG\Response(
* response=200, * response=200,
* description="Returns specific stop referenced via identificator.", * description="Returns specific stop referenced via identificator.",
* @SWG\Schema(type="object", properties={ * @SWG\Schema(ref=@Model(type=TrackStop::class))
* @SWG\Property(property="track", type="object", ref=@Model(type=Track::class)),
* @SWG\Property(property="order", type="integer", minimum="0")
* })
* ) * )
*
* @SWG\Tag(name="Tracks")
*/ */
public function tracks(ReferenceFactory $reference, TrackRepository $tracks, $id) public function tracks(TrackRepository $tracks, $id)
{ {
$stop = $reference->get(Stop::class, $id); return $this->json($tracks->stops(new RelatedFilter(Stop::reference($id))));
return $this->json($tracks->getByStop($stop)->map(function ($tuple) {
return array_combine(['track', 'order'], $tuple);
}));
} }
public static function group(Collection $stops) public static function group(Collection $stops)

View File

@ -19,6 +19,7 @@ use function App\Functions\encapsulate;
/** /**
* @Route("/tracks") * @Route("/tracks")
* @SWG\Tag(name="Tracks")
*/ */
class TracksController extends Controller class TracksController extends Controller
{ {
@ -27,7 +28,6 @@ class TracksController extends Controller
* response=200, * response=200,
* description="Returns all tracks for specific provider, e.g. ZTM Gdańsk.", * description="Returns all tracks for specific provider, e.g. ZTM Gdańsk.",
* ) * )
* @SWG\Tag(name="Tracks")
* @Route("/", methods={"GET"}) * @Route("/", methods={"GET"})
*/ */
public function index(Request $request, TrackRepository $repository) public function index(Request $request, TrackRepository $repository)
@ -37,6 +37,17 @@ class TracksController extends Controller
return $this->json($repository->all(...$modifiers)); return $this->json($repository->all(...$modifiers));
} }
/**
* @Route("/stops", methods={"GET"})
* @Route("/{track}/stops", methods={"GET"})
*/
public function stops(Request $request, TrackRepository $repository)
{
$modifiers = $this->getStopsModifiersFromRequest($request);
return $this->json($repository->stops(...$modifiers));
}
private function getModifiersFromRequest(Request $request) private function getModifiersFromRequest(Request $request)
{ {
if ($request->query->has('stop')) { if ($request->query->has('stop')) {
@ -59,4 +70,27 @@ class TracksController extends Controller
yield new IdFilter($id); yield new IdFilter($id);
} }
} }
private function getStopsModifiersFromRequest(Request $request)
{
if ($request->query->has('stop')) {
$stop = $request->query->get('stop');
$stop = Stop::reference($stop);
yield new RelatedFilter($stop);
}
if ($request->query->has('track') || $request->attributes->has('track')) {
$track = $request->get('track');
$track = Track::reference($track);
yield new RelatedFilter($track);
}
if ($request->query->has('id')) {
$id = encapsulate($request->query->get('id'));
yield new IdFilter($id);
}
}
} }

View File

@ -45,16 +45,18 @@ class TrackEntity implements Entity, Fillable
/** /**
* Stops in track * Stops in track
* @var StopInTrack[]|Collection *
* @ORM\OneToMany(targetEntity=StopInTrack::class, fetch="LAZY", mappedBy="track", cascade={"persist"}) * @var TrackStopEntity[]|Collection
* @ORM\OneToMany(targetEntity=TrackStopEntity::class, fetch="LAZY", mappedBy="track", cascade={"persist"})
* @ORM\OrderBy({"order": "ASC"}) * @ORM\OrderBy({"order": "ASC"})
*/ */
private $stopsInTrack; private $stopsInTrack;
/** /**
* Final stop in this track. * Final stop in this track.
* @var StopInTrack *
* @ORM\OneToOne(targetEntity=StopInTrack::class, fetch="LAZY") * @var TrackStopEntity
* @ORM\OneToOne(targetEntity=TrackStopEntity::class, fetch="LAZY")
*/ */
private $final; private $final;
@ -114,7 +116,7 @@ class TrackEntity implements Entity, Fillable
$this->final = $this->stopsInTrack->last(); $this->final = $this->stopsInTrack->last();
} }
public function getFinal(): StopInTrack public function getFinal(): TrackStopEntity
{ {
return $this->final; return $this->final;
} }

View File

@ -14,7 +14,7 @@ use Doctrine\ORM\Mapping as ORM;
* @ORM\UniqueConstraint(name="stop_in_track_idx", columns={"stop_id", "track_id", "sequence"}) * @ORM\UniqueConstraint(name="stop_in_track_idx", columns={"stop_id", "track_id", "sequence"})
* }) * })
*/ */
class StopInTrack implements Fillable, Referable class TrackStopEntity implements Fillable, Referable
{ {
use FillTrait, ReferableEntityTrait; use FillTrait, ReferableEntityTrait;

View File

@ -8,9 +8,10 @@ use App\Handler\ModifierHandler;
use App\Model\Line; use App\Model\Line;
use App\Model\Stop; use App\Model\Stop;
use App\Model\Track; use App\Model\Track;
use App\Model\TrackStop;
use App\Modifier\RelatedFilter; use App\Modifier\RelatedFilter;
use App\Service\IdUtils; use App\Service\IdUtils;
use App\Service\ReferenceFactory; use App\Service\EntityReferenceFactory;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface;
@ -22,6 +23,10 @@ class RelatedFilterDatabaseGenericHandler implements ModifierHandler, ServiceSub
Line::class => 'line', Line::class => 'line',
Stop::class => TrackByStopDatabaseHandler::class, Stop::class => TrackByStopDatabaseHandler::class,
], ],
TrackStop::class => [
Stop::class => 'stop',
Track::class => 'track',
],
]; ];
private $em; private $em;
@ -33,7 +38,7 @@ class RelatedFilterDatabaseGenericHandler implements ModifierHandler, ServiceSub
ContainerInterface $inner, ContainerInterface $inner,
EntityManagerInterface $em, EntityManagerInterface $em,
IdUtils $idUtils, IdUtils $idUtils,
ReferenceFactory $references EntityReferenceFactory $references
) { ) {
$this->inner = $inner; $this->inner = $inner;
$this->em = $em; $this->em = $em;
@ -71,6 +76,7 @@ class RelatedFilterDatabaseGenericHandler implements ModifierHandler, ServiceSub
/** @var ModifierHandler $inner */ /** @var ModifierHandler $inner */
$inner = $this->inner->get($relationship); $inner = $this->inner->get($relationship);
$inner->process($event); $inner->process($event);
return; return;
} }
@ -80,8 +86,7 @@ class RelatedFilterDatabaseGenericHandler implements ModifierHandler, ServiceSub
$builder $builder
->join(sprintf('%s.%s', $alias, $relationship), $relationship) ->join(sprintf('%s.%s', $alias, $relationship), $relationship)
->andWhere(sprintf("%s = %s", $relationship, $parameter)) ->andWhere(sprintf("%s = %s", $relationship, $parameter))
->setParameter($parameter, $reference) ->setParameter($parameter, $reference);
;
} }
/** /**

View File

@ -2,18 +2,18 @@
namespace App\Handler\Database; namespace App\Handler\Database;
use App\Entity\StopInTrack; use App\Entity\TrackStopEntity;
use App\Event\HandleDatabaseModifierEvent; use App\Event\HandleDatabaseModifierEvent;
use App\Event\HandleModifierEvent; use App\Event\HandleModifierEvent;
use App\Handler\ModifierHandler; use App\Handler\ModifierHandler;
use App\Modifier\RelatedFilter; use App\Modifier\RelatedFilter;
use App\Service\ReferenceFactory; use App\Service\EntityReferenceFactory;
class TrackByStopDatabaseHandler implements ModifierHandler class TrackByStopDatabaseHandler implements ModifierHandler
{ {
private $references; private $references;
public function __construct(ReferenceFactory $references) public function __construct(EntityReferenceFactory $references)
{ {
$this->references = $references; $this->references = $references;
} }

View File

@ -4,22 +4,8 @@ namespace App\Model;
use Carbon\Carbon; use Carbon\Carbon;
class ScheduledStop implements Fillable class ScheduledStop extends TrackStop
{ {
use FillTrait;
/**
* Stop (as a place) related to that scheduled bus stop
* @var Stop
*/
private $stop;
/**
* Order in trip
* @var int
*/
private $order;
/** /**
* Arrival time * Arrival time
* @var Carbon * @var Carbon
@ -32,26 +18,6 @@ class ScheduledStop implements Fillable
*/ */
private $departure; private $departure;
public function getStop()
{
return $this->stop;
}
public function setStop($stop): void
{
$this->stop = $stop;
}
public function getOrder(): int
{
return $this->order;
}
public function setOrder(int $order): void
{
$this->order = $order;
}
public function getArrival(): Carbon public function getArrival(): Carbon
{ {
return $this->arrival; return $this->arrival;

58
src/Model/TrackStop.php Normal file
View File

@ -0,0 +1,58 @@
<?php
namespace App\Model;
use JMS\Serializer\Annotation as Serializer;
class TrackStop implements Fillable
{
use FillTrait;
/**
* Order in trip
* @var int
*/
private $order;
/**
* Stop (as a place) related to that scheduled bus stop
* @var Stop
*/
private $stop;
/**
* Track that this stop is part of.
* @var Track|null
*/
private $track;
public function getStop()
{
return $this->stop;
}
public function setStop($stop): void
{
$this->stop = $stop;
}
public function getOrder(): int
{
return $this->order;
}
public function setOrder(int $order): void
{
$this->order = $order;
}
public function getTrack(): ?Track
{
return $this->track;
}
public function setTrack(?Track $track): void
{
$this->track = $track;
}
}

View File

@ -28,6 +28,8 @@ use Symfony\Contracts\Service\ServiceSubscriberInterface;
abstract class DatabaseRepository implements ServiceSubscriberInterface, Repository abstract class DatabaseRepository implements ServiceSubscriberInterface, Repository
{ {
const DEFAULT_LIMIT = 100;
/** @var EntityManagerInterface */ /** @var EntityManagerInterface */
protected $em; protected $em;
@ -115,6 +117,8 @@ abstract class DatabaseRepository implements ServiceSubscriberInterface, Reposit
protected function allFromQueryBuilder(QueryBuilder $builder, iterable $modifiers, array $meta = []) protected function allFromQueryBuilder(QueryBuilder $builder, iterable $modifiers, array $meta = [])
{ {
$builder->setMaxResults(self::DEFAULT_LIMIT);
$reducers = $this->processQueryBuilder($builder, $modifiers, $meta); $reducers = $this->processQueryBuilder($builder, $modifiers, $meta);
return $reducers->reduce(function ($result, $reducer) { return $reducers->reduce(function ($result, $reducer) {

View File

@ -3,7 +3,7 @@
namespace App\Provider\Database; namespace App\Provider\Database;
use App\Entity\StopEntity; use App\Entity\StopEntity;
use App\Entity\StopInTrack; use App\Entity\TrackStopEntity;
use App\Entity\TrackEntity; use App\Entity\TrackEntity;
use App\Entity\TripEntity; use App\Entity\TripEntity;
use App\Entity\TripStopEntity; use App\Entity\TripStopEntity;

View File

@ -2,33 +2,28 @@
namespace App\Provider\Database; namespace App\Provider\Database;
use App\Entity\StopEntity; use App\Entity\TrackStopEntity;
use App\Entity\StopInTrack;
use App\Entity\TrackEntity; use App\Entity\TrackEntity;
use App\Model\TrackStop;
use App\Modifier\Modifier; use App\Modifier\Modifier;
use App\Model\Track; use App\Model\Track;
use App\Provider\TrackRepository; use App\Provider\TrackRepository;
use Tightenco\Collect\Support\Collection; use Tightenco\Collect\Support\Collection;
use Kadet\Functional as f;
use function App\Functions\encapsulate;
class GenericTrackRepository extends DatabaseRepository implements TrackRepository class GenericTrackRepository extends DatabaseRepository implements TrackRepository
{ {
public function getByStop($stop): Collection public function stops(Modifier ...$modifiers): Collection
{ {
$reference = f\apply(f\ref([$this, 'reference']), StopEntity::class); $builder = $this->em
->createQueryBuilder()
->from(TrackStopEntity::class, 'track_stop')
->select(['track_stop']);
$tracks = $this->em->createQueryBuilder() return $this->allFromQueryBuilder($builder, $modifiers, [
->from(StopInTrack::class, 'st') 'alias' => 'track_stop',
->join('st.track', 't') 'entity' => TrackStopEntity::class,
->where('st.stop in (:stop)') 'type' => TrackStop::class,
->select(['st', 't']) ]);
->getQuery()
->execute(['stop' => array_map($reference, encapsulate($stop))]);
return collect($tracks)->map(function (StopInTrack $entity) {
return [ $this->convert($entity->getTrack()), $entity->getOrder() ];
});
} }
public function all(Modifier ...$modifiers): Collection public function all(Modifier ...$modifiers): Collection

View File

@ -3,9 +3,10 @@
namespace App\Provider; namespace App\Provider;
use App\Model\Track; use App\Model\Track;
use App\Modifier\Modifier;
use Tightenco\Collect\Support\Collection; use Tightenco\Collect\Support\Collection;
interface TrackRepository extends FluentRepository interface TrackRepository extends FluentRepository
{ {
public function getByStop($stop): Collection; public function stops(Modifier ...$modifiers): Collection;
} }

View File

@ -6,7 +6,7 @@ use App\Entity\LineEntity;
use App\Entity\OperatorEntity; use App\Entity\OperatorEntity;
use App\Entity\ProviderEntity; use App\Entity\ProviderEntity;
use App\Entity\StopEntity; use App\Entity\StopEntity;
use App\Entity\StopInTrack; use App\Entity\TrackStopEntity;
use App\Entity\TrackEntity; use App\Entity\TrackEntity;
use App\Entity\TripEntity; use App\Entity\TripEntity;
use App\Entity\TripStopEntity; use App\Entity\TripStopEntity;
@ -216,7 +216,7 @@ class ZtmGdanskDataUpdateSubscriber implements EventSubscriberInterface
return !in_array($stop['stopId'], $this->stopBlacklist); return !in_array($stop['stopId'], $this->stopBlacklist);
}) })
->map(function ($stop) use ($entity, $provider) { ->map(function ($stop) use ($entity, $provider) {
return StopInTrack::createFromArray([ return TrackStopEntity::createFromArray([
'stop' => $this->em->getReference( 'stop' => $this->em->getReference(
StopEntity::class, StopEntity::class,
$this->ids->generate($provider, $stop['stopId']) $this->ids->generate($provider, $stop['stopId'])

View File

@ -5,16 +5,19 @@ namespace App\Service;
use App\Entity\LineEntity; use App\Entity\LineEntity;
use App\Entity\ProviderEntity; use App\Entity\ProviderEntity;
use App\Entity\StopEntity; use App\Entity\StopEntity;
use App\Entity\TrackEntity;
use App\Model\Line; use App\Model\Line;
use App\Model\Referable; use App\Model\Referable;
use App\Model\Stop; use App\Model\Stop;
use App\Model\Track;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
final class ReferenceFactory final class EntityReferenceFactory
{ {
protected $mapping = [ protected $mapping = [
Line::class => LineEntity::class, Line::class => LineEntity::class,
Stop::class => StopEntity::class, Stop::class => StopEntity::class,
Track::class => TrackEntity::class,
]; ];
private $em; private $em;

View File

@ -2,6 +2,7 @@
namespace App\Service; namespace App\Service;
use App\Entity\TrackStopEntity;
use App\Entity\TripStopEntity; use App\Entity\TripStopEntity;
use App\Model\ScheduledStop; use App\Model\ScheduledStop;
@ -11,8 +12,16 @@ class ScheduledStopConverter implements Converter, RecursiveConverter
public function convert($entity) public function convert($entity)
{ {
/** @var ScheduledStop $entity */
if ($entity instanceof TrackStopEntity) {
return ScheduledStop::createFromArray([
'stop' => $this->parent->convert($entity->getStop()),
'track' => $this->parent->convert($entity->getTrack()),
'order' => $entity->getOrder(),
]);
}
if ($entity instanceof TripStopEntity) {
return ScheduledStop::createFromArray([ return ScheduledStop::createFromArray([
'arrival' => $entity->getArrival(), 'arrival' => $entity->getArrival(),
'departure' => $entity->getDeparture(), 'departure' => $entity->getDeparture(),
@ -20,9 +29,11 @@ class ScheduledStopConverter implements Converter, RecursiveConverter
'order' => $entity->getOrder(), 'order' => $entity->getOrder(),
]); ]);
} }
}
public function supports($entity) public function supports($entity)
{ {
return $entity instanceof TripStopEntity; return $entity instanceof TripStopEntity
|| $entity instanceof TrackStopEntity;
} }
} }