35_new_repository_pattern_with_filters_and_modifiers #1
@ -23,7 +23,7 @@ services:
|
||||
# this creates a service per class whose id is the fully-qualified class name
|
||||
App\:
|
||||
resource: '../src/*'
|
||||
exclude: '../src/{DependencyInjection,Entity,Model,Migrations,Tests,Functions,Kernel.php}'
|
||||
exclude: '../src/{DependencyInjection,Exception,Modifiers,Entity,Model,Migrations,Tests,Functions,Kernel.php}'
|
||||
|
||||
# controllers are imported separately to make sure services can be injected
|
||||
# as action arguments even if you don't extend any base controller class
|
||||
|
@ -80,7 +80,7 @@ export class FinderComponent extends Vue {
|
||||
|
||||
this.state = 'fetching';
|
||||
|
||||
const response = await fetch(urls.prepare(urls.stops.grouped, { name: this.filter }));
|
||||
const response = await fetch(urls.prepare(urls.stops.grouped, { name: this.filter, 'include-destinations': true }));
|
||||
|
||||
if (response.ok) {
|
||||
this.found = (await response.json()).reduce((accumulator, { name, stops }) => Object.assign(accumulator, { [name]: stops }), {});
|
||||
|
@ -8,7 +8,11 @@ export function query(params: UrlParams = { }) {
|
||||
function *simplify(name: string, param: any): IterableIterator<ParamValuePair> {
|
||||
if (typeof param === 'string') {
|
||||
yield [ name, param ];
|
||||
} else if (typeof param === 'number') {
|
||||
} else if (typeof param === 'boolean') {
|
||||
if (param) {
|
||||
yield [ name, '1' ];
|
||||
}
|
||||
} else if (typeof param === 'number') {
|
||||
yield [ name, param.toString() ];
|
||||
} else if (param instanceof Array) {
|
||||
for (let entry of param) {
|
||||
|
@ -5,6 +5,7 @@ namespace App\Controller\Api\v1;
|
||||
|
||||
use App\Controller\Controller;
|
||||
use App\Model\Departure;
|
||||
use App\Modifier\IdFilter;
|
||||
use App\Provider\DepartureRepository;
|
||||
use App\Provider\StopRepository;
|
||||
use App\Service\SerializerContextFactory;
|
||||
@ -34,7 +35,7 @@ class DeparturesController extends Controller
|
||||
*/
|
||||
public function stop(DepartureRepository $departures, StopRepository $stops, $stop)
|
||||
{
|
||||
$stop = $stops->getById($stop);
|
||||
$stop = $stops->first(new IdFilter($stop));
|
||||
|
||||
return $this->json($departures->getForStop($stop));
|
||||
}
|
||||
@ -65,7 +66,7 @@ class DeparturesController extends Controller
|
||||
public function stops(DepartureRepository $departures, StopRepository $stops, Request $request)
|
||||
{
|
||||
$stops = $stops
|
||||
->getManyById($request->query->get('stop'))
|
||||
->all(new IdFilter($request->query->get('stop')))
|
||||
->flatMap(ref([ $departures, 'getForStop' ]))
|
||||
->sortBy(property('departure'));
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Controller\Api\v1;
|
||||
|
||||
use App\Controller\Controller;
|
||||
use App\Model\Stop;
|
||||
use App\Model\Track;
|
||||
use App\Model\StopGroup;
|
||||
use App\Modifier\IdFilter;
|
||||
use App\Modifier\FieldFilter;
|
||||
use App\Modifier\IncludeDestinations;
|
||||
use App\Modifier\RelatedFilter;
|
||||
use App\Provider\StopRepository;
|
||||
use App\Provider\TrackRepository;
|
||||
use App\Service\Proxy\ReferenceFactory;
|
||||
@ -38,7 +41,8 @@ class StopsController extends Controller
|
||||
* name="id",
|
||||
* in="query",
|
||||
* type="array",
|
||||
* description="Stop identificators to retrieve at once. Can be used to bulk load data. If not specified will return all data.",
|
||||
* description="Stop identificators to retrieve at once. Can be used to bulk load data. If not specified will
|
||||
* return all data.",
|
||||
* @SWG\Items(type="string")
|
||||
* )
|
||||
*
|
||||
@ -46,16 +50,9 @@ class StopsController extends Controller
|
||||
*/
|
||||
public function index(Request $request, StopRepository $stops)
|
||||
{
|
||||
switch (true) {
|
||||
case $request->query->has('id'):
|
||||
$result = $stops->getManyById($request->query->get('id'));
|
||||
break;
|
||||
$modifiers = $this->getModifiersFromRequest($request);
|
||||
|
||||
default:
|
||||
$result = $stops->getAll();
|
||||
}
|
||||
|
||||
return $this->json($result->all());
|
||||
return $this->json($stops->all(...$modifiers)->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,16 +73,9 @@ class StopsController extends Controller
|
||||
*/
|
||||
public function groups(Request $request, StopRepository $stops)
|
||||
{
|
||||
switch (true) {
|
||||
case $request->query->has('name'):
|
||||
$result = $stops->findByName($request->query->get('name'));
|
||||
break;
|
||||
$modifiers = $this->getModifiersFromRequest($request);
|
||||
|
||||
default:
|
||||
$result = $stops->getAll();
|
||||
}
|
||||
|
||||
return $this->json(static::group($result)->all());
|
||||
return $this->json(static::group($stops->all(...$modifiers))->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,7 +96,7 @@ class StopsController extends Controller
|
||||
*/
|
||||
public function one(Request $request, StopRepository $stops, $id)
|
||||
{
|
||||
return $this->json($stops->getById($id));
|
||||
return $this->json($stops->first(new IdFilter($id), new IncludeDestinations()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,4 +135,19 @@ class StopsController extends Controller
|
||||
return $group;
|
||||
})->values();
|
||||
}
|
||||
|
||||
private function getModifiersFromRequest(Request $request)
|
||||
{
|
||||
if ($request->query->has('name')) {
|
||||
yield FieldFilter::contains('name', $request->query->get('name'));
|
||||
}
|
||||
|
||||
if ($request->query->has('id')) {
|
||||
yield new IdFilter($request->query->get('id'));
|
||||
}
|
||||
|
||||
if ($request->query->has('include-destinations')) {
|
||||
yield new IncludeDestinations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,13 @@
|
||||
namespace App\Controller\Api\v1;
|
||||
|
||||
use App\Controller\Controller;
|
||||
use App\Model\Line;
|
||||
use App\Model\Stop;
|
||||
use App\Model\Track;
|
||||
use App\Modifier\IdFilter;
|
||||
use App\Modifier\RelatedFilter;
|
||||
use App\Provider\TrackRepository;
|
||||
use App\Service\IterableUtils;
|
||||
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||
use Swagger\Annotations as SWG;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@ -28,43 +32,31 @@ class TracksController extends Controller
|
||||
*/
|
||||
public function index(Request $request, TrackRepository $repository)
|
||||
{
|
||||
switch (true) {
|
||||
case $request->query->has('stop'):
|
||||
return $this->byStop($request, $repository);
|
||||
case $request->query->has('line'):
|
||||
return $this->byLine($request, $repository);
|
||||
case $request->query->has('id'):
|
||||
return $this->byId($request, $repository);
|
||||
default:
|
||||
throw new BadRequestHttpException(
|
||||
sprintf(
|
||||
'At least one parameter of %s must be set.',
|
||||
implode(', ', ['stop', 'line', 'id'])
|
||||
)
|
||||
);
|
||||
$modifiers = $this->getModifiersFromRequest($request);
|
||||
|
||||
return $this->json($repository->all(...$modifiers));
|
||||
}
|
||||
|
||||
private function getModifiersFromRequest(Request $request)
|
||||
{
|
||||
if ($request->query->has('stop')) {
|
||||
$stop = $request->query->get('stop');
|
||||
$stop = Stop::reference($stop);
|
||||
|
||||
yield new RelatedFilter($stop);
|
||||
}
|
||||
|
||||
if ($request->query->has('line')) {
|
||||
$line = $request->query->get('line');
|
||||
$line = Line::reference($line);
|
||||
|
||||
yield new RelatedFilter($line);
|
||||
}
|
||||
|
||||
if ($request->query->has('id')) {
|
||||
$id = encapsulate($request->query->get('id'));
|
||||
|
||||
yield new IdFilter($id);
|
||||
}
|
||||
}
|
||||
|
||||
private function byId(Request $request, TrackRepository $repository)
|
||||
{
|
||||
$id = encapsulate($request->query->get('id'));
|
||||
|
||||
return $this->json($repository->getManyById($id));
|
||||
}
|
||||
|
||||
private function byStop(Request $request, TrackRepository $repository)
|
||||
{
|
||||
$stop = $request->query->get('stop');
|
||||
$stop = array_map([Stop::class, 'reference'], encapsulate($stop));
|
||||
|
||||
return $this->json($repository->getByStop($stop));
|
||||
}
|
||||
|
||||
private function byLine(Request $request, TrackRepository $repository)
|
||||
{
|
||||
$line = $request->query->get('line');
|
||||
$line = array_map([Stop::class, 'reference'], encapsulate($line));
|
||||
|
||||
return $this->json($repository->getByLine($line));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace App\Controller\Api\v1;
|
||||
|
||||
use App\Controller\Controller;
|
||||
use App\Model\Trip;
|
||||
use App\Modifier\IdFilter;
|
||||
use App\Provider\TripRepository;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
@ -18,7 +19,7 @@ class TripController extends Controller
|
||||
*/
|
||||
public function one($id, TripRepository $repository)
|
||||
{
|
||||
$trip = $repository->getById($id);
|
||||
$trip = $repository->all(new IdFilter($id));
|
||||
|
||||
return $this->json($trip, Response::HTTP_OK, [], $this->serializerContextFactory->create(Trip::class));
|
||||
}
|
||||
|
34
src/Event/HandleDatabaseModifierEvent.php
Normal file
34
src/Event/HandleDatabaseModifierEvent.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Event;
|
||||
|
||||
use App\Event\HandleModifierEvent;
|
||||
use App\Modifier\Modifier;
|
||||
use App\Provider\Repository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
class HandleDatabaseModifierEvent extends HandleModifierEvent
|
||||
{
|
||||
private $builder;
|
||||
|
||||
public function __construct(
|
||||
Modifier $modifier,
|
||||
Repository $repository,
|
||||
QueryBuilder $builder,
|
||||
array $meta = []
|
||||
) {
|
||||
parent::__construct($modifier, $repository, $meta);
|
||||
|
||||
$this->builder = $builder;
|
||||
}
|
||||
|
||||
public function getBuilder(): QueryBuilder
|
||||
{
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
public function replaceBuilder(QueryBuilder $builder): void
|
||||
{
|
||||
$this->builder = $builder;
|
||||
}
|
||||
}
|
35
src/Event/HandleModifierEvent.php
Normal file
35
src/Event/HandleModifierEvent.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Event;
|
||||
|
||||
use App\Modifier\Modifier;
|
||||
use App\Provider\Repository;
|
||||
|
||||
class HandleModifierEvent
|
||||
{
|
||||
private $repository;
|
||||
private $modifier;
|
||||
private $meta = [];
|
||||
|
||||
public function __construct(Modifier $modifier, Repository $repository, array $meta = [])
|
||||
{
|
||||
$this->repository = $repository;
|
||||
$this->modifier = $modifier;
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
public function getModifier(): Modifier
|
||||
{
|
||||
return $this->modifier;
|
||||
}
|
||||
|
||||
public function getRepository()
|
||||
{
|
||||
return $this->repository;
|
||||
}
|
||||
|
||||
public function getMeta(): array
|
||||
{
|
||||
return $this->meta;
|
||||
}
|
||||
}
|
27
src/Event/PostProcessEvent.php
Normal file
27
src/Event/PostProcessEvent.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Event;
|
||||
|
||||
use App\Modifier\Modifier;
|
||||
use App\Provider\Repository;
|
||||
|
||||
class PostProcessEvent extends HandleModifierEvent
|
||||
{
|
||||
private $data;
|
||||
|
||||
public function __construct($data, Modifier $modifier, Repository $repository, array $meta = [])
|
||||
{
|
||||
parent::__construct($modifier, $repository, $meta);
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function setData($data): void
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
13
src/Exception/InvalidOptionException.php
Normal file
13
src/Exception/InvalidOptionException.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exception;
|
||||
|
||||
class InvalidOptionException extends \InvalidArgumentException
|
||||
{
|
||||
public static function invalidType($parameter, $value, array $expected = [])
|
||||
{
|
||||
return new \InvalidArgumentException(
|
||||
sprintf('Expected %s to be of type: %s. %s given.', $parameter, implode(', ', $expected), gettype($value))
|
||||
);
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@
|
||||
namespace App\Exception;
|
||||
|
||||
|
||||
class NonExistentServiceException extends \Exception
|
||||
class NonExistentServiceException extends \LogicException
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,6 @@
|
||||
|
||||
namespace App\Exception;
|
||||
|
||||
class NotSupportedException extends \RuntimeException
|
||||
class NotSupportedException extends \LogicException
|
||||
{
|
||||
}
|
||||
}
|
||||
|
14
src/Exception/UnsupportedModifierException.php
Normal file
14
src/Exception/UnsupportedModifierException.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exception;
|
||||
|
||||
use App\Modifier\Modifier;
|
||||
use App\Provider\Repository;
|
||||
|
||||
class UnsupportedModifierException extends \LogicException
|
||||
{
|
||||
public static function createFromModifier(Modifier $modifier, Repository $repository)
|
||||
{
|
||||
return new static(sprintf("Modifier %s is not supported by %s.", get_class($modifier), get_class($repository)));
|
||||
}
|
||||
}
|
58
src/Handler/Database/FieldFilterDatabaseHandler.php
Normal file
58
src/Handler/Database/FieldFilterDatabaseHandler.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Handler\Database;
|
||||
|
||||
use App\Event\HandleDatabaseModifierEvent;
|
||||
use App\Event\HandleModifierEvent;
|
||||
use App\Handler\ModifierHandler;
|
||||
use App\Model\Stop;
|
||||
use App\Modifier\FieldFilter;
|
||||
use function App\Functions\encapsulate;
|
||||
|
||||
class FieldFilterDatabaseHandler implements ModifierHandler
|
||||
{
|
||||
protected $mapping = [
|
||||
Stop::class => [
|
||||
'name' => 'name',
|
||||
],
|
||||
];
|
||||
|
||||
public function process(HandleModifierEvent $event)
|
||||
{
|
||||
if (!$event instanceof HandleDatabaseModifierEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var FieldFilter $modifier */
|
||||
$modifier = $event->getModifier();
|
||||
$builder = $event->getBuilder();
|
||||
$alias = $event->getMeta()['alias'];
|
||||
|
||||
$field = $this->mapFieldName($event->getMeta()['type'], $modifier->getField());
|
||||
$operator = $modifier->getOperator();
|
||||
$value = $modifier->getValue();
|
||||
|
||||
$parameter = sprintf(":%s_%s", $alias, $field);
|
||||
|
||||
if ($operator === 'in' || $operator === 'not in') {
|
||||
$parameter = "($parameter)";
|
||||
$value = encapsulate($value);
|
||||
}
|
||||
|
||||
$builder
|
||||
->andWhere(sprintf("%s.%s %s %s", $alias, $field, $operator, $parameter))
|
||||
->setParameter($parameter, $value)
|
||||
;
|
||||
}
|
||||
|
||||
protected function mapFieldName(string $class, string $field)
|
||||
{
|
||||
if (!isset($this->mapping[$class][$field])) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf("Unable to map field %s of %s into entity field.", $field, $class)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->mapping[$class][$field];
|
||||
}
|
||||
}
|
44
src/Handler/Database/IdFilterDatabaseHandler.php
Normal file
44
src/Handler/Database/IdFilterDatabaseHandler.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Handler\Database;
|
||||
|
||||
use App\Handler\ModifierHandler;
|
||||
use App\Modifier\IdFilter;
|
||||
use App\Event\HandleDatabaseModifierEvent;
|
||||
use App\Event\HandleModifierEvent;
|
||||
use App\Service\IdUtils;
|
||||
use function Kadet\Functional\apply;
|
||||
|
||||
class IdFilterDatabaseHandler implements ModifierHandler
|
||||
{
|
||||
/**
|
||||
* @var IdUtils
|
||||
*/
|
||||
private $id;
|
||||
|
||||
public function __construct(IdUtils $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function process(HandleModifierEvent $event)
|
||||
{
|
||||
if (!$event instanceof HandleDatabaseModifierEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var IdFilter $modifier */
|
||||
$modifier = $event->getModifier();
|
||||
$builder = $event->getBuilder();
|
||||
$alias = $event->getMeta()['alias'];
|
||||
$provider = $event->getMeta()['provider'];
|
||||
|
||||
$id = $modifier->getId();
|
||||
$mapper = apply([$this->id, 'generate'], $provider);
|
||||
|
||||
$builder
|
||||
->andWhere($modifier->isMultiple() ? "{$alias} in (:id)" : "{$alias} = :id")
|
||||
->setParameter(':id', $modifier->isMultiple() ? array_map($mapper, $id) : $mapper($id));
|
||||
;
|
||||
}
|
||||
}
|
76
src/Handler/Database/IncludeDestinationsDatabaseHandler.php
Normal file
76
src/Handler/Database/IncludeDestinationsDatabaseHandler.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Handler\Database;
|
||||
|
||||
use App\Entity\TrackEntity;
|
||||
use App\Event\PostProcessEvent;
|
||||
use App\Handler\PostProcessingHandler;
|
||||
use App\Model\Destination;
|
||||
use App\Model\Stop;
|
||||
use App\Service\Converter;
|
||||
use App\Service\IdUtils;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
use Kadet\Functional as f;
|
||||
use Kadet\Functional\Transforms as t;
|
||||
|
||||
class IncludeDestinationsDatabaseHandler implements PostProcessingHandler
|
||||
{
|
||||
private $em;
|
||||
private $converter;
|
||||
private $id;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, Converter $converter, IdUtils $id)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
$this->converter = $converter;
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function postProcess(PostProcessEvent $event)
|
||||
{
|
||||
$provider = $event->getMeta()['provider'];
|
||||
$stops = $event
|
||||
->getData()
|
||||
->map(t\property('id'))
|
||||
->map(f\apply([$this->id, 'generate'], $provider))
|
||||
->all();
|
||||
|
||||
$destinations = collect($this->em->createQueryBuilder()
|
||||
->select('t', 'tl', 'f', 'fs', 'ts')
|
||||
->from(TrackEntity::class, 't')
|
||||
->join('t.stopsInTrack', 'ts')
|
||||
->join('t.line', 'tl')
|
||||
->where('ts.stop IN (:stops)')
|
||||
->join('t.final', 'f')
|
||||
->join('f.stop', 'fs')
|
||||
->getQuery()
|
||||
->execute(['stops' => $stops]))
|
||||
->reduce(function ($grouped, TrackEntity $track) {
|
||||
foreach ($track->getStopsInTrack()->map(t\property('stop'))->map(t\property('id')) as $stop) {
|
||||
$grouped[$stop] = ($grouped[$stop] ?? collect())->add($track);
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}, collect())
|
||||
->map(function (Collection $tracks) {
|
||||
return $tracks
|
||||
->groupBy(function (TrackEntity $track) {
|
||||
return $track->getFinal()->getStop()->getId();
|
||||
})->map(function (Collection $tracks, $id) {
|
||||
return Destination::createFromArray([
|
||||
'stop' => $this->converter->convert($tracks->first()->getFinal()->getStop()),
|
||||
'lines' => $tracks
|
||||
->map(t\property('line'))
|
||||
->unique(t\property('id'))
|
||||
->map(f\ref([$this->converter, 'convert']))
|
||||
->values(),
|
||||
]);
|
||||
})->values();
|
||||
});
|
||||
|
||||
$event->getData()->each(function (Stop $stop) use ($provider, $destinations) {
|
||||
$stop->setDestinations($destinations[$this->id->generate($provider, $stop->getId())]);
|
||||
});
|
||||
}
|
||||
}
|
27
src/Handler/Database/LimitDatabaseHandler.php
Normal file
27
src/Handler/Database/LimitDatabaseHandler.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Handler\Database;
|
||||
|
||||
use App\Event\HandleDatabaseModifierEvent;
|
||||
use App\Event\HandleModifierEvent;
|
||||
use App\Handler\ModifierHandler;
|
||||
use App\Modifier\Limit;
|
||||
|
||||
class LimitDatabaseHandler implements ModifierHandler
|
||||
{
|
||||
public function process(HandleModifierEvent $event)
|
||||
{
|
||||
if (!$event instanceof HandleDatabaseModifierEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Limit $modifier */
|
||||
$modifier = $event->getModifier();
|
||||
$builder = $event->getBuilder();
|
||||
|
||||
$builder
|
||||
->setFirstResult($modifier->getOffset())
|
||||
->setMaxResults($modifier->getCount())
|
||||
;
|
||||
}
|
||||
}
|
96
src/Handler/Database/RelatedFilterDatabaseGenericHandler.php
Normal file
96
src/Handler/Database/RelatedFilterDatabaseGenericHandler.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Handler\Database;
|
||||
|
||||
use App\Event\HandleDatabaseModifierEvent;
|
||||
use App\Event\HandleModifierEvent;
|
||||
use App\Handler\ModifierHandler;
|
||||
use App\Model\Line;
|
||||
use App\Model\Stop;
|
||||
use App\Model\Track;
|
||||
use App\Modifier\RelatedFilter;
|
||||
use App\Service\IdUtils;
|
||||
use App\Service\ReferenceFactory;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Contracts\Service\ServiceSubscriberInterface;
|
||||
|
||||
class RelatedFilterDatabaseGenericHandler implements ModifierHandler, ServiceSubscriberInterface
|
||||
{
|
||||
protected $mapping = [
|
||||
Track::class => [
|
||||
Line::class => 'line',
|
||||
Stop::class => TrackByStopDatabaseHandler::class,
|
||||
],
|
||||
];
|
||||
|
||||
private $em;
|
||||
private $inner;
|
||||
private $id;
|
||||
private $references;
|
||||
|
||||
public function __construct(
|
||||
ContainerInterface $inner,
|
||||
EntityManagerInterface $em,
|
||||
IdUtils $idUtils,
|
||||
ReferenceFactory $references
|
||||
) {
|
||||
$this->inner = $inner;
|
||||
$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($type, $this->mapping)) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf("Relationship filtering for %s is not supported.", $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()];
|
||||
|
||||
if ($this->inner->has($relationship)) {
|
||||
/** @var ModifierHandler $inner */
|
||||
$inner = $this->inner->get($relationship);
|
||||
$inner->process($event);
|
||||
return;
|
||||
}
|
||||
|
||||
$parameter = sprintf(":%s_%s", $alias, $relationship);
|
||||
$reference = $this->references->create($modifier->getRelated(), $event->getMeta()['provider']);
|
||||
|
||||
$builder
|
||||
->join(sprintf('%s.%s', $alias, $relationship), $relationship)
|
||||
->andWhere(sprintf("%s = %s", $relationship, $parameter))
|
||||
->setParameter($parameter, $reference)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function getSubscribedServices()
|
||||
{
|
||||
return [
|
||||
TrackByStopDatabaseHandler::class,
|
||||
];
|
||||
}
|
||||
}
|
43
src/Handler/Database/TrackByStopDatabaseHandler.php
Normal file
43
src/Handler/Database/TrackByStopDatabaseHandler.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Handler\Database;
|
||||
|
||||
use App\Entity\StopInTrack;
|
||||
use App\Event\HandleDatabaseModifierEvent;
|
||||
use App\Event\HandleModifierEvent;
|
||||
use App\Handler\ModifierHandler;
|
||||
use App\Modifier\RelatedFilter;
|
||||
use App\Service\ReferenceFactory;
|
||||
|
||||
class TrackByStopDatabaseHandler implements ModifierHandler
|
||||
{
|
||||
private $references;
|
||||
|
||||
public function __construct(ReferenceFactory $references)
|
||||
{
|
||||
$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'];
|
||||
|
||||
$relationship = 'stopsInTrack';
|
||||
|
||||
$parameter = sprintf(":%s_%s", $alias, $relationship);
|
||||
$reference = $this->references->create($modifier->getRelated(), $event->getMeta()['provider']);
|
||||
|
||||
$builder
|
||||
->join(sprintf("%s.%s", $alias, $relationship), 'stop_in_track')
|
||||
->andWhere(sprintf("stop_in_track.stop = %s", $parameter))
|
||||
->setParameter($parameter, $reference)
|
||||
;
|
||||
}
|
||||
}
|
10
src/Handler/ModifierHandler.php
Normal file
10
src/Handler/ModifierHandler.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Handler;
|
||||
|
||||
use App\Event\HandleModifierEvent;
|
||||
|
||||
interface ModifierHandler
|
||||
{
|
||||
public function process(HandleModifierEvent $event);
|
||||
}
|
10
src/Handler/PostProcessingHandler.php
Normal file
10
src/Handler/PostProcessingHandler.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Handler;
|
||||
|
||||
use App\Event\PostProcessEvent;
|
||||
|
||||
interface PostProcessingHandler
|
||||
{
|
||||
public function postProcess(PostProcessEvent $event);
|
||||
}
|
37
src/Modifier/FieldFilter.php
Normal file
37
src/Modifier/FieldFilter.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modifier;
|
||||
|
||||
class FieldFilter implements Modifier
|
||||
{
|
||||
private $field;
|
||||
private $value;
|
||||
private $operator;
|
||||
|
||||
public function __construct(string $field, $value, string $operator = '=')
|
||||
{
|
||||
$this->field = $field;
|
||||
$this->value = $value;
|
||||
$this->operator = $operator;
|
||||
}
|
||||
|
||||
public static function contains(string $field, string $value)
|
||||
{
|
||||
return new static($field, "%$value%", 'LIKE');
|
||||
}
|
||||
|
||||
public function getField(): string
|
||||
{
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getOperator(): string
|
||||
{
|
||||
return $this->operator;
|
||||
}
|
||||
}
|
31
src/Modifier/IdFilter.php
Normal file
31
src/Modifier/IdFilter.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modifier;
|
||||
|
||||
use App\Exception\InvalidOptionException;
|
||||
use App\Modifier\Modifier;
|
||||
|
||||
class IdFilter implements Modifier
|
||||
{
|
||||
/** @var string|array */
|
||||
private $id;
|
||||
|
||||
public function __construct($id)
|
||||
{
|
||||
if (!is_iterable($id) && !is_string($id)) {
|
||||
throw InvalidOptionException::invalidType('id', $id, ['string', 'array']);
|
||||
}
|
||||
|
||||
$this->id = $id instanceof \Traversable ? iterator_to_array($id) : $id;
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function isMultiple()
|
||||
{
|
||||
return is_array($this->id);
|
||||
}
|
||||
}
|
7
src/Modifier/IncludeDestinations.php
Normal file
7
src/Modifier/IncludeDestinations.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modifier;
|
||||
|
||||
class IncludeDestinations implements Modifier
|
||||
{
|
||||
}
|
30
src/Modifier/Limit.php
Normal file
30
src/Modifier/Limit.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modifier;
|
||||
|
||||
class Limit implements Modifier
|
||||
{
|
||||
private $offset;
|
||||
private $count;
|
||||
|
||||
public function __construct(int $offset = 0, ?int $count = null)
|
||||
{
|
||||
$this->offset = $offset;
|
||||
$this->count = $count;
|
||||
}
|
||||
|
||||
public function getOffset()
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
public function getCount()
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
public static function count(int $count)
|
||||
{
|
||||
return new static(0, $count);
|
||||
}
|
||||
}
|
8
src/Modifier/Modifier.php
Normal file
8
src/Modifier/Modifier.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modifier;
|
||||
|
||||
interface Modifier
|
||||
{
|
||||
|
||||
}
|
27
src/Modifier/RelatedFilter.php
Normal file
27
src/Modifier/RelatedFilter.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modifier;
|
||||
|
||||
use App\Model\Referable;
|
||||
|
||||
class RelatedFilter implements Modifier
|
||||
{
|
||||
private $relationship;
|
||||
private $object;
|
||||
|
||||
public function __construct(Referable $object, ?string $relation = null)
|
||||
{
|
||||
$this->object = $object;
|
||||
$this->relationship = $relation ?: get_class($object);
|
||||
}
|
||||
|
||||
public function getRelationship(): string
|
||||
{
|
||||
return $this->relationship;
|
||||
}
|
||||
|
||||
public function getRelated(): Referable
|
||||
{
|
||||
return $this->object;
|
||||
}
|
||||
}
|
@ -2,15 +2,31 @@
|
||||
|
||||
namespace App\Provider\Database;
|
||||
|
||||
use App\Entity\Entity;
|
||||
use App\Entity\ProviderEntity;
|
||||
use App\Event\HandleDatabaseModifierEvent;
|
||||
use App\Event\PostProcessEvent;
|
||||
use App\Exception\UnsupportedModifierException;
|
||||
use App\Handler\Database\IdFilterDatabaseHandler;
|
||||
use App\Handler\Database\LimitDatabaseHandler;
|
||||
use App\Handler\Database\FieldFilterDatabaseHandler;
|
||||
use App\Handler\Database\RelatedFilterDatabaseGenericHandler;
|
||||
use App\Handler\ModifierHandler;
|
||||
use App\Handler\PostProcessingHandler;
|
||||
use App\Model\Referable;
|
||||
use App\Modifier\IdFilter;
|
||||
use App\Modifier\Limit;
|
||||
use App\Modifier\Modifier;
|
||||
use App\Modifier\FieldFilter;
|
||||
use App\Modifier\RelatedFilter;
|
||||
use App\Provider\Repository;
|
||||
use App\Service\Converter;
|
||||
use App\Service\IdUtils;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Kadet\Functional as f;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Contracts\Service\ServiceSubscriberInterface;
|
||||
|
||||
class DatabaseRepository
|
||||
abstract class DatabaseRepository implements ServiceSubscriberInterface, Repository
|
||||
{
|
||||
/** @var EntityManagerInterface */
|
||||
protected $em;
|
||||
@ -24,22 +40,30 @@ class DatabaseRepository
|
||||
/** @var Converter */
|
||||
protected $converter;
|
||||
|
||||
/** @var ContainerInterface */
|
||||
protected $handlers;
|
||||
|
||||
/**
|
||||
* DatabaseRepository constructor.
|
||||
*
|
||||
* @param EntityManagerInterface $em
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em, IdUtils $id, Converter $converter)
|
||||
{
|
||||
public function __construct(
|
||||
EntityManagerInterface $em,
|
||||
IdUtils $id,
|
||||
Converter $converter,
|
||||
ContainerInterface $handlers
|
||||
) {
|
||||
$this->em = $em;
|
||||
$this->id = $id;
|
||||
$this->converter = $converter;
|
||||
$this->handlers = $handlers;
|
||||
}
|
||||
|
||||
/** @return static */
|
||||
public function withProvider(ProviderEntity $provider)
|
||||
{
|
||||
$result = clone $this;
|
||||
$result = clone $this;
|
||||
$result->provider = $provider;
|
||||
|
||||
return $result;
|
||||
@ -56,4 +80,87 @@ class DatabaseRepository
|
||||
|
||||
return $this->em->getReference($class, $id);
|
||||
}
|
||||
|
||||
protected function processQueryBuilder(QueryBuilder $builder, iterable $modifiers, array $meta = [])
|
||||
{
|
||||
$reducers = [];
|
||||
|
||||
foreach ($modifiers as $modifier) {
|
||||
$handler = $this->getHandler($modifier);
|
||||
|
||||
if ($handler instanceof ModifierHandler) {
|
||||
$event = new HandleDatabaseModifierEvent($modifier, $this, $builder, array_merge([
|
||||
'provider' => $this->provider,
|
||||
], $meta));
|
||||
|
||||
$handler->process($event);
|
||||
}
|
||||
|
||||
if ($handler instanceof PostProcessingHandler) {
|
||||
$reducers[] = function ($result) use ($meta, $modifier, $handler) {
|
||||
$event = new PostProcessEvent($result, $modifier, $this, array_merge([
|
||||
'provider' => $this->provider,
|
||||
], $meta));
|
||||
|
||||
$handler->postProcess($event);
|
||||
|
||||
return $event->getData();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return collect($reducers);
|
||||
}
|
||||
|
||||
protected function allFromQueryBuilder(QueryBuilder $builder, iterable $modifiers, array $meta = [])
|
||||
{
|
||||
$reducers = $this->processQueryBuilder($builder, $modifiers, $meta);
|
||||
|
||||
return $reducers->reduce(function ($result, $reducer) {
|
||||
return $reducer($result);
|
||||
}, collect($builder->getQuery()->execute())->map(\Closure::fromCallable([$this, 'convert'])));
|
||||
}
|
||||
|
||||
public function first(Modifier ...$modifiers)
|
||||
{
|
||||
return $this->all(Limit::count(1), ...$modifiers)->first();
|
||||
}
|
||||
|
||||
protected function getHandler(Modifier $modifier)
|
||||
{
|
||||
$class = get_class($modifier);
|
||||
|
||||
if (!$this->handlers->has($class)) {
|
||||
throw UnsupportedModifierException::createFromModifier($modifier, $this);
|
||||
}
|
||||
|
||||
return $this->handlers->get($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array describing handlers for each modifier type. Syntax is as follows:
|
||||
* [ IdFilter::class => IdFilterDatabaseHandler::class ]
|
||||
*
|
||||
* It is internally used as part of service subscriber.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function getHandlers()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function getSubscribedServices()
|
||||
{
|
||||
return array_merge([
|
||||
IdFilter::class => IdFilterDatabaseHandler::class,
|
||||
Limit::class => LimitDatabaseHandler::class,
|
||||
FieldFilter::class => FieldFilterDatabaseHandler::class,
|
||||
RelatedFilter::class => RelatedFilterDatabaseGenericHandler::class,
|
||||
], static::getHandlers());
|
||||
}
|
||||
}
|
||||
|
@ -3,34 +3,32 @@
|
||||
namespace App\Provider\Database;
|
||||
|
||||
use App\Entity\LineEntity;
|
||||
use App\Event\HandleDatabaseModifierEvent;
|
||||
use App\Handler\Database\LimitDatabaseHandler;
|
||||
use App\Handler\Database\IdFilterDatabaseHandler;
|
||||
use App\Handler\ModifierHandler;
|
||||
use App\Model\Line;
|
||||
use App\Modifier\Limit;
|
||||
use App\Modifier\IdFilter;
|
||||
use App\Provider\LineRepository;
|
||||
use App\Modifier\Modifier;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
use Kadet\Functional as f;
|
||||
|
||||
class GenericLineRepository extends DatabaseRepository implements LineRepository
|
||||
{
|
||||
public function getAll(): Collection
|
||||
public function all(Modifier ...$modifiers): Collection
|
||||
{
|
||||
$repository = $this->em->getRepository(LineEntity::class);
|
||||
$lines = $repository->findAll();
|
||||
$builder = $this->em
|
||||
->createQueryBuilder()
|
||||
->from(LineEntity::class, 'line')
|
||||
->select('line')
|
||||
;
|
||||
|
||||
return collect($lines)->map(f\ref([$this, 'convert']));
|
||||
return $this->allFromQueryBuilder($builder, $modifiers, [
|
||||
'alias' => 'line',
|
||||
'entity' => LineEntity::class,
|
||||
'type' => Line::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getById($id): ?Line
|
||||
{
|
||||
$repository = $this->em->getRepository(LineEntity::class);
|
||||
return $this->convert($repository->find($id));
|
||||
}
|
||||
|
||||
public function getManyById($ids): Collection
|
||||
{
|
||||
$ids = collect($ids)->map(f\apply(f\ref([$this->id, 'generate']), $this->provider));
|
||||
|
||||
$repository = $this->em->getRepository(LineEntity::class);
|
||||
$lines = $repository->findBy(['id' => $ids->all()]);
|
||||
|
||||
return collect($lines)->map(f\ref([$this, 'convert']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,32 +2,26 @@
|
||||
|
||||
namespace App\Provider\Database;
|
||||
|
||||
use App\Entity\OperatorEntity;
|
||||
use App\Model\Operator;
|
||||
use App\Modifier\Modifier;
|
||||
use App\Provider\OperatorRepository;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
|
||||
class GenericOperatorRepository extends DatabaseRepository implements OperatorRepository
|
||||
{
|
||||
public function getAll(): Collection
|
||||
public function all(Modifier ...$modifiers): Collection
|
||||
{
|
||||
$repository = $this->em->getRepository(Operator::class);
|
||||
$operators = $repository->findAll();
|
||||
$builder = $this->em
|
||||
->createQueryBuilder()
|
||||
->from(OperatorEntity::class, 'operator')
|
||||
->select('operator')
|
||||
;
|
||||
|
||||
return collect($operators);
|
||||
return $this->allFromQueryBuilder($builder, $modifiers, [
|
||||
'alias' => 'operator',
|
||||
'entity' => OperatorEntity::class,
|
||||
'type' => Operator::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getById($id): ?Operator
|
||||
{
|
||||
$repository = $this->em->getRepository(Operator::class);
|
||||
|
||||
return $repository->find($id);
|
||||
}
|
||||
|
||||
public function getManyById($ids): Collection
|
||||
{
|
||||
$repository = $this->em->getRepository(Operator::class);
|
||||
$operators = $repository->findBy(['id' => $ids]);
|
||||
|
||||
return collect($operators);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,4 +70,9 @@ class GenericScheduleRepository extends DatabaseRepository implements ScheduleRe
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
protected static function getHandlers()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -3,84 +3,34 @@
|
||||
namespace App\Provider\Database;
|
||||
|
||||
use App\Entity\StopEntity;
|
||||
use App\Entity\TrackEntity;
|
||||
use App\Model\Destination;
|
||||
use App\Handler\Database\IncludeDestinationsDatabaseHandler;
|
||||
use App\Model\Stop;
|
||||
use App\Modifier\Modifier;
|
||||
use App\Modifier\IncludeDestinations;
|
||||
use App\Provider\StopRepository;
|
||||
use Kadet\Functional as f;
|
||||
use Kadet\Functional\Transforms as t;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
|
||||
class GenericStopRepository extends DatabaseRepository implements StopRepository
|
||||
{
|
||||
public function getAll(): Collection
|
||||
public function all(Modifier ...$modifiers): Collection
|
||||
{
|
||||
$stops = $this->em->getRepository(StopEntity::class)->findAll();
|
||||
$builder = $this->em
|
||||
->createQueryBuilder()
|
||||
->from(StopEntity::class, 'stop')
|
||||
->select('stop')
|
||||
;
|
||||
|
||||
return collect($stops)->map(f\ref([$this, 'convert']));
|
||||
return $this->allFromQueryBuilder($builder, $modifiers, [
|
||||
'alias' => 'stop',
|
||||
'entity' => StopEntity::class,
|
||||
'type' => Stop::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getById($id): ?Stop
|
||||
protected static function getHandlers()
|
||||
{
|
||||
$id = $this->id->generate($this->provider, $id);
|
||||
$stop = $this->em->getRepository(StopEntity::class)->find($id);
|
||||
|
||||
return $this->convert($stop);
|
||||
}
|
||||
|
||||
public function getManyById($ids): Collection
|
||||
{
|
||||
$ids = collect($ids)->map(f\apply(f\ref([$this->id, 'generate']), $this->provider));
|
||||
$stops = $this->em->getRepository(StopEntity::class)->findBy(['id' => $ids->all()]);
|
||||
|
||||
return collect($stops)->map(f\ref([$this, 'convert']));
|
||||
}
|
||||
|
||||
public function findByName(string $name): Collection
|
||||
{
|
||||
$query = $this->em->createQueryBuilder()
|
||||
->select('s')
|
||||
->from(StopEntity::class, 's')
|
||||
->where('s.name LIKE :name')
|
||||
->getQuery();
|
||||
|
||||
$stops = collect($query->execute([':name' => "%$name%"]));
|
||||
|
||||
$destinations = collect($this->em->createQueryBuilder()
|
||||
->select('t', 'tl', 'f', 'fs', 'ts')
|
||||
->from(TrackEntity::class, 't')
|
||||
->join('t.stopsInTrack', 'ts')
|
||||
->join('t.line', 'tl')
|
||||
->where('ts.stop IN (:stops)')
|
||||
->join('t.final', 'f')
|
||||
->join('f.stop', 'fs')
|
||||
->getQuery()
|
||||
->execute(['stops' => $stops->map(t\property('id'))->all()]))
|
||||
->reduce(function ($grouped, TrackEntity $track) {
|
||||
foreach ($track->getStopsInTrack()->map(t\property('stop'))->map(t\property('id')) as $stop) {
|
||||
$grouped[$stop] = ($grouped[$stop] ?? collect())->add($track);
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}, collect())
|
||||
->map(function (Collection $tracks) {
|
||||
return $tracks
|
||||
->groupBy(function (TrackEntity $track) {
|
||||
return $track->getFinal()->getStop()->getId();
|
||||
})->map(function (Collection $tracks, $id) {
|
||||
return Destination::createFromArray([
|
||||
'stop' => $this->convert($tracks->first()->getFinal()->getStop()),
|
||||
'lines' => $tracks
|
||||
->map(t\property('line'))
|
||||
->unique(t\property('id'))
|
||||
->map(f\ref([$this, 'convert']))
|
||||
->values(),
|
||||
]);
|
||||
})->values();
|
||||
});
|
||||
|
||||
return collect($stops)->map(f\ref([$this, 'convert']))->each(function (Stop $stop) use ($destinations) {
|
||||
$stop->setDestinations($destinations[$this->id->generate($this->provider, $stop->getId())]);
|
||||
});
|
||||
return array_merge(parent::getHandlers(), [
|
||||
IncludeDestinations::class => IncludeDestinationsDatabaseHandler::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -2,36 +2,18 @@
|
||||
|
||||
namespace App\Provider\Database;
|
||||
|
||||
use App\Entity\LineEntity;
|
||||
use App\Entity\StopEntity;
|
||||
use App\Entity\StopInTrack;
|
||||
use App\Entity\TrackEntity;
|
||||
use function App\Functions\encapsulate;
|
||||
use App\Model\Stop;
|
||||
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 getAll(): Collection
|
||||
{
|
||||
$tracks = $this->em->getRepository(TrackEntity::class)->findAll();
|
||||
|
||||
return collect($tracks)->map(f\ref([$this, 'convert']));
|
||||
}
|
||||
|
||||
public function getById($id): Track
|
||||
{
|
||||
// TODO: Implement getById() method.
|
||||
}
|
||||
|
||||
public function getManyById($ids): Collection
|
||||
{
|
||||
// TODO: Implement getManyById() method.
|
||||
}
|
||||
|
||||
public function getByStop($stop): Collection
|
||||
{
|
||||
$reference = f\apply(f\ref([$this, 'reference']), StopEntity::class);
|
||||
@ -49,19 +31,17 @@ class GenericTrackRepository extends DatabaseRepository implements TrackReposito
|
||||
});
|
||||
}
|
||||
|
||||
public function getByLine($line): Collection
|
||||
public function all(Modifier ...$modifiers): Collection
|
||||
{
|
||||
$reference = f\apply(f\ref([$this, 'reference']), LineEntity::class);
|
||||
$builder = $this->em
|
||||
->createQueryBuilder()
|
||||
->from(TrackEntity::class, 'track')
|
||||
->select('track');
|
||||
|
||||
$tracks = $this->em->createQueryBuilder()
|
||||
->from(StopInTrack::class, 'st')
|
||||
->join('st.track', 't')
|
||||
->join('t.stops', 's')
|
||||
->where('st.line in (:line)')
|
||||
->select(['st', 't', 's'])
|
||||
->getQuery()
|
||||
->execute(['stop' => array_map($reference, encapsulate($line))]);
|
||||
|
||||
return collect($tracks)->map(f\ref([$this, 'convert']));
|
||||
return $this->allFromQueryBuilder($builder, $modifiers, [
|
||||
'alias' => 'track',
|
||||
'entity' => TrackEntity::class,
|
||||
'type' => Track::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,25 +4,25 @@ namespace App\Provider\Database;
|
||||
|
||||
use App\Entity\TripEntity;
|
||||
use App\Model\Trip;
|
||||
use App\Modifier\Modifier;
|
||||
use App\Provider\TripRepository;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
|
||||
class GenericTripRepository extends DatabaseRepository implements TripRepository
|
||||
{
|
||||
public function getById(string $id): Trip
|
||||
public function all(Modifier ...$modifiers): Collection
|
||||
{
|
||||
$id = $this->id->generate($this->provider, $id);
|
||||
|
||||
$trip = $this->em
|
||||
$builder = $this->em
|
||||
->createQueryBuilder()
|
||||
->from(TripEntity::class, 't')
|
||||
->join('t.stops', 'ts')
|
||||
->from(TripEntity::class, 'trip')
|
||||
->join('trip.stops', 'ts')
|
||||
->join('ts.stop', 's')
|
||||
->select('t', 'ts')
|
||||
->where('t.id = :id')
|
||||
->getQuery()
|
||||
->setParameter('id', $id)
|
||||
->getOneOrNullResult();
|
||||
->select('t', 'ts');
|
||||
|
||||
return $this->convert($trip);
|
||||
return $this->allFromQueryBuilder($builder, $modifiers, [
|
||||
'alias' => 'trip',
|
||||
'entity' => TripEntity::class,
|
||||
'type' => Trip::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Provider\Dummy;
|
||||
|
||||
use App\Model\Stop;
|
||||
use App\Modifier\Modifier;
|
||||
use App\Provider\StopRepository;
|
||||
use App\Service\Proxy\ReferenceFactory;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
@ -41,4 +42,14 @@ class DummyStopRepository implements StopRepository
|
||||
{
|
||||
return collect();
|
||||
}
|
||||
|
||||
public function first(Modifier ...$modifiers)
|
||||
{
|
||||
// TODO: Implement first() method.
|
||||
}
|
||||
|
||||
public function all(Modifier ...$modifiers): Collection
|
||||
{
|
||||
// TODO: Implement all() method.
|
||||
}
|
||||
}
|
||||
|
12
src/Provider/FluentRepository.php
Normal file
12
src/Provider/FluentRepository.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Provider;
|
||||
|
||||
use App\Modifier\Modifier;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
|
||||
interface FluentRepository extends Repository
|
||||
{
|
||||
public function first(Modifier ...$modifiers);
|
||||
public function all(Modifier ...$modifiers): Collection;
|
||||
}
|
@ -3,14 +3,6 @@
|
||||
|
||||
namespace App\Provider;
|
||||
|
||||
|
||||
use App\Model\Line;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
|
||||
interface LineRepository extends Repository
|
||||
interface LineRepository extends FluentRepository
|
||||
{
|
||||
public function getAll(): Collection;
|
||||
|
||||
public function getById($id): ?Line;
|
||||
public function getManyById($ids): Collection;
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,6 @@ namespace App\Provider;
|
||||
use App\Model\Operator;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
|
||||
interface OperatorRepository
|
||||
interface OperatorRepository extends FluentRepository
|
||||
{
|
||||
public function getAll(): Collection;
|
||||
public function getById($id): ?Operator;
|
||||
public function getManyById($ids): Collection;
|
||||
}
|
||||
}
|
||||
|
@ -6,5 +6,4 @@ namespace App\Provider;
|
||||
|
||||
interface Repository
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,6 @@ namespace App\Provider;
|
||||
use App\Model\Stop;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
|
||||
interface StopRepository extends Repository
|
||||
interface StopRepository extends FluentRepository
|
||||
{
|
||||
public function getAll(): Collection;
|
||||
public function getById($id): ?Stop;
|
||||
public function getManyById($ids): Collection;
|
||||
public function findByName(string $name): Collection;
|
||||
}
|
||||
|
@ -5,13 +5,7 @@ namespace App\Provider;
|
||||
use App\Model\Track;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
|
||||
interface TrackRepository
|
||||
interface TrackRepository extends FluentRepository
|
||||
{
|
||||
public function getAll(): Collection;
|
||||
|
||||
public function getById($id): Track;
|
||||
public function getManyById($ids): Collection;
|
||||
|
||||
public function getByStop($stop): Collection;
|
||||
public function getByLine($line): Collection;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ namespace App\Provider;
|
||||
|
||||
use App\Model\Trip;
|
||||
|
||||
interface TripRepository
|
||||
interface TripRepository extends FluentRepository
|
||||
{
|
||||
public function getById(string $id): Trip;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use App\Model\Departure;
|
||||
use App\Model\Line;
|
||||
use App\Model\Stop;
|
||||
use App\Model\Vehicle;
|
||||
use App\Modifier\IdFilter;
|
||||
use App\Provider\Database\GenericScheduleRepository;
|
||||
use App\Provider\DepartureRepository;
|
||||
use App\Provider\LineRepository;
|
||||
@ -65,7 +66,8 @@ class ZtmGdanskDepartureRepository implements DepartureRepository
|
||||
$lines = $estimates->map(function ($delay) {
|
||||
return $delay['routeId'];
|
||||
})->unique();
|
||||
$lines = $this->lines->getManyById($lines)->keyBy(t\property('id'));
|
||||
|
||||
$lines = $this->lines->all(new IdFilter($lines))->keyBy(t\property('id'));
|
||||
|
||||
return collect($estimates)->map(function ($delay) use ($stop, $lines) {
|
||||
$scheduled = (new Carbon($delay['theoreticalTime'], 'Europe/Warsaw'))->tz('UTC');
|
||||
|
@ -11,6 +11,7 @@ class IdUtils
|
||||
|
||||
public function generate(ProviderEntity $provider, $id)
|
||||
{
|
||||
// todo: use array cache if not fast enough
|
||||
return sprintf('%s%s%s', $provider->getId(), self::DELIMITER, $id);
|
||||
}
|
||||
|
||||
@ -23,4 +24,4 @@ class IdUtils
|
||||
{
|
||||
return $this->strip($entity->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
42
src/Service/ReferenceFactory.php
Normal file
42
src/Service/ReferenceFactory.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\LineEntity;
|
||||
use App\Entity\ProviderEntity;
|
||||
use App\Entity\StopEntity;
|
||||
use App\Model\Line;
|
||||
use App\Model\Referable;
|
||||
use App\Model\Stop;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
final class ReferenceFactory
|
||||
{
|
||||
protected $mapping = [
|
||||
Line::class => LineEntity::class,
|
||||
Stop::class => StopEntity::class,
|
||||
];
|
||||
|
||||
private $em;
|
||||
private $id;
|
||||
|
||||
public function __construct(EntityManagerInterface $em, IdUtils $id)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function create(Referable $object, ProviderEntity $provider)
|
||||
{
|
||||
$class = get_class($object);
|
||||
|
||||
if (!array_key_exists($class, $this->mapping)) {
|
||||
throw new \InvalidArgumentException(sprintf("Cannot make entity reference of %s.", $class));
|
||||
}
|
||||
|
||||
return $this->em->getReference(
|
||||
$this->mapping[$class],
|
||||
$this->id->generate($provider, $object->getId())
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user