Skip to content

Commit

Permalink
Merge pull request #148 from dnna/efficientorderexport
Browse files Browse the repository at this point in the history
Make order export more efficient when using ORM
  • Loading branch information
Matthias Alt authored Oct 22, 2018
2 parents 9657286 + 6742a85 commit 76aa551
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 10 deletions.
16 changes: 8 additions & 8 deletions spec/Exporter/Plugin/OrderResourcePluginSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use FriendsOfSylius\SyliusImportExportPlugin\Exporter\ORM\Hydrator\HydratorInterface;
use FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin\OrderResourcePlugin;
use FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin\ResourcePlugin;
use FriendsOfSylius\SyliusImportExportPlugin\Service\AddressConcatenationInterface;
Expand All @@ -29,9 +30,10 @@ function let(
RepositoryInterface $repository,
PropertyAccessorInterface $propertyAccessor,
EntityManagerInterface $entityManager,
AddressConcatenationInterface $addressConcatenation
AddressConcatenationInterface $addressConcatenation,
HydratorInterface $hydrator
) {
$this->beConstructedWith($repository, $propertyAccessor, $entityManager, $addressConcatenation);
$this->beConstructedWith($repository, $propertyAccessor, $entityManager, $addressConcatenation, $hydrator);
}

function it_is_initializable()
Expand All @@ -45,19 +47,17 @@ function it_implements_the_resource_plugin_interface()
}

function it_should_add_customer_data(
RepositoryInterface $repository,
EntityManagerInterface $entityManager,
OrderInterface $resource,
PropertyAccessorInterface $propertyAccessor,
ClassMetaData $classMetadata,
AddressConcatenationInterface $addressConcatenation
AddressConcatenationInterface $addressConcatenation,
HydratorInterface $hydrator
) {
$idsToExport = [1];

$repository->findBy(
[
'id' => $idsToExport,
]
$hydrator->getHydratedResources(
$idsToExport
)->willReturn(
[
$resource,
Expand Down
8 changes: 8 additions & 0 deletions src/Controller/ExportDataController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use FriendsOfSylius\SyliusImportExportPlugin\Exporter\ExporterRegistry;
use FriendsOfSylius\SyliusImportExportPlugin\Exporter\ResourceExporterInterface;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
use Sylius\Bundle\ResourceBundle\Controller\RequestConfiguration;
use Sylius\Bundle\ResourceBundle\Controller\RequestConfigurationFactoryInterface;
Expand Down Expand Up @@ -101,6 +102,13 @@ private function findRepository(string $resource): RepositoryInterface
*/
private function getResourceIds($resources): array
{
if ($resources instanceof ResourceGridView
&& $resources->getData()->getAdapter() instanceof DoctrineORMAdapter) {
$query = $resources->getData()->getAdapter()->getQuery()->setMaxResults(null);

return array_column($query->getArrayResult(), 'id');
}

return array_map(function (ResourceInterface $resource) {
return $resource->getId();
}, $this->getResources($resources));
Expand Down
17 changes: 17 additions & 0 deletions src/Exporter/ORM/Hydrator/HydratorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace FriendsOfSylius\SyliusImportExportPlugin\Exporter\ORM\Hydrator;

use Sylius\Component\Resource\Model\ResourceInterface;

interface HydratorInterface
{
/**
* @param int[]|string[] $idsToExport
*
* @return ResourceInterface[]
*/
public function getHydratedResources(array $idsToExport): array;
}
100 changes: 100 additions & 0 deletions src/Exporter/ORM/Hydrator/OrderHydrator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

declare(strict_types=1);

namespace FriendsOfSylius\SyliusImportExportPlugin\Exporter\ORM\Hydrator;

use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Sylius\Component\Resource\Model\ResourceInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;

final class OrderHydrator implements HydratorInterface
{
/**
* @var RepositoryInterface
*/
private $repository;

public function __construct(
RepositoryInterface $repository
) {
$this->repository = $repository;
}

/**
* {@inheritdoc}
*/
public function getHydratedResources(array $idsToExport): array
{
if (!$this->repository instanceof \Doctrine\ORM\EntityRepository) {
/** @var ResourceInterface[] $items */
$items = $this->repository->findBy(['id' => $idsToExport]);

return $items;
}

$query = $this->findOrdersQb($idsToExport)->getQuery();
$items = $this->enableEagerLoading($query)->getResult();
$this->hydrateOrderItemsQb($idsToExport)->getQuery()->getResult(); // This result can be discarded

return $items;
}

/**
* @param int[]|string[] $idsToExport
*/
private function findOrdersQb(array $idsToExport): QueryBuilder
{
/**
* @var \Doctrine\ORM\EntityRepository
*/
$repository = $this->repository;

return $repository->createQueryBuilder('o')
->andWhere('o.id IN (:exportIds)')
->setParameter('exportIds', $idsToExport)
;
}

/**
* @param int[]|string[] $idsToExport
*/
private function hydrateOrderItemsQb(array $idsToExport): QueryBuilder
{
/**
* @var \Doctrine\ORM\EntityRepository
*/
$repository = $this->repository;

// Partial hydration to make sure order items don't get lazy-loaded
return $repository->createQueryBuilder('o')
->select('PARTIAL o.{id}, items')
->leftJoin('o.items', 'items')
->andWhere('o.id IN (:exportIds)')
->setParameter('exportIds', $idsToExport)
;
}

private function enableEagerLoading(Query $query): Query
{
return $query
->setFetchMode(
$this->repository->getClassName(),
'customer',
ClassMetadata::FETCH_EAGER
)
->setFetchMode(
$this->repository->getClassName(),
'shippingAddress',
ClassMetadata::FETCH_EAGER
)
->setFetchMode(
$this->repository->getClassName(),
'billingAddress',
ClassMetadata::FETCH_EAGER
)
;
}
}
19 changes: 18 additions & 1 deletion src/Exporter/Plugin/OrderResourcePlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
namespace FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin;

use Doctrine\ORM\EntityManagerInterface;
use FriendsOfSylius\SyliusImportExportPlugin\Exporter\ORM\Hydrator\HydratorInterface;
use FriendsOfSylius\SyliusImportExportPlugin\Service\AddressConcatenationInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\OrderItemInterface;
use Sylius\Component\Product\Model\ProductInterface;
use Sylius\Component\Product\Model\ProductVariantInterface;
use Sylius\Component\Resource\Model\ResourceInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;

Expand All @@ -20,15 +22,22 @@ class OrderResourcePlugin extends ResourcePlugin
*/
private $addressConcatenation;

/**
* @var HydratorInterface
*/
private $orderHydrator;

public function __construct(
RepositoryInterface $repository,
PropertyAccessorInterface $propertyAccessor,
EntityManagerInterface $entityManager,
AddressConcatenationInterface $addressConcatenation
AddressConcatenationInterface $addressConcatenation,
HydratorInterface $orderHydrator
) {
parent::__construct($repository, $propertyAccessor, $entityManager);

$this->addressConcatenation = $addressConcatenation;
$this->orderHydrator = $orderHydrator;
}

/**
Expand Down Expand Up @@ -131,4 +140,12 @@ private function addOrderItemData(array $items, OrderInterface $resource): void

$this->addDataForResource($resource, 'Product_list', $str);
}

protected function findResources(array $idsToExport): array
{
/** @var ResourceInterface[] $items */
$items = $this->orderHydrator->getHydratedResources($idsToExport);

return $items;
}
}
2 changes: 1 addition & 1 deletion src/Exporter/Plugin/ResourcePlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ private function addDataForId(ResourceInterface $resource): void
*
* @return ResourceInterface[]
*/
private function findResources(array $idsToExport): array
protected function findResources(array $idsToExport): array
{
/** @var ResourceInterface[] $items */
$items = $this->repository->findBy(['id' => $idsToExport]);
Expand Down
7 changes: 7 additions & 0 deletions src/Resources/config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ services:
- "@property_accessor"
- "@doctrine.orm.entity_manager"
- "@sylius.service.address_concatenation"
- "@sylius.exporter.orm.hydrator.orders"

sylius.exporter.plugin.resource.customers:
class: FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin\ResourcePlugin
Expand All @@ -108,6 +109,12 @@ services:
- "@property_accessor"
- "@doctrine.orm.entity_manager"

# ORM hydrators to improve performance
sylius.exporter.orm.hydrator.orders:
class: FriendsOfSylius\SyliusImportExportPlugin\Exporter\ORM\Hydrator\OrderHydrator
arguments:
- "@sylius.repository.order"

# PluginPools for Exporters. Can contain multiple Plugins
sylius.exporter.pluginpool.countries:
class: FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin\PluginPool
Expand Down

0 comments on commit 76aa551

Please sign in to comment.