From a3de2b244f0a550c2de340d3280f24f4ce0e1062 Mon Sep 17 00:00:00 2001
From: Kacper Donat <kadet1090@gmail.com>
Date: Mon, 17 Feb 2020 21:48:00 +0100
Subject: [PATCH] Rewrite Track Repository into fluent pattern

---
 src/Controller/Api/v1/StopsController.php     |  1 +
 src/Controller/Api/v1/TracksController.php    | 11 ++-
 .../Database/FieldFilterDatabaseHandler.php   |  8 +-
 .../Database/IdFilterDatabaseHandler.php      |  2 +-
 .../IncludeDestinationsDatabaseHandler.php    |  2 +-
 .../RelatedFilterDatabaseGenericHandler.php   | 96 +++++++++++++++++++
 src/Handler/PostProcessingHandler.php         |  3 +-
 src/Modifier/RelatedFilter.php                | 27 ++++++
 src/Provider/Database/DatabaseRepository.php  | 39 ++++----
 .../Database/GenericTrackRepository.php       | 49 +++-------
 .../Database/GenericTripRepository.php        |  2 +-
 src/Provider/TrackRepository.php              | 10 +-
 12 files changed, 177 insertions(+), 73 deletions(-)
 create mode 100644 src/Handler/Database/RelatedFilterDatabaseGenericHandler.php
 create mode 100644 src/Modifier/RelatedFilter.php

diff --git a/src/Controller/Api/v1/StopsController.php b/src/Controller/Api/v1/StopsController.php
index 0fca3e4..64c4107 100644
--- a/src/Controller/Api/v1/StopsController.php
+++ b/src/Controller/Api/v1/StopsController.php
@@ -10,6 +10,7 @@ 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;
diff --git a/src/Controller/Api/v1/TracksController.php b/src/Controller/Api/v1/TracksController.php
index 069a338..6f9fbad 100644
--- a/src/Controller/Api/v1/TracksController.php
+++ b/src/Controller/Api/v1/TracksController.php
@@ -3,8 +3,11 @@
 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 Nelmio\ApiDocBundle\Annotation\Model;
 use Swagger\Annotations as SWG;
@@ -49,7 +52,7 @@ class TracksController extends Controller
     {
         $id = encapsulate($request->query->get('id'));
 
-        return $this->json($repository->getManyById($id));
+        return $this->json($repository->all(new IdFilter($id)));
     }
 
     private function byStop(Request $request, TrackRepository $repository)
@@ -63,8 +66,8 @@ class TracksController extends Controller
     private function byLine(Request $request, TrackRepository $repository)
     {
         $line = $request->query->get('line');
-        $line = array_map([Stop::class, 'reference'], encapsulate($line));
+        $line = Line::reference($line);
 
-        return $this->json($repository->getByLine($line));
+        return $this->json($repository->all(new RelatedFilter($line)));
     }
-}
\ No newline at end of file
+}
diff --git a/src/Handler/Database/FieldFilterDatabaseHandler.php b/src/Handler/Database/FieldFilterDatabaseHandler.php
index cb77a66..0bfc83c 100644
--- a/src/Handler/Database/FieldFilterDatabaseHandler.php
+++ b/src/Handler/Database/FieldFilterDatabaseHandler.php
@@ -7,6 +7,7 @@ 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
 {
@@ -33,8 +34,13 @@ class FieldFilterDatabaseHandler implements ModifierHandler
 
         $parameter = sprintf(":%s_%s", $alias, $field);
 
+        if ($operator === 'in' || $operator === 'not in') {
+            $parameter = "($parameter)";
+            $value     = encapsulate($value);
+        }
+
         $builder
-            ->where(sprintf("%s.%s %s %s", $alias, $field, $operator, $parameter))
+            ->andWhere(sprintf("%s.%s %s %s", $alias, $field, $operator, $parameter))
             ->setParameter($parameter, $value)
         ;
     }
diff --git a/src/Handler/Database/IdFilterDatabaseHandler.php b/src/Handler/Database/IdFilterDatabaseHandler.php
index 10f124c..9f95d94 100644
--- a/src/Handler/Database/IdFilterDatabaseHandler.php
+++ b/src/Handler/Database/IdFilterDatabaseHandler.php
@@ -37,7 +37,7 @@ class IdFilterDatabaseHandler implements ModifierHandler
         $mapper   = apply([$this->id, 'generate'], $provider);
 
         $builder
-            ->where($modifier->isMultiple() ? "{$alias} in (:id)" : "{$alias} = :id")
+            ->andWhere($modifier->isMultiple() ? "{$alias} in (:id)" : "{$alias} = :id")
             ->setParameter(':id', $modifier->isMultiple() ? array_map($mapper, $id) : $mapper($id));
         ;
     }
diff --git a/src/Handler/Database/IncludeDestinationsDatabaseHandler.php b/src/Handler/Database/IncludeDestinationsDatabaseHandler.php
index 4d5e1f2..b58287f 100644
--- a/src/Handler/Database/IncludeDestinationsDatabaseHandler.php
+++ b/src/Handler/Database/IncludeDestinationsDatabaseHandler.php
@@ -27,7 +27,7 @@ class IncludeDestinationsDatabaseHandler implements PostProcessingHandler
         $this->id = $id;
     }
 
-    public function process(PostProcessEvent $event)
+    public function postProcess(PostProcessEvent $event)
     {
         $provider = $event->getMeta()['provider'];
         $stops = $event
diff --git a/src/Handler/Database/RelatedFilterDatabaseGenericHandler.php b/src/Handler/Database/RelatedFilterDatabaseGenericHandler.php
new file mode 100644
index 0000000..cb4627e
--- /dev/null
+++ b/src/Handler/Database/RelatedFilterDatabaseGenericHandler.php
@@ -0,0 +1,96 @@
+<?php
+
+namespace App\Handler\Database;
+
+use App\Entity\LineEntity;
+use App\Entity\ProviderEntity;
+use App\Event\HandleDatabaseModifierEvent;
+use App\Event\HandleModifierEvent;
+use App\Handler\ModifierHandler;
+use App\Model\Line;
+use App\Model\Referable;
+use App\Model\Track;
+use App\Modifier\RelatedFilter;
+use App\Service\IdUtils;
+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',
+        ],
+    ];
+
+    protected $references = [
+        Line::class => LineEntity::class,
+    ];
+
+    private $em;
+    private $inner;
+    private $id;
+
+    public function __construct(ContainerInterface $inner, EntityManagerInterface $em, IdUtils $idUtils)
+    {
+        $this->inner = $inner;
+        $this->em = $em;
+        $this->id = $idUtils;
+    }
+
+    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()];
+
+        $parameter = sprintf(":%s_%s", $alias, $relationship);
+        $reference = $this->getEntityReference($modifier->getRelated(), $event->getMeta()['provider']);
+
+        $builder
+            ->join(sprintf('%s.%s', $alias, $relationship), $relationship)
+            ->andWhere(sprintf("%s = %s", $relationship, $parameter))
+            ->setParameter($parameter, $reference)
+        ;
+    }
+
+    // todo: extract that to separate service
+    private function getEntityReference(Referable $object, ProviderEntity $provider)
+    {
+        return $this->em->getReference(
+            $this->references[get_class($object)],
+            $this->id->generate($provider, $object->getId())
+        );
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public static function getSubscribedServices()
+    {
+        return [
+            TrackRelatedFilterDatabaseHandler::class,
+        ];
+    }
+}
diff --git a/src/Handler/PostProcessingHandler.php b/src/Handler/PostProcessingHandler.php
index 4da8a89..a18f0d8 100644
--- a/src/Handler/PostProcessingHandler.php
+++ b/src/Handler/PostProcessingHandler.php
@@ -2,10 +2,9 @@
 
 namespace App\Handler;
 
-use App\Event\HandleModifierEvent;
 use App\Event\PostProcessEvent;
 
 interface PostProcessingHandler
 {
-    public function process(PostProcessEvent $event);
+    public function postProcess(PostProcessEvent $event);
 }
diff --git a/src/Modifier/RelatedFilter.php b/src/Modifier/RelatedFilter.php
new file mode 100644
index 0000000..f3e9038
--- /dev/null
+++ b/src/Modifier/RelatedFilter.php
@@ -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;
+    }
+}
diff --git a/src/Provider/Database/DatabaseRepository.php b/src/Provider/Database/DatabaseRepository.php
index e7e7529..60ad5ca 100644
--- a/src/Provider/Database/DatabaseRepository.php
+++ b/src/Provider/Database/DatabaseRepository.php
@@ -9,12 +9,15 @@ 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;
@@ -85,27 +88,26 @@ abstract class DatabaseRepository implements ServiceSubscriberInterface, Reposit
         foreach ($modifiers as $modifier) {
             $handler = $this->getHandler($modifier);
 
-            switch (true) {
-                case $handler instanceof PostProcessingHandler:
-                    $reducers[] = function ($result) use ($meta, $modifier, $handler) {
-                        $event = new PostProcessEvent($result, $modifier, $this, array_merge([
-                            'provider' => $this->provider,
-                        ], $meta));
+            if ($handler instanceof ModifierHandler) {
+                $event = new HandleDatabaseModifierEvent($modifier, $this, $builder, array_merge([
+                    'provider' => $this->provider,
+                ], $meta));
 
-                        $handler->process($event);
+                $handler->process($event);
+            }
 
-                        return $event->getData();
-                    };
-                    break;
-
-                default:
-                    $event = new HandleDatabaseModifierEvent($modifier, $this, $builder, array_merge([
+            if ($handler instanceof PostProcessingHandler) {
+                $reducers[] = function ($result) use ($meta, $modifier, $handler) {
+                    $event = new PostProcessEvent($result, $modifier, $this, array_merge([
                         'provider' => $this->provider,
                     ], $meta));
 
-                    $handler->process($event);
-                    break;
+                    $handler->postProcess($event);
+
+                    return $event->getData();
+                };
             }
+
         }
 
         return collect($reducers);
@@ -155,9 +157,10 @@ abstract class DatabaseRepository implements ServiceSubscriberInterface, Reposit
     public static function getSubscribedServices()
     {
         return array_merge([
-            IdFilter::class    => IdFilterDatabaseHandler::class,
-            Limit::class       => LimitDatabaseHandler::class,
-            FieldFilter::class => FieldFilterDatabaseHandler::class,
+            IdFilter::class      => IdFilterDatabaseHandler::class,
+            Limit::class         => LimitDatabaseHandler::class,
+            FieldFilter::class   => FieldFilterDatabaseHandler::class,
+            RelatedFilter::class => RelatedFilterDatabaseGenericHandler::class,
         ], static::getHandlers());
     }
 }
diff --git a/src/Provider/Database/GenericTrackRepository.php b/src/Provider/Database/GenericTrackRepository.php
index 53bd235..82699d7 100644
--- a/src/Provider/Database/GenericTrackRepository.php
+++ b/src/Provider/Database/GenericTrackRepository.php
@@ -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,24 +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']));
-    }
-
-    protected static function getHandlers()
-    {
-        return [];
+        return $this->allFromQueryBuilder($builder, $modifiers, [
+            'alias'  => 'track',
+            'entity' => TrackEntity::class,
+            'type'   => Track::class,
+        ]);
     }
 }
diff --git a/src/Provider/Database/GenericTripRepository.php b/src/Provider/Database/GenericTripRepository.php
index 00626ad..6ffd652 100644
--- a/src/Provider/Database/GenericTripRepository.php
+++ b/src/Provider/Database/GenericTripRepository.php
@@ -20,7 +20,7 @@ class GenericTripRepository extends DatabaseRepository implements TripRepository
             ->select('t', 'ts');
 
         return $this->allFromQueryBuilder($builder, $modifiers, [
-            'alias'  => 'operator',
+            'alias'  => 'trip',
             'entity' => TripEntity::class,
             'type'   => Trip::class,
         ]);
diff --git a/src/Provider/TrackRepository.php b/src/Provider/TrackRepository.php
index 708e329..3e70a03 100644
--- a/src/Provider/TrackRepository.php
+++ b/src/Provider/TrackRepository.php
@@ -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;
-}
\ No newline at end of file
+}