Track stops fluent quering

This commit is contained in:
Kacper Donat 2020-02-20 17:33:31 +01:00
parent 9f3f6bf22b
commit a9a0f2f413
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\Model\Stop;
use App\Model\Track;
use App\Model\TrackStop;
use App\Model\StopGroup;
use App\Modifier\IdFilter;
use App\Modifier\FieldFilter;
@ -12,7 +12,6 @@ use App\Modifier\IncludeDestinations;
use App\Modifier\RelatedFilter;
use App\Provider\StopRepository;
use App\Provider\TrackRepository;
use App\Service\Proxy\ReferenceFactory;
use Nelmio\ApiDocBundle\Annotation\Model;
use Swagger\Annotations as SWG;
use Symfony\Component\HttpFoundation\Request;
@ -105,21 +104,12 @@ class StopsController extends Controller
* @SWG\Response(
* response=200,
* description="Returns specific stop referenced via identificator.",
* @SWG\Schema(type="object", properties={
* @SWG\Property(property="track", type="object", ref=@Model(type=Track::class)),
* @SWG\Property(property="order", type="integer", minimum="0")
* })
* @SWG\Schema(ref=@Model(type=TrackStop::class))
* )
*
* @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->getByStop($stop)->map(function ($tuple) {
return array_combine(['track', 'order'], $tuple);
}));
return $this->json($tracks->stops(new RelatedFilter(Stop::reference($id))));
}
public static function group(Collection $stops)

View File

@ -19,6 +19,7 @@ use function App\Functions\encapsulate;
/**
* @Route("/tracks")
* @SWG\Tag(name="Tracks")
*/
class TracksController extends Controller
{
@ -27,7 +28,6 @@ class TracksController extends Controller
* response=200,
* description="Returns all tracks for specific provider, e.g. ZTM Gdańsk.",
* )
* @SWG\Tag(name="Tracks")
* @Route("/", methods={"GET"})
*/
public function index(Request $request, TrackRepository $repository)
@ -37,6 +37,17 @@ class TracksController extends Controller
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)
{
if ($request->query->has('stop')) {
@ -59,4 +70,27 @@ class TracksController extends Controller
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
* @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"})
*/
private $stopsInTrack;
/**
* 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;
@ -114,7 +116,7 @@ class TrackEntity implements Entity, Fillable
$this->final = $this->stopsInTrack->last();
}
public function getFinal(): StopInTrack
public function getFinal(): TrackStopEntity
{
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"})
* })
*/
class StopInTrack implements Fillable, Referable
class TrackStopEntity implements Fillable, Referable
{
use FillTrait, ReferableEntityTrait;

View File

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

View File

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

View File

@ -138,4 +138,4 @@ class Line implements Fillable, Referable
{
$this->operator = $operator;
}
}
}

View File

@ -4,22 +4,8 @@ namespace App\Model;
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
* @var Carbon
@ -32,26 +18,6 @@ class ScheduledStop implements Fillable
*/
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
{
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
{
const DEFAULT_LIMIT = 100;
/** @var EntityManagerInterface */
protected $em;
@ -115,6 +117,8 @@ abstract class DatabaseRepository implements ServiceSubscriberInterface, Reposit
protected function allFromQueryBuilder(QueryBuilder $builder, iterable $modifiers, array $meta = [])
{
$builder->setMaxResults(self::DEFAULT_LIMIT);
$reducers = $this->processQueryBuilder($builder, $modifiers, $meta);
return $reducers->reduce(function ($result, $reducer) {

View File

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

View File

@ -2,33 +2,28 @@
namespace App\Provider\Database;
use App\Entity\StopEntity;
use App\Entity\StopInTrack;
use App\Entity\TrackStopEntity;
use App\Entity\TrackEntity;
use App\Model\TrackStop;
use App\Modifier\Modifier;
use App\Model\Track;
use App\Provider\TrackRepository;
use Tightenco\Collect\Support\Collection;
use Kadet\Functional as f;
use function App\Functions\encapsulate;
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()
->from(StopInTrack::class, 'st')
->join('st.track', 't')
->where('st.stop in (:stop)')
->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() ];
});
return $this->allFromQueryBuilder($builder, $modifiers, [
'alias' => 'track_stop',
'entity' => TrackStopEntity::class,
'type' => TrackStop::class,
]);
}
public function all(Modifier ...$modifiers): Collection

View File

@ -3,9 +3,10 @@
namespace App\Provider;
use App\Model\Track;
use App\Modifier\Modifier;
use Tightenco\Collect\Support\Collection;
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\ProviderEntity;
use App\Entity\StopEntity;
use App\Entity\StopInTrack;
use App\Entity\TrackStopEntity;
use App\Entity\TrackEntity;
use App\Entity\TripEntity;
use App\Entity\TripStopEntity;
@ -216,7 +216,7 @@ class ZtmGdanskDataUpdateSubscriber implements EventSubscriberInterface
return !in_array($stop['stopId'], $this->stopBlacklist);
})
->map(function ($stop) use ($entity, $provider) {
return StopInTrack::createFromArray([
return TrackStopEntity::createFromArray([
'stop' => $this->em->getReference(
StopEntity::class,
$this->ids->generate($provider, $stop['stopId'])

View File

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

View File

@ -2,6 +2,7 @@
namespace App\Service;
use App\Entity\TrackStopEntity;
use App\Entity\TripStopEntity;
use App\Model\ScheduledStop;
@ -11,18 +12,28 @@ class ScheduledStopConverter implements Converter, RecursiveConverter
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(),
]);
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([
'arrival' => $entity->getArrival(),
'departure' => $entity->getDeparture(),
'stop' => $this->parent->convert($entity->getStop()),
'order' => $entity->getOrder(),
]);
}
}
public function supports($entity)
{
return $entity instanceof TripStopEntity;
return $entity instanceof TripStopEntity
|| $entity instanceof TrackStopEntity;
}
}