diff --git a/composer.json b/composer.json index 20a22a4..4456728 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,7 @@ "ext-iconv": "*", "ext-json": "*", "nesbot/carbon": "^1.33", + "ocramius/proxy-manager": "^2.2", "sensio/framework-extra-bundle": "^5.2", "symfony/console": "*", "symfony/flex": "^1.1", diff --git a/composer.lock b/composer.lock index 99880b7..3683b64 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0af7567013958485d4223a33f8ba099a", + "content-hash": "23c4b2debfec2fc7da526a8bd2007ec3", "packages": [ { "name": "doctrine/annotations", diff --git a/config/services.yaml b/config/services.yaml index 0ea34cb..279dc5f 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -31,10 +31,32 @@ services: resource: '../src/Provider' public: true + # add more service definitions when explicit configuration is needed + # please note that last definitions always *replace* previous ones + + #proxy configuration + proxy.locator: + class: 'ProxyManager\FileLocator\FileLocator' + arguments: ['%kernel.cache_dir%/proxy'] + + proxy.strategy: + class: 'ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy' + arguments: ['@proxy.locator'] + + proxy.config: + class: 'ProxyManager\Configuration' + calls: + - ['setGeneratorStrategy', ['@proxy.strategy']] + - ['setProxiesTargetDir', ['%kernel.cache_dir%/proxy']] + + ProxyManager\Configuration: '@proxy.config' + + # serializer configuration serializer.datetime_normalizer: class: Symfony\Component\Serializer\Normalizer\DateTimeNormalizer arguments: [!php/const DateTime::ATOM] - tags: [serializer.normalizer] + tags: [serializer.normalizer] - # add more service definitions when explicit configuration is needed - # please note that last definitions always *replace* previous ones + App\Service\Normalizer\: + resource: '../src/Service/Normalizer' + tags: [serializer.normalizer] diff --git a/src/Entity/TrackEntity.php b/src/Entity/TrackEntity.php index 833c71a..b3d2482 100644 --- a/src/Entity/TrackEntity.php +++ b/src/Entity/TrackEntity.php @@ -46,7 +46,7 @@ class TrackEntity implements Entity, Fillable * Stops in track * @var Collection * @ORM\OneToMany(targetEntity=StopInTrack::class, mappedBy="track", cascade={"persist"}) - * @ORM\OrderBy({"order"}) + * @ORM\OrderBy({"order": "ASC"}) */ private $stopsInTrack; diff --git a/src/Kernel.php b/src/Kernel.php index dc2f242..fa55da6 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -48,6 +48,10 @@ class Kernel extends BaseKernel $loader->load($confDir.'/{packages}/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob'); $loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob'); $loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob'); + + if (!file_exists($this->getCacheDir().'/proxy')) { + mkdir($this->getCacheDir().'/proxy'); + } } protected function configureRoutes(RouteCollectionBuilder $routes) diff --git a/src/Model/Line.php b/src/Model/Line.php index 9d17a9f..917913b 100644 --- a/src/Model/Line.php +++ b/src/Model/Line.php @@ -2,12 +2,9 @@ namespace App\Model; -use Symfony\Component\Serializer\Normalizer\NormalizableInterface; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Tightenco\Collect\Support\Collection; -class Line implements Fillable, Referable, NormalizableInterface +class Line implements Fillable, Referable { const TYPE_TRAM = 'tram'; const TYPE_BUS = 'bus'; @@ -120,12 +117,4 @@ class Line implements Fillable, Referable, NormalizableInterface { $this->operator = $operator; } - - public function normalize(NormalizerInterface $normalizer, $format = null, array $context = []) - { - $normalizer = new ObjectNormalizer(); - $normalizer->setIgnoredAttributes(['tracks']); - - return $normalizer->normalize($this); - } } \ No newline at end of file diff --git a/src/Model/Stop.php b/src/Model/Stop.php index 4d0ee1f..aeaa429 100644 --- a/src/Model/Stop.php +++ b/src/Model/Stop.php @@ -2,6 +2,7 @@ namespace App\Model; +use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Normalizer\NormalizableInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Tightenco\Collect\Support\Arr; @@ -76,6 +77,7 @@ class Stop implements Referable, Fillable, NormalizableInterface $this->variant = $variant; } + /** @Groups({"hidden"}) */ public function getLatitude(): ?float { return $this->latitude; @@ -86,6 +88,7 @@ class Stop implements Referable, Fillable, NormalizableInterface $this->latitude = $latitude; } + /** @Groups({"hidden"}) */ public function getLongitude(): ?float { return $this->longitude; diff --git a/src/Provider/Database/DatabaseRepository.php b/src/Provider/Database/DatabaseRepository.php index 18b622b..1581733 100644 --- a/src/Provider/Database/DatabaseRepository.php +++ b/src/Provider/Database/DatabaseRepository.php @@ -2,9 +2,12 @@ namespace App\Provider\Database; +use App\Entity\Entity; use App\Entity\ProviderEntity; +use App\Service\EntityConverter; use App\Service\IdUtils; use Doctrine\ORM\EntityManagerInterface; +use Kadet\Functional as f; class DatabaseRepository { @@ -17,15 +20,19 @@ class DatabaseRepository /** @var IdUtils */ protected $id; + /** @var EntityConverter */ + protected $converter; + /** * DatabaseRepository constructor. * * @param EntityManagerInterface $em */ - public function __construct(EntityManagerInterface $em, IdUtils $id) + public function __construct(EntityManagerInterface $em, IdUtils $id, EntityConverter $converter) { - $this->em = $em; - $this->id = $id; + $this->em = $em; + $this->id = $id; + $this->converter = $converter; } /** @return static */ @@ -36,4 +43,9 @@ class DatabaseRepository return $result; } + + protected function convert(Entity $entity) + { + return $this->converter->convert($entity); + } } \ No newline at end of file diff --git a/src/Provider/Database/GenericLineRepository.php b/src/Provider/Database/GenericLineRepository.php index 94235f6..e9bab49 100644 --- a/src/Provider/Database/GenericLineRepository.php +++ b/src/Provider/Database/GenericLineRepository.php @@ -33,15 +33,4 @@ class GenericLineRepository extends DatabaseRepository implements LineRepository return collect($lines)->map(f\ref([$this, 'convert'])); } - - private function convert(LineEntity $line): Line - { - return Line::createFromArray([ - 'id' => $this->id->of($line), - 'symbol' => $line->getSymbol(), - 'night' => $line->isNight(), - 'fast' => $line->isFast(), - 'type' => $line->getType() - ]); - } } \ No newline at end of file diff --git a/src/Provider/Database/GenericStopRepository.php b/src/Provider/Database/GenericStopRepository.php index 2ed9efb..25bd674 100644 --- a/src/Provider/Database/GenericStopRepository.php +++ b/src/Provider/Database/GenericStopRepository.php @@ -15,7 +15,7 @@ class GenericStopRepository extends DatabaseRepository implements StopRepository { $stops = $this->em->getRepository(StopEntity::class)->findAll(); - return collect($stops)->map(\Closure::fromCallable([$this, 'convert'])); + return collect($stops)->map(f\ref([$this, 'convert'])); } public function getAllGroups(): Collection @@ -36,7 +36,7 @@ class GenericStopRepository extends DatabaseRepository implements StopRepository $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(\Closure::fromCallable([$this, 'convert'])); + return collect($stops)->map(f\ref([$this, 'convert'])); } public function findGroupsByName(string $name): Collection @@ -47,26 +47,11 @@ class GenericStopRepository extends DatabaseRepository implements StopRepository ->where('s.name LIKE :name') ->getQuery(); - $stops = collect($query->execute([':name' => "%$name%"]))->map(\Closure::fromCallable([$this, 'convert'])); + $stops = collect($query->execute([':name' => "%$name%"]))->map(f\ref([$this, 'convert'])); return $this->group($stops); } - private function convert(StopEntity $entity): Stop - { - return Stop::createFromArray([ - 'id' => $this->id->of($entity), - 'name' => $entity->getName(), - 'description' => $entity->getDescription(), - 'variant' => $entity->getVariant(), - 'onDemand' => $entity->isOnDemand(), - 'location' => [ - $entity->getLatitude(), - $entity->getLongitude(), - ], - ]); - } - private function group(Collection $stops) { return $stops->groupBy(function (Stop $stop) { diff --git a/src/Service/EntityConverter.php b/src/Service/EntityConverter.php new file mode 100644 index 0000000..a50d61f --- /dev/null +++ b/src/Service/EntityConverter.php @@ -0,0 +1,151 @@ +id = $id; + $this->reference = $reference; + } + + /** + * @param Entity $entity + * @param array $cache + * + * @return Line|Track|Stop|Operator + */ + public function convert(Entity $entity, array $cache = []) + { + if (array_key_exists($key = get_class($entity).':'.$this->getId($entity), $cache)) { + return $cache[$key]; + } + + if ($entity instanceof Proxy && !$entity->__isInitialized()) { + return $this->reference($entity); + } + + $result = $this->create($entity); + $cache = $cache + [$key => $result]; + $convert = f\partial([$this, 'convert'], _, $cache); + + switch (true) { + case $entity instanceof OperatorEntity: + $result->fill([ + 'name' => $entity->getName(), + 'phone' => $entity->getPhone(), + 'email' => $entity->getEmail(), + 'url' => $entity->getEmail(), + ]); + break; + + case $entity instanceof LineEntity: + $result->fill([ + 'id' => $this->id->of($entity), + 'symbol' => $entity->getSymbol(), + 'type' => $entity->getType(), + 'operator' => $convert($entity->getOperator()), + 'tracks' => $this->collection($entity->getTracks(), $convert), + ]); + break; + + case $entity instanceof TrackEntity: + $result->fill([ + 'variant' => $entity->getVariant(), + 'description' => $entity->getDescription(), + 'stops' => $this->collection($entity->getStops(), $convert), + 'line' => $convert($entity->getLine()), + ]); + break; + + case $entity instanceof StopEntity: + $result->fill([ + 'name' => $entity->getName(), + 'variant' => $entity->getVariant(), + 'description' => $entity->getDescription(), + 'location' => [ + $entity->getLatitude(), + $entity->getLongitude(), + ], + ]); + } + + return $result; + } + + // HACK to not trigger doctrine stupid lazy loading. + private function getId(Entity $entity) { + if ($entity instanceof Proxy) { + $id = (new \ReflectionClass(get_parent_class($entity)))->getProperty('id'); + $id->setAccessible(true); + + return $id->getValue($entity); + } + + return $entity->getId(); + } + + private function collection(PersistentCollection $collection, $converter) + { + if ($collection->isInitialized()) { + return collect($collection)->map($converter); + } + + return collect(); + } + + private function getModelClassForEntity(Entity $entity) + { + switch (true) { + case $entity instanceof OperatorEntity: + return Operator::class; + + case $entity instanceof LineEntity: + return Line::class; + + case $entity instanceof TrackEntity: + return Track::class; + + case $entity instanceof StopEntity: + return Stop::class; + + default: + return false; + } + } + + private function create(Entity $entity) + { + $id = $this->id->of($entity); + $class = $this->getModelClassForEntity($entity); + + return $class::createFromArray(['id' => $id]); + } + + private function reference(Entity $entity) + { + $id = $this->id->strip($this->getId($entity)); + $class = $this->getModelClassForEntity($entity); + + return $this->reference->get($class, ['id' => $id]); + } +} \ No newline at end of file diff --git a/src/Service/IdUtils.php b/src/Service/IdUtils.php index d1fe477..61bc00b 100644 --- a/src/Service/IdUtils.php +++ b/src/Service/IdUtils.php @@ -7,18 +7,20 @@ use App\Entity\ProviderEntity; class IdUtils { + const DELIMITER = '::'; + public function generate(ProviderEntity $provider, $id) { - return sprintf('%s-%s', $provider->getId(), $id); + return sprintf('%s%s%s', $provider->getId(), self::DELIMITER, $id); } - public function strip(ProviderEntity $provider, $id) + public function strip($id) { - return substr($id, strlen($provider->getId()) + 1); + return explode(self::DELIMITER, $id)[1]; } public function of(Entity $entity) { - return $this->strip($entity->getProvider(), $entity->getId()); + return $this->strip($entity->getId()); } } \ No newline at end of file diff --git a/src/Service/Normalizer/JustReferenceNormalizer.php b/src/Service/Normalizer/JustReferenceNormalizer.php new file mode 100644 index 0000000..d30deea --- /dev/null +++ b/src/Service/Normalizer/JustReferenceNormalizer.php @@ -0,0 +1,44 @@ + $object->getId() ]; + } + + /** + * Checks whether the given class is supported for normalization by this normalizer. + * + * @param mixed $data Data to normalize + * @param string $format The format being (de-)serialized from or into + * + * @return bool + */ + public function supportsNormalization($data, $format = null) + { + return $data instanceof JustReference; + } +} \ No newline at end of file diff --git a/src/Service/Normalizer/StopNormalizer.php b/src/Service/Normalizer/StopNormalizer.php new file mode 100644 index 0000000..9a75a10 --- /dev/null +++ b/src/Service/Normalizer/StopNormalizer.php @@ -0,0 +1,28 @@ +normalizer = $normalizer; + } + + public function normalize($object, $format = null, array $context = []) + { + return Arr::except($this->normalizer->normalize($object), ['latitude', 'longitude']); + } + + public function supportsNormalization($data, $format = null) + { + return $data instanceof Stop; + } +} \ No newline at end of file diff --git a/src/Service/Proxy/ReferenceObjectFactory.php b/src/Service/Proxy/ReferenceObjectFactory.php new file mode 100644 index 0000000..fdb0dd4 --- /dev/null +++ b/src/Service/Proxy/ReferenceObjectFactory.php @@ -0,0 +1,25 @@ +generateProxy($class); + + $object = new $proxy(); + $object->fill($id); + return $object; + } + + protected function getGenerator(): ProxyGeneratorInterface + { + return new ReferenceObjectGenerator(); + } +} \ No newline at end of file diff --git a/src/Service/Proxy/ReferenceObjectGenerator.php b/src/Service/Proxy/ReferenceObjectGenerator.php new file mode 100644 index 0000000..0dad49f --- /dev/null +++ b/src/Service/Proxy/ReferenceObjectGenerator.php @@ -0,0 +1,19 @@ +getInterfaceNames(), [ JustReference::class ]); + + $generator->setExtendedClass($class->getName()); + $generator->setImplementedInterfaces($interfaces); + } +} \ No newline at end of file