database entity conversion
This commit is contained in:
parent
7c5ee55612
commit
432d90fa1b
@ -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",
|
||||
|
2
composer.lock
generated
2
composer.lock
generated
@ -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",
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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()
|
||||
]);
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
151
src/Service/EntityConverter.php
Normal file
151
src/Service/EntityConverter.php
Normal file
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\Entity;
|
||||
use App\Entity\LineEntity;
|
||||
use App\Entity\OperatorEntity;
|
||||
use App\Entity\StopEntity;
|
||||
use App\Entity\TrackEntity;
|
||||
use App\Model\Line;
|
||||
use App\Model\Operator;
|
||||
use App\Model\Stop;
|
||||
use App\Model\Track;
|
||||
use App\Service\Proxy\ReferenceObjectFactory;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Proxy\Proxy;
|
||||
use Kadet\Functional as f;
|
||||
use const Kadet\Functional\_;
|
||||
|
||||
final class EntityConverter
|
||||
{
|
||||
private $id;
|
||||
private $reference;
|
||||
|
||||
public function __construct(IdUtils $id, ReferenceObjectFactory $reference)
|
||||
{
|
||||
$this->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]);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
44
src/Service/Normalizer/JustReferenceNormalizer.php
Normal file
44
src/Service/Normalizer/JustReferenceNormalizer.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Normalizer;
|
||||
|
||||
use App\Model\JustReference;
|
||||
use Symfony\Component\Serializer\Exception\CircularReferenceException;
|
||||
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Serializer\Exception\LogicException;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
|
||||
class JustReferenceNormalizer implements NormalizerInterface
|
||||
{
|
||||
/**
|
||||
* Normalizes an object into a set of arrays/scalars.
|
||||
*
|
||||
* @param mixed $object Object to normalize
|
||||
* @param string $format Format the normalization result will be encoded as
|
||||
* @param array $context Context options for the normalizer
|
||||
*
|
||||
* @return array|string|int|float|bool
|
||||
*
|
||||
* @throws InvalidArgumentException Occurs when the object given is not an attempted type for the normalizer
|
||||
* @throws CircularReferenceException Occurs when the normalizer detects a circular reference when no circular
|
||||
* reference handler can fix it
|
||||
* @throws LogicException Occurs when the normalizer is not called in an expected context
|
||||
*/
|
||||
public function normalize($object, $format = null, array $context = [])
|
||||
{
|
||||
return [ 'id' => $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;
|
||||
}
|
||||
}
|
28
src/Service/Normalizer/StopNormalizer.php
Normal file
28
src/Service/Normalizer/StopNormalizer.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Normalizer;
|
||||
|
||||
use App\Model\Stop;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||
use Tightenco\Collect\Support\Arr;
|
||||
|
||||
class StopNormalizer implements NormalizerInterface
|
||||
{
|
||||
private $normalizer;
|
||||
|
||||
public function __construct(ObjectNormalizer $normalizer)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
25
src/Service/Proxy/ReferenceObjectFactory.php
Normal file
25
src/Service/Proxy/ReferenceObjectFactory.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Proxy;
|
||||
|
||||
use ProxyManager\Factory\AbstractBaseFactory;
|
||||
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;
|
||||
|
||||
class ReferenceObjectFactory extends AbstractBaseFactory
|
||||
{
|
||||
public function get($class, $id)
|
||||
{
|
||||
$id = is_array($id) ? $id : compact('id');
|
||||
|
||||
$proxy = $this->generateProxy($class);
|
||||
|
||||
$object = new $proxy();
|
||||
$object->fill($id);
|
||||
return $object;
|
||||
}
|
||||
|
||||
protected function getGenerator(): ProxyGeneratorInterface
|
||||
{
|
||||
return new ReferenceObjectGenerator();
|
||||
}
|
||||
}
|
19
src/Service/Proxy/ReferenceObjectGenerator.php
Normal file
19
src/Service/Proxy/ReferenceObjectGenerator.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Proxy;
|
||||
|
||||
use App\Model\JustReference;
|
||||
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;
|
||||
use ReflectionClass;
|
||||
use Zend\Code\Generator\ClassGenerator;
|
||||
|
||||
class ReferenceObjectGenerator implements ProxyGeneratorInterface
|
||||
{
|
||||
public function generate(ReflectionClass $class, ClassGenerator $generator)
|
||||
{
|
||||
$interfaces = array_merge($class->getInterfaceNames(), [ JustReference::class ]);
|
||||
|
||||
$generator->setExtendedClass($class->getName());
|
||||
$generator->setImplementedInterfaces($interfaces);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user